Implement locktower voting (#3251)
* locktower components and tests * integrate locktower into replay stage * track locktower duration * make sure threshold is checked after simulating the vote * check vote lockouts using the VoteState program * duplicate vote test * epoch stakes * disable impossible to verify tests
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							cedff2fca1
						
					
				
				
					commit
					61a4b998fa
				
			| @@ -1,7 +1,7 @@ | |||||||
| //! The `bank_forks` module implments BankForks a DAG of checkpointed Banks | //! The `bank_forks` module implments BankForks a DAG of checkpointed Banks | ||||||
|  |  | ||||||
|  | use hashbrown::{HashMap, HashSet}; | ||||||
| use solana_runtime::bank::Bank; | use solana_runtime::bank::Bank; | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::ops::Index; | use std::ops::Index; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
| @@ -27,6 +27,47 @@ impl BankForks { | |||||||
|             working_bank, |             working_bank, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Create a map of bank slot id to the set of ancestors for the bank slot. | ||||||
|  |     pub fn ancestors(&self) -> HashMap<u64, HashSet<u64>> { | ||||||
|  |         let mut ancestors = HashMap::new(); | ||||||
|  |         let mut pending: Vec<Arc<Bank>> = self.banks.values().cloned().collect(); | ||||||
|  |         while !pending.is_empty() { | ||||||
|  |             let bank = pending.pop().unwrap(); | ||||||
|  |             if ancestors.get(&bank.slot()).is_some() { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             let set = bank.parents().into_iter().map(|b| b.slot()).collect(); | ||||||
|  |             ancestors.insert(bank.slot(), set); | ||||||
|  |             pending.extend(bank.parents().into_iter()); | ||||||
|  |         } | ||||||
|  |         ancestors | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Create a map of bank slot id to the set of all of its descendants | ||||||
|  |     pub fn decendants(&self) -> HashMap<u64, HashSet<u64>> { | ||||||
|  |         let mut decendants = HashMap::new(); | ||||||
|  |         let mut pending: Vec<Arc<Bank>> = self.banks.values().cloned().collect(); | ||||||
|  |         let mut done = HashSet::new(); | ||||||
|  |         assert!(!pending.is_empty()); | ||||||
|  |         while !pending.is_empty() { | ||||||
|  |             let bank = pending.pop().unwrap(); | ||||||
|  |             if done.contains(&bank.slot()) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             done.insert(bank.slot()); | ||||||
|  |             let _ = decendants.entry(bank.slot()).or_insert(HashSet::new()); | ||||||
|  |             for parent in bank.parents() { | ||||||
|  |                 decendants | ||||||
|  |                     .entry(parent.slot()) | ||||||
|  |                     .or_insert(HashSet::new()) | ||||||
|  |                     .insert(bank.slot()); | ||||||
|  |             } | ||||||
|  |             pending.extend(bank.parents().into_iter()); | ||||||
|  |         } | ||||||
|  |         decendants | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub fn frozen_banks(&self) -> HashMap<u64, Arc<Bank>> { |     pub fn frozen_banks(&self) -> HashMap<u64, Arc<Bank>> { | ||||||
|         let mut frozen_banks: Vec<Arc<Bank>> = vec![]; |         let mut frozen_banks: Vec<Arc<Bank>> = vec![]; | ||||||
|         frozen_banks.extend(self.banks.values().filter(|v| v.is_frozen()).cloned()); |         frozen_banks.extend(self.banks.values().filter(|v| v.is_frozen()).cloned()); | ||||||
| @@ -106,6 +147,41 @@ mod tests { | |||||||
|         assert_eq!(bank_forks.working_bank().tick_height(), 1); |         assert_eq!(bank_forks.working_bank().tick_height(), 1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_bank_forks_decendants() { | ||||||
|  |         let (genesis_block, _) = GenesisBlock::new(10_000); | ||||||
|  |         let bank = Bank::new(&genesis_block); | ||||||
|  |         let mut bank_forks = BankForks::new(0, bank); | ||||||
|  |         let bank0 = bank_forks[0].clone(); | ||||||
|  |         let bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 1); | ||||||
|  |         bank_forks.insert(1, bank); | ||||||
|  |         let bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 2); | ||||||
|  |         bank_forks.insert(2, bank); | ||||||
|  |         let decendants = bank_forks.decendants(); | ||||||
|  |         let children: Vec<u64> = decendants[&0].iter().cloned().collect(); | ||||||
|  |         assert_eq!(children, vec![1, 2]); | ||||||
|  |         assert!(decendants[&1].is_empty()); | ||||||
|  |         assert!(decendants[&2].is_empty()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_bank_forks_ancestors() { | ||||||
|  |         let (genesis_block, _) = GenesisBlock::new(10_000); | ||||||
|  |         let bank = Bank::new(&genesis_block); | ||||||
|  |         let mut bank_forks = BankForks::new(0, bank); | ||||||
|  |         let bank0 = bank_forks[0].clone(); | ||||||
|  |         let bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 1); | ||||||
|  |         bank_forks.insert(1, bank); | ||||||
|  |         let bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 2); | ||||||
|  |         bank_forks.insert(2, bank); | ||||||
|  |         let ancestors = bank_forks.ancestors(); | ||||||
|  |         assert!(ancestors[&0].is_empty()); | ||||||
|  |         let parents: Vec<u64> = ancestors[&1].iter().cloned().collect(); | ||||||
|  |         assert_eq!(parents, vec![0]); | ||||||
|  |         let parents: Vec<u64> = ancestors[&2].iter().cloned().collect(); | ||||||
|  |         assert_eq!(parents, vec![0]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_bank_forks_frozen_banks() { |     fn test_bank_forks_frozen_banks() { | ||||||
|         let (genesis_block, _) = GenesisBlock::new(10_000); |         let (genesis_block, _) = GenesisBlock::new(10_000); | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ pub mod leader_schedule; | |||||||
| pub mod leader_schedule_utils; | pub mod leader_schedule_utils; | ||||||
| pub mod local_cluster; | pub mod local_cluster; | ||||||
| pub mod local_vote_signer_service; | pub mod local_vote_signer_service; | ||||||
|  | pub mod locktower; | ||||||
| pub mod packet; | pub mod packet; | ||||||
| pub mod poh; | pub mod poh; | ||||||
| pub mod poh_recorder; | pub mod poh_recorder; | ||||||
|   | |||||||
							
								
								
									
										575
									
								
								core/src/locktower.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								core/src/locktower.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,575 @@ | |||||||
|  | use crate::bank_forks::BankForks; | ||||||
|  | use crate::staking_utils; | ||||||
|  | use hashbrown::{HashMap, HashSet}; | ||||||
|  | use solana_runtime::bank::Bank; | ||||||
|  | use solana_sdk::account::Account; | ||||||
|  | use solana_sdk::pubkey::Pubkey; | ||||||
|  | use solana_vote_api::vote_instruction::Vote; | ||||||
|  | use solana_vote_api::vote_state::{Lockout, VoteState, MAX_LOCKOUT_HISTORY}; | ||||||
|  |  | ||||||
|  | const VOTE_THRESHOLD_DEPTH: usize = 8; | ||||||
|  | const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64; | ||||||
|  |  | ||||||
|  | #[derive(Default)] | ||||||
|  | pub struct EpochStakes { | ||||||
|  |     slot: u64, | ||||||
|  |     stakes: HashMap<Pubkey, u64>, | ||||||
|  |     total_staked: u64, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Default)] | ||||||
|  | pub struct StakeLockout { | ||||||
|  |     lockout: u64, | ||||||
|  |     stake: u64, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Default)] | ||||||
|  | pub struct Locktower { | ||||||
|  |     epoch_stakes: EpochStakes, | ||||||
|  |     threshold_depth: usize, | ||||||
|  |     threshold_size: f64, | ||||||
|  |     lockouts: VoteState, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl EpochStakes { | ||||||
|  |     pub fn new(slot: u64, stakes: HashMap<Pubkey, u64>) -> Self { | ||||||
|  |         let total_staked = stakes.values().sum(); | ||||||
|  |         Self { | ||||||
|  |             slot, | ||||||
|  |             stakes, | ||||||
|  |             total_staked, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pub fn new_for_tests(lamports: u64) -> Self { | ||||||
|  |         Self::new(0, vec![(Pubkey::default(), lamports)].into_iter().collect()) | ||||||
|  |     } | ||||||
|  |     pub fn new_from_stake_accounts(slot: u64, accounts: &[(Pubkey, Account)]) -> Self { | ||||||
|  |         let stakes = accounts.iter().map(|(k, v)| (*k, v.lamports)).collect(); | ||||||
|  |         Self::new(slot, stakes) | ||||||
|  |     } | ||||||
|  |     pub fn new_from_bank(bank: &Bank) -> 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) | ||||||
|  |             .expect("voting require a bank with stakes"); | ||||||
|  |         Self::new(bank_epoch, stakes) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Locktower { | ||||||
|  |     pub fn new_from_forks(bank_forks: &BankForks) -> Self { | ||||||
|  |         //TODO: which bank to start with? | ||||||
|  |         let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect(); | ||||||
|  |         frozen_banks.sort_by_key(|b| (b.parents().len(), b.slot())); | ||||||
|  |         if let Some(bank) = frozen_banks.last() { | ||||||
|  |             Self::new_from_bank(bank) | ||||||
|  |         } else { | ||||||
|  |             Self::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn new_from_bank(bank: &Bank) -> Self { | ||||||
|  |         let current_epoch = bank.get_epoch_and_slot_index(bank.slot()).0; | ||||||
|  |         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 { | ||||||
|  |                 if *delegate_id == bank.collector_id() { | ||||||
|  |                     let state = VoteState::deserialize(&account.data).expect("votes"); | ||||||
|  |                     if lockouts.votes.len() < state.votes.len() { | ||||||
|  |                         //TODO: which state to init with? | ||||||
|  |                         lockouts = state; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         let epoch_stakes = EpochStakes::new_from_bank(bank); | ||||||
|  |         Self { | ||||||
|  |             epoch_stakes, | ||||||
|  |             threshold_depth: VOTE_THRESHOLD_DEPTH, | ||||||
|  |             threshold_size: VOTE_THRESHOLD_SIZE, | ||||||
|  |             lockouts, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pub fn new(epoch_stakes: EpochStakes, threshold_depth: usize, threshold_size: f64) -> Self { | ||||||
|  |         Self { | ||||||
|  |             epoch_stakes, | ||||||
|  |             threshold_depth, | ||||||
|  |             threshold_size, | ||||||
|  |             lockouts: VoteState::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pub fn collect_vote_lockouts<F>( | ||||||
|  |         &self, | ||||||
|  |         bank_slot: u64, | ||||||
|  |         vote_accounts: F, | ||||||
|  |         ancestors: &HashMap<u64, HashSet<u64>>, | ||||||
|  |     ) -> HashMap<u64, StakeLockout> | ||||||
|  |     where | ||||||
|  |         F: Iterator<Item = (Pubkey, Account)>, | ||||||
|  |     { | ||||||
|  |         let mut stake_lockouts = HashMap::new(); | ||||||
|  |         for (key, account) in vote_accounts { | ||||||
|  |             let lamports: u64 = *self.epoch_stakes.stakes.get(&key).unwrap_or(&0); | ||||||
|  |             if lamports == 0 { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             let mut vote_state: VoteState = VoteState::deserialize(&account.data) | ||||||
|  |                 .expect("bank should always have valid VoteState data"); | ||||||
|  |             let start_root = vote_state.root_slot; | ||||||
|  |             vote_state.process_vote(Vote { slot: bank_slot }); | ||||||
|  |             for vote in &vote_state.votes { | ||||||
|  |                 Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); | ||||||
|  |             } | ||||||
|  |             if start_root != vote_state.root_slot { | ||||||
|  |                 if let Some(root) = start_root { | ||||||
|  |                     let vote = Lockout { | ||||||
|  |                         confirmation_count: MAX_LOCKOUT_HISTORY as u32, | ||||||
|  |                         slot: root, | ||||||
|  |                     }; | ||||||
|  |                     Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if let Some(root) = vote_state.root_slot { | ||||||
|  |                 let vote = Lockout { | ||||||
|  |                     confirmation_count: MAX_LOCKOUT_HISTORY as u32, | ||||||
|  |                     slot: root, | ||||||
|  |                 }; | ||||||
|  |                 Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); | ||||||
|  |             } | ||||||
|  |             // each account hash a stake for all the forks in the active tree for this bank | ||||||
|  |             Self::update_ancestor_stakes(&mut stake_lockouts, bank_slot, lamports, ancestors); | ||||||
|  |         } | ||||||
|  |         stake_lockouts | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn update_epoch(&mut self, bank: &Bank) { | ||||||
|  |         let bank_epoch = bank.get_epoch_and_slot_index(bank.slot()).0; | ||||||
|  |         if bank_epoch != self.epoch_stakes.slot { | ||||||
|  |             assert!( | ||||||
|  |                 bank_epoch > self.epoch_stakes.slot, | ||||||
|  |                 "epoch_stakes cannot move backwards" | ||||||
|  |             ); | ||||||
|  |             self.epoch_stakes = EpochStakes::new_from_bank(bank); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn record_vote(&mut self, slot: u64) { | ||||||
|  |         self.lockouts.process_vote(Vote { slot }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn calculate_weight(&self, stake_lockouts: &HashMap<u64, StakeLockout>) -> u128 { | ||||||
|  |         let mut sum = 0u128; | ||||||
|  |         let root_slot = self.lockouts.root_slot.unwrap_or(0); | ||||||
|  |         for (slot, stake_lockout) in stake_lockouts { | ||||||
|  |             if self.lockouts.root_slot.is_some() && *slot <= root_slot { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             sum += u128::from(stake_lockout.lockout) * u128::from(stake_lockout.stake) | ||||||
|  |         } | ||||||
|  |         sum | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn has_voted(&self, slot: u64) -> bool { | ||||||
|  |         for vote in &self.lockouts.votes { | ||||||
|  |             if vote.slot == slot { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn is_locked_out(&self, slot: u64, decendants: &HashMap<u64, HashSet<u64>>) -> bool { | ||||||
|  |         let mut lockouts = self.lockouts.clone(); | ||||||
|  |         lockouts.process_vote(Vote { slot }); | ||||||
|  |         for vote in &lockouts.votes { | ||||||
|  |             if vote.slot == slot { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if !decendants[&vote.slot].contains(&slot) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if let Some(root) = lockouts.root_slot { | ||||||
|  |             decendants[&root].contains(&slot) | ||||||
|  |         } else { | ||||||
|  |             true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn check_vote_stake_threshold( | ||||||
|  |         &self, | ||||||
|  |         slot: u64, | ||||||
|  |         stake_lockouts: &HashMap<u64, StakeLockout>, | ||||||
|  |     ) -> bool { | ||||||
|  |         let mut lockouts = self.lockouts.clone(); | ||||||
|  |         lockouts.process_vote(Vote { slot }); | ||||||
|  |         let vote = lockouts.nth_recent_vote(self.threshold_depth); | ||||||
|  |         if let Some(vote) = vote { | ||||||
|  |             if let Some(fork_stake) = stake_lockouts.get(&vote.slot) { | ||||||
|  |                 (fork_stake.stake as f64 / self.epoch_stakes.total_staked as f64) | ||||||
|  |                     > self.threshold_size | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Update lockouts for all the ancestors | ||||||
|  |     fn update_ancestor_lockouts( | ||||||
|  |         stake_lockouts: &mut HashMap<u64, StakeLockout>, | ||||||
|  |         vote: &Lockout, | ||||||
|  |         ancestors: &HashMap<u64, HashSet<u64>>, | ||||||
|  |     ) { | ||||||
|  |         let mut slot_with_ancestors = vec![vote.slot]; | ||||||
|  |         slot_with_ancestors.extend(&ancestors[&vote.slot]); | ||||||
|  |         for slot in slot_with_ancestors { | ||||||
|  |             let entry = &mut stake_lockouts.entry(slot).or_default(); | ||||||
|  |             entry.lockout += vote.lockout(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Update stake for all the ancestors. | ||||||
|  |     /// Note, stake is the same for all the ancestor. | ||||||
|  |     fn update_ancestor_stakes( | ||||||
|  |         stake_lockouts: &mut HashMap<u64, StakeLockout>, | ||||||
|  |         slot: u64, | ||||||
|  |         lamports: u64, | ||||||
|  |         ancestors: &HashMap<u64, HashSet<u64>>, | ||||||
|  |     ) { | ||||||
|  |         let mut slot_with_ancestors = vec![slot]; | ||||||
|  |         slot_with_ancestors.extend(&ancestors[&slot]); | ||||||
|  |         for slot in slot_with_ancestors { | ||||||
|  |             let entry = &mut stake_lockouts.entry(slot).or_default(); | ||||||
|  |             entry.stake += lamports; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod test { | ||||||
|  |     use super::*; | ||||||
|  |     use solana_sdk::signature::{Keypair, KeypairUtil}; | ||||||
|  |  | ||||||
|  |     fn gen_accounts(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, Account)> { | ||||||
|  |         let mut accounts = vec![]; | ||||||
|  |         for (lamports, votes) in stake_votes { | ||||||
|  |             let mut account = Account::default(); | ||||||
|  |             account.data = vec![0; 1024]; | ||||||
|  |             account.lamports = *lamports; | ||||||
|  |             let mut vote_state = VoteState::default(); | ||||||
|  |             for slot in *votes { | ||||||
|  |                 vote_state.process_vote(Vote { slot: *slot }); | ||||||
|  |             } | ||||||
|  |             vote_state | ||||||
|  |                 .serialize(&mut account.data) | ||||||
|  |                 .expect("serialize state"); | ||||||
|  |             accounts.push((Keypair::new().pubkey(), account)); | ||||||
|  |         } | ||||||
|  |         accounts | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_collect_vote_lockouts_no_epoch_stakes() { | ||||||
|  |         let accounts = gen_accounts(&[(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())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors); | ||||||
|  |         assert!(staked_lockouts.is_empty()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[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 locktower = Locktower::new(epoch_stakes, 0, 0.67); | ||||||
|  |         let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors); | ||||||
|  |         assert_eq!(staked_lockouts[&0].stake, 2); | ||||||
|  |         assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_collect_vote_lockouts_root() { | ||||||
|  |         let votes: Vec<u64> = (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 mut locktower = Locktower::new(epoch_stakes, 0, 0.67); | ||||||
|  |         let mut ancestors = HashMap::new(); | ||||||
|  |         for i in 0..(MAX_LOCKOUT_HISTORY + 1) { | ||||||
|  |             locktower.record_vote(i as u64); | ||||||
|  |             ancestors.insert(i as u64, (0..i as u64).into_iter().collect()); | ||||||
|  |         } | ||||||
|  |         assert_eq!(locktower.lockouts.root_slot, Some(0)); | ||||||
|  |         let staked_lockouts = locktower.collect_vote_lockouts( | ||||||
|  |             MAX_LOCKOUT_HISTORY as u64, | ||||||
|  |             accounts.into_iter(), | ||||||
|  |             &ancestors, | ||||||
|  |         ); | ||||||
|  |         for i in 0..MAX_LOCKOUT_HISTORY { | ||||||
|  |             assert_eq!(staked_lockouts[&(i as u64)].stake, 2); | ||||||
|  |         } | ||||||
|  |         // should be the sum of all the weights for root | ||||||
|  |         assert!(staked_lockouts[&0].lockout > (2 * (1 << MAX_LOCKOUT_HISTORY))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_calculate_weight_skips_root() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         locktower.lockouts.root_slot = Some(1); | ||||||
|  |         let stakes = vec![ | ||||||
|  |             ( | ||||||
|  |                 0, | ||||||
|  |                 StakeLockout { | ||||||
|  |                     stake: 1, | ||||||
|  |                     lockout: 8, | ||||||
|  |                 }, | ||||||
|  |             ), | ||||||
|  |             ( | ||||||
|  |                 1, | ||||||
|  |                 StakeLockout { | ||||||
|  |                     stake: 1, | ||||||
|  |                     lockout: 8, | ||||||
|  |                 }, | ||||||
|  |             ), | ||||||
|  |         ] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         assert_eq!(locktower.calculate_weight(&stakes), 0u128); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_calculate_weight() { | ||||||
|  |         let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let stakes = vec![( | ||||||
|  |             0, | ||||||
|  |             StakeLockout { | ||||||
|  |                 stake: 1, | ||||||
|  |                 lockout: 8, | ||||||
|  |             }, | ||||||
|  |         )] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         assert_eq!(locktower.calculate_weight(&stakes), 8u128); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_vote_threshold_without_votes() { | ||||||
|  |         let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); | ||||||
|  |         let stakes = vec![( | ||||||
|  |             0, | ||||||
|  |             StakeLockout { | ||||||
|  |                 stake: 1, | ||||||
|  |                 lockout: 8, | ||||||
|  |             }, | ||||||
|  |         )] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         assert!(locktower.check_vote_stake_threshold(0, &stakes)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_empty() { | ||||||
|  |         let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = HashMap::new(); | ||||||
|  |         assert!(locktower.is_locked_out(0, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_root_slot_child() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![(0, vec![1].into_iter().collect())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         locktower.lockouts.root_slot = Some(0); | ||||||
|  |         assert!(locktower.is_locked_out(1, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_root_slot_sibling() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![(0, vec![1].into_iter().collect())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         locktower.lockouts.root_slot = Some(0); | ||||||
|  |         assert!(!locktower.is_locked_out(2, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_already_voted() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         assert!(locktower.has_voted(0)); | ||||||
|  |         assert!(!locktower.has_voted(1)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_double_vote() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         locktower.record_vote(1); | ||||||
|  |         assert!(!locktower.is_locked_out(0, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_child() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![(0, vec![1].into_iter().collect())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         assert!(locktower.is_locked_out(1, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_sibling() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![ | ||||||
|  |             (0, vec![1, 2].into_iter().collect()), | ||||||
|  |             (1, HashSet::new()), | ||||||
|  |             (2, HashSet::new()), | ||||||
|  |         ] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         locktower.record_vote(1); | ||||||
|  |         assert!(!locktower.is_locked_out(2, &decendants)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_is_locked_out_last_vote_expired() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); | ||||||
|  |         let decendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())] | ||||||
|  |             .into_iter() | ||||||
|  |             .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         locktower.record_vote(1); | ||||||
|  |         assert!(locktower.is_locked_out(4, &decendants)); | ||||||
|  |         locktower.record_vote(4); | ||||||
|  |         assert_eq!(locktower.lockouts.votes[0].slot, 0); | ||||||
|  |         assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2); | ||||||
|  |         assert_eq!(locktower.lockouts.votes[1].slot, 4); | ||||||
|  |         assert_eq!(locktower.lockouts.votes[1].confirmation_count, 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_vote_threshold_below_threshold() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); | ||||||
|  |         let stakes = vec![( | ||||||
|  |             0, | ||||||
|  |             StakeLockout { | ||||||
|  |                 stake: 1, | ||||||
|  |                 lockout: 8, | ||||||
|  |             }, | ||||||
|  |         )] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         assert!(!locktower.check_vote_stake_threshold(1, &stakes)); | ||||||
|  |     } | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_vote_threshold_above_threshold() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); | ||||||
|  |         let stakes = vec![( | ||||||
|  |             0, | ||||||
|  |             StakeLockout { | ||||||
|  |                 stake: 2, | ||||||
|  |                 lockout: 8, | ||||||
|  |             }, | ||||||
|  |         )] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         assert!(locktower.check_vote_stake_threshold(1, &stakes)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_vote_threshold_above_threshold_after_pop() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); | ||||||
|  |         let stakes = vec![( | ||||||
|  |             0, | ||||||
|  |             StakeLockout { | ||||||
|  |                 stake: 2, | ||||||
|  |                 lockout: 8, | ||||||
|  |             }, | ||||||
|  |         )] | ||||||
|  |         .into_iter() | ||||||
|  |         .collect(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         locktower.record_vote(1); | ||||||
|  |         locktower.record_vote(2); | ||||||
|  |         assert!(locktower.check_vote_stake_threshold(6, &stakes)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_check_vote_threshold_above_threshold_no_stake() { | ||||||
|  |         let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); | ||||||
|  |         let stakes = HashMap::new(); | ||||||
|  |         locktower.record_vote(0); | ||||||
|  |         assert!(!locktower.check_vote_stake_threshold(1, &stakes)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_lockout_is_updated_for_entire_branch() { | ||||||
|  |         let mut stake_lockouts = HashMap::new(); | ||||||
|  |         let vote = Lockout { | ||||||
|  |             slot: 2, | ||||||
|  |             confirmation_count: 1, | ||||||
|  |         }; | ||||||
|  |         let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect(); | ||||||
|  |         let mut ancestors = HashMap::new(); | ||||||
|  |         ancestors.insert(2, set); | ||||||
|  |         let set: HashSet<u64> = vec![0u64].into_iter().collect(); | ||||||
|  |         ancestors.insert(1, set); | ||||||
|  |         Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors); | ||||||
|  |         assert_eq!(stake_lockouts[&0].lockout, 2); | ||||||
|  |         assert_eq!(stake_lockouts[&1].lockout, 2); | ||||||
|  |         assert_eq!(stake_lockouts[&2].lockout, 2); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_lockout_is_updated_for_slot_or_lower() { | ||||||
|  |         let mut stake_lockouts = HashMap::new(); | ||||||
|  |         let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect(); | ||||||
|  |         let mut ancestors = HashMap::new(); | ||||||
|  |         ancestors.insert(2, set); | ||||||
|  |         let set: HashSet<u64> = vec![0u64].into_iter().collect(); | ||||||
|  |         ancestors.insert(1, set); | ||||||
|  |         let vote = Lockout { | ||||||
|  |             slot: 2, | ||||||
|  |             confirmation_count: 1, | ||||||
|  |         }; | ||||||
|  |         Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors); | ||||||
|  |         let vote = Lockout { | ||||||
|  |             slot: 1, | ||||||
|  |             confirmation_count: 2, | ||||||
|  |         }; | ||||||
|  |         Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors); | ||||||
|  |         assert_eq!(stake_lockouts[&0].lockout, 2 + 4); | ||||||
|  |         assert_eq!(stake_lockouts[&1].lockout, 2 + 4); | ||||||
|  |         assert_eq!(stake_lockouts[&2].lockout, 2); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_stake_is_updated_for_entire_branch() { | ||||||
|  |         let mut stake_lockouts = HashMap::new(); | ||||||
|  |         let mut account = Account::default(); | ||||||
|  |         account.lamports = 1; | ||||||
|  |         let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect(); | ||||||
|  |         let ancestors: HashMap<u64, HashSet<u64>> = [(2u64, set)].into_iter().cloned().collect(); | ||||||
|  |         Locktower::update_ancestor_stakes(&mut stake_lockouts, 2, account.lamports, &ancestors); | ||||||
|  |         assert_eq!(stake_lockouts[&0].stake, 1); | ||||||
|  |         assert_eq!(stake_lockouts[&1].stake, 1); | ||||||
|  |         assert_eq!(stake_lockouts[&2].stake, 1); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,6 +6,7 @@ use crate::blocktree_processor; | |||||||
| use crate::cluster_info::ClusterInfo; | use crate::cluster_info::ClusterInfo; | ||||||
| use crate::entry::{Entry, EntryReceiver, EntrySender, EntrySlice}; | use crate::entry::{Entry, EntryReceiver, EntrySender, EntrySlice}; | ||||||
| use crate::leader_schedule_utils; | use crate::leader_schedule_utils; | ||||||
|  | use crate::locktower::Locktower; | ||||||
| use crate::packet::BlobError; | use crate::packet::BlobError; | ||||||
| use crate::poh_recorder::PohRecorder; | use crate::poh_recorder::PohRecorder; | ||||||
| use crate::result; | use crate::result; | ||||||
| @@ -17,7 +18,7 @@ use solana_runtime::bank::Bank; | |||||||
| use solana_sdk::hash::Hash; | use solana_sdk::hash::Hash; | ||||||
| use solana_sdk::pubkey::Pubkey; | use solana_sdk::pubkey::Pubkey; | ||||||
| use solana_sdk::signature::KeypairUtil; | use solana_sdk::signature::KeypairUtil; | ||||||
| use solana_sdk::timing::duration_as_ms; | use solana_sdk::timing::{self, duration_as_ms}; | ||||||
| use solana_vote_api::vote_transaction::VoteTransaction; | use solana_vote_api::vote_transaction::VoteTransaction; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::sync::atomic::{AtomicBool, Ordering}; | use std::sync::atomic::{AtomicBool, Ordering}; | ||||||
| @@ -78,6 +79,7 @@ impl ReplayStage { | |||||||
|         let my_id = *my_id; |         let my_id = *my_id; | ||||||
|         let vote_account = *vote_account; |         let vote_account = *vote_account; | ||||||
|         let mut ticks_per_slot = 0; |         let mut ticks_per_slot = 0; | ||||||
|  |         let mut locktower = Locktower::new_from_forks(&bank_forks.read().unwrap()); | ||||||
|  |  | ||||||
|         // Start the replay stage loop |         // Start the replay stage loop | ||||||
|         let t_replay = Builder::new() |         let t_replay = Builder::new() | ||||||
| @@ -94,7 +96,6 @@ impl ReplayStage { | |||||||
|                     Self::generate_new_bank_forks(&blocktree, &mut bank_forks.write().unwrap()); |                     Self::generate_new_bank_forks(&blocktree, &mut bank_forks.write().unwrap()); | ||||||
|                     let active_banks = bank_forks.read().unwrap().active_banks(); |                     let active_banks = bank_forks.read().unwrap().active_banks(); | ||||||
|                     trace!("active banks {:?}", active_banks); |                     trace!("active banks {:?}", active_banks); | ||||||
|                     let mut votable: Vec<Arc<Bank>> = vec![]; |  | ||||||
|                     let mut is_tpu_bank_active = poh_recorder.lock().unwrap().bank().is_some(); |                     let mut is_tpu_bank_active = poh_recorder.lock().unwrap().bank().is_some(); | ||||||
|                     for bank_slot in &active_banks { |                     for bank_slot in &active_banks { | ||||||
|                         let bank = bank_forks.read().unwrap().get(*bank_slot).unwrap().clone(); |                         let bank = bank_forks.read().unwrap().get(*bank_slot).unwrap().clone(); | ||||||
| @@ -113,7 +114,6 @@ impl ReplayStage { | |||||||
|                                 &my_id, |                                 &my_id, | ||||||
|                                 bank, |                                 bank, | ||||||
|                                 &mut progress, |                                 &mut progress, | ||||||
|                                 &mut votable, |  | ||||||
|                                 &slot_full_sender, |                                 &slot_full_sender, | ||||||
|                             ); |                             ); | ||||||
|                         } |                         } | ||||||
| @@ -125,11 +125,40 @@ impl ReplayStage { | |||||||
|                         ticks_per_slot = bank.ticks_per_slot(); |                         ticks_per_slot = bank.ticks_per_slot(); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     // TODO: fork selection |                     let locktower_start = Instant::now(); | ||||||
|                     // vote on the latest one for now |                     // Locktower voting | ||||||
|                     votable.sort_by(|b1, b2| b1.slot().cmp(&b2.slot())); |                     let decendants = bank_forks.read().unwrap().decendants(); | ||||||
|  |                     let ancestors = bank_forks.read().unwrap().ancestors(); | ||||||
|  |                     let frozen_banks = bank_forks.read().unwrap().frozen_banks(); | ||||||
|  |                     let mut votable: Vec<(u128, Arc<Bank>)> = frozen_banks | ||||||
|  |                         .values() | ||||||
|  |                         .filter(|b| b.is_votable()) | ||||||
|  |                         .filter(|b| !locktower.has_voted(b.slot())) | ||||||
|  |                         .filter(|b| !locktower.is_locked_out(b.slot(), &decendants)) | ||||||
|  |                         .map(|bank| { | ||||||
|  |                             ( | ||||||
|  |                                 bank, | ||||||
|  |                                 locktower.collect_vote_lockouts( | ||||||
|  |                                     bank.slot(), | ||||||
|  |                                     bank.vote_accounts(), | ||||||
|  |                                     &ancestors, | ||||||
|  |                                 ), | ||||||
|  |                             ) | ||||||
|  |                         }) | ||||||
|  |                         .filter(|(b, stake_lockouts)| { | ||||||
|  |                             locktower.check_vote_stake_threshold(b.slot(), &stake_lockouts) | ||||||
|  |                         }) | ||||||
|  |                         .map(|(b, stake_lockouts)| { | ||||||
|  |                             (locktower.calculate_weight(&stake_lockouts), b.clone()) | ||||||
|  |                         }) | ||||||
|  |                         .collect(); | ||||||
|  |  | ||||||
|                     if let Some(bank) = votable.last() { |                     votable.sort_by_key(|b| b.0); | ||||||
|  |                     let ms = timing::duration_as_ms(&locktower_start.elapsed()); | ||||||
|  |                     info!("@{:?} locktower duration: {:?}", timing::timestamp(), ms,); | ||||||
|  |                     inc_new_counter_info!("replay_stage-locktower_duration", ms as usize); | ||||||
|  |  | ||||||
|  |                     if let Some((_, bank)) = votable.last() { | ||||||
|                         subscriptions.notify_subscribers(&bank); |                         subscriptions.notify_subscribers(&bank); | ||||||
|  |  | ||||||
|                         if let Some(ref voting_keypair) = voting_keypair { |                         if let Some(ref voting_keypair) = voting_keypair { | ||||||
| @@ -141,6 +170,8 @@ impl ReplayStage { | |||||||
|                                 bank.last_blockhash(), |                                 bank.last_blockhash(), | ||||||
|                                 0, |                                 0, | ||||||
|                             ); |                             ); | ||||||
|  |                             locktower.record_vote(bank.slot()); | ||||||
|  |                             locktower.update_epoch(&bank); | ||||||
|                             cluster_info.write().unwrap().push_vote(vote); |                             cluster_info.write().unwrap().push_vote(vote); | ||||||
|                         } |                         } | ||||||
|                         let next_leader_slot = |                         let next_leader_slot = | ||||||
| @@ -350,7 +381,6 @@ impl ReplayStage { | |||||||
|         my_id: &Pubkey, |         my_id: &Pubkey, | ||||||
|         bank: Arc<Bank>, |         bank: Arc<Bank>, | ||||||
|         progress: &mut HashMap<u64, (Hash, usize)>, |         progress: &mut HashMap<u64, (Hash, usize)>, | ||||||
|         votable: &mut Vec<Arc<Bank>>, |  | ||||||
|         slot_full_sender: &Sender<(u64, Pubkey)>, |         slot_full_sender: &Sender<(u64, Pubkey)>, | ||||||
|     ) { |     ) { | ||||||
|         bank.freeze(); |         bank.freeze(); | ||||||
| @@ -359,9 +389,6 @@ impl ReplayStage { | |||||||
|         if let Err(e) = slot_full_sender.send((bank.slot(), bank.collector_id())) { |         if let Err(e) = slot_full_sender.send((bank.slot(), bank.collector_id())) { | ||||||
|             info!("{} slot_full alert failed: {:?}", my_id, e); |             info!("{} slot_full alert failed: {:?}", my_id, e); | ||||||
|         } |         } | ||||||
|         if bank.is_votable() { |  | ||||||
|             votable.push(bank); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn generate_new_bank_forks(blocktree: &Blocktree, forks: &mut BankForks) { |     fn generate_new_bank_forks(blocktree: &Blocktree, forks: &mut BankForks) { | ||||||
| @@ -490,42 +517,6 @@ mod test { | |||||||
|         let _ignored = remove_dir_all(&my_ledger_path); |         let _ignored = remove_dir_all(&my_ledger_path); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_no_vote_empty_transmission() { |  | ||||||
|         let genesis_block = GenesisBlock::new(10_000).0; |  | ||||||
|         let bank = Arc::new(Bank::new(&genesis_block)); |  | ||||||
|         let mut blockhash = bank.last_blockhash(); |  | ||||||
|         let mut entries = Vec::new(); |  | ||||||
|         for _ in 0..genesis_block.ticks_per_slot { |  | ||||||
|             let entry = next_entry_mut(&mut blockhash, 1, vec![]); //just ticks |  | ||||||
|             entries.push(entry); |  | ||||||
|         } |  | ||||||
|         let (sender, _receiver) = channel(); |  | ||||||
|  |  | ||||||
|         let mut progress = HashMap::new(); |  | ||||||
|         let (forward_entry_sender, _forward_entry_receiver) = channel(); |  | ||||||
|         ReplayStage::replay_entries_into_bank( |  | ||||||
|             &bank, |  | ||||||
|             entries.clone(), |  | ||||||
|             &mut progress, |  | ||||||
|             &forward_entry_sender, |  | ||||||
|             0, |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
|  |  | ||||||
|         let mut votable = vec![]; |  | ||||||
|         ReplayStage::process_completed_bank( |  | ||||||
|             &Pubkey::default(), |  | ||||||
|             bank, |  | ||||||
|             &mut progress, |  | ||||||
|             &mut votable, |  | ||||||
|             &sender, |  | ||||||
|         ); |  | ||||||
|         assert!(progress.is_empty()); |  | ||||||
|         // Don't vote on slot that only contained ticks |  | ||||||
|         assert!(votable.is_empty()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_replay_stage_poh_ok_entry_receiver() { |     fn test_replay_stage_poh_ok_entry_receiver() { | ||||||
|         let (forward_entry_sender, forward_entry_receiver) = channel(); |         let (forward_entry_sender, forward_entry_receiver) = channel(); | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ fn node_staked_accounts(bank: &Bank) -> impl Iterator<Item = (Pubkey, u64, Accou | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| fn node_staked_accounts_at_epoch( | pub fn node_staked_accounts_at_epoch( | ||||||
|     bank: &Bank, |     bank: &Bank, | ||||||
|     epoch_height: u64, |     epoch_height: u64, | ||||||
| ) -> Option<impl Iterator<Item = (&Pubkey, u64, &Account)>> { | ) -> Option<impl Iterator<Item = (&Pubkey, u64, &Account)>> { | ||||||
|   | |||||||
| @@ -39,6 +39,9 @@ impl Lockout { | |||||||
|     pub fn expiration_slot(&self) -> u64 { |     pub fn expiration_slot(&self) -> u64 { | ||||||
|         self.slot + self.lockout() |         self.slot + self.lockout() | ||||||
|     } |     } | ||||||
|  |     pub fn is_expired(&self, slot: u64) -> bool { | ||||||
|  |         self.expiration_slot() < slot | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] | #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] | ||||||
| @@ -110,6 +113,15 @@ impl VoteState { | |||||||
|         self.double_lockouts(); |         self.double_lockouts(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> { | ||||||
|  |         if position < self.votes.len() { | ||||||
|  |             let pos = self.votes.len() - 1 - position; | ||||||
|  |             self.votes.get(pos) | ||||||
|  |         } else { | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Number of "credits" owed to this account from the mining pool. Submit this |     /// Number of "credits" owed to this account from the mining pool. Submit this | ||||||
|     /// VoteState to the Rewards program to trade credits for lamports. |     /// VoteState to the Rewards program to trade credits for lamports. | ||||||
|     pub fn credits(&self) -> u64 { |     pub fn credits(&self) -> u64 { | ||||||
| @@ -123,11 +135,7 @@ impl VoteState { | |||||||
|  |  | ||||||
|     fn pop_expired_votes(&mut self, slot: u64) { |     fn pop_expired_votes(&mut self, slot: u64) { | ||||||
|         loop { |         loop { | ||||||
|             if self |             if self.votes.back().map_or(false, |v| v.is_expired(slot)) { | ||||||
|                 .votes |  | ||||||
|                 .back() |  | ||||||
|                 .map_or(false, |v| v.expiration_slot() < slot) |  | ||||||
|             { |  | ||||||
|                 self.votes.pop_back(); |                 self.votes.pop_back(); | ||||||
|             } else { |             } else { | ||||||
|                 break; |                 break; | ||||||
| @@ -462,6 +470,34 @@ mod tests { | |||||||
|         assert_eq!(vote_state.credits(), 0); |         assert_eq!(vote_state.credits(), 0); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_duplicate_vote() { | ||||||
|  |         let voter_id = Keypair::new().pubkey(); | ||||||
|  |         let mut vote_state = VoteState::new(&voter_id); | ||||||
|  |         vote_state.process_vote(Vote::new(0)); | ||||||
|  |         vote_state.process_vote(Vote::new(1)); | ||||||
|  |         vote_state.process_vote(Vote::new(0)); | ||||||
|  |         assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1); | ||||||
|  |         assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0); | ||||||
|  |         assert!(vote_state.nth_recent_vote(2).is_none()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_nth_recent_vote() { | ||||||
|  |         let voter_id = Keypair::new().pubkey(); | ||||||
|  |         let mut vote_state = VoteState::new(&voter_id); | ||||||
|  |         for i in 0..MAX_LOCKOUT_HISTORY { | ||||||
|  |             vote_state.process_vote(Vote::new(i as u64)); | ||||||
|  |         } | ||||||
|  |         for i in 0..(MAX_LOCKOUT_HISTORY - 1) { | ||||||
|  |             assert_eq!( | ||||||
|  |                 vote_state.nth_recent_vote(i).unwrap().slot as usize, | ||||||
|  |                 MAX_LOCKOUT_HISTORY - i - 1, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         assert!(vote_state.nth_recent_vote(MAX_LOCKOUT_HISTORY).is_none()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn check_lockouts(vote_state: &VoteState) { |     fn check_lockouts(vote_state: &VoteState) { | ||||||
|         for (i, vote) in vote_state.votes.iter().enumerate() { |         for (i, vote) in vote_state.votes.iter().enumerate() { | ||||||
|             let num_lockouts = vote_state.votes.len() - i; |             let num_lockouts = vote_state.votes.len() - i; | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ fn test_spend_and_verify_all_nodes_1() { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
|  | #[ignore] //TODO: confirmations are not useful: #3346 | ||||||
| fn test_spend_and_verify_all_nodes_2() { | fn test_spend_and_verify_all_nodes_2() { | ||||||
|     solana_logger::setup(); |     solana_logger::setup(); | ||||||
|     let num_nodes = 2; |     let num_nodes = 2; | ||||||
| @@ -34,6 +35,7 @@ fn test_spend_and_verify_all_nodes_2() { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
|  | #[ignore] //TODO: confirmations are not useful: #3346 | ||||||
| fn test_spend_and_verify_all_nodes_3() { | fn test_spend_and_verify_all_nodes_3() { | ||||||
|     solana_logger::setup(); |     solana_logger::setup(); | ||||||
|     let num_nodes = 3; |     let num_nodes = 3; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user