diff --git a/core/src/cluster_info_vote_listener.rs b/core/src/cluster_info_vote_listener.rs index 3752c17184..4a11fb02ee 100644 --- a/core/src/cluster_info_vote_listener.rs +++ b/core/src/cluster_info_vote_listener.rs @@ -82,3 +82,41 @@ impl Service for ClusterInfoVoteListener { Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::locktower::MAX_RECENT_VOTES; + use crate::packet; + use solana_sdk::hash::Hash; + use solana_sdk::signature::{Keypair, KeypairUtil}; + use solana_sdk::transaction::Transaction; + use solana_vote_api::vote_instruction; + use solana_vote_api::vote_state::Vote; + + #[test] + fn test_max_vote_tx_fits() { + solana_logger::setup(); + let node_keypair = Keypair::new(); + let vote_keypair = Keypair::new(); + let votes = (0..MAX_RECENT_VOTES) + .map(|i| Vote::new(i as u64, Hash::default())) + .collect::>(); + let vote_ix = vote_instruction::vote( + &node_keypair.pubkey(), + &vote_keypair.pubkey(), + &vote_keypair.pubkey(), + votes, + ); + + let mut vote_tx = Transaction::new_unsigned_instructions(vec![vote_ix]); + vote_tx.partial_sign(&[&node_keypair], Hash::default()); + vote_tx.partial_sign(&[&vote_keypair], Hash::default()); + + use bincode::serialized_size; + info!("max vote size {}", serialized_size(&vote_tx).unwrap()); + + let msgs = packet::to_packets(&[vote_tx]); // panics if won't fit + + assert_eq!(msgs.len(), 1); + } +} diff --git a/core/src/locktower.rs b/core/src/locktower.rs index a20fe6f3ea..6cc69a387c 100644 --- a/core/src/locktower.rs +++ b/core/src/locktower.rs @@ -4,13 +4,15 @@ use hashbrown::{HashMap, HashSet}; use solana_metrics::datapoint_info; use solana_runtime::bank::Bank; use solana_sdk::account::Account; +use solana_sdk::hash::Hash; use solana_sdk::pubkey::Pubkey; use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY}; +use std::collections::VecDeque; use std::sync::Arc; pub const VOTE_THRESHOLD_DEPTH: usize = 8; pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64; -const MAX_RECENT_VOTES: usize = 16; +pub const MAX_RECENT_VOTES: usize = 16; #[derive(Default)] pub struct EpochStakes { @@ -33,6 +35,7 @@ pub struct Locktower { threshold_depth: usize, threshold_size: f64, lockouts: VoteState, + recent_votes: VecDeque, } impl EpochStakes { @@ -83,6 +86,7 @@ impl Locktower { threshold_depth: VOTE_THRESHOLD_DEPTH, threshold_size: VOTE_THRESHOLD_SIZE, lockouts: VoteState::default(), + recent_votes: VecDeque::default(), }; let bank = locktower.find_heaviest_bank(bank_forks).unwrap(); @@ -96,6 +100,7 @@ impl Locktower { threshold_depth, threshold_size, lockouts: VoteState::default(), + recent_votes: VecDeque::default(), } } pub fn collect_vote_lockouts( @@ -113,8 +118,11 @@ impl Locktower { if lamports == 0 { continue; } - let mut vote_state: VoteState = VoteState::deserialize(&account.data) - .expect("bank should always have valid VoteState data"); + let vote_state = VoteState::from(&account); + if vote_state.is_none() { + continue; + } + let mut vote_state = vote_state.unwrap(); if key == self.epoch_stakes.delegate_id || vote_state.node_id == self.epoch_stakes.delegate_id @@ -136,7 +144,9 @@ impl Locktower { ); } let start_root = vote_state.root_slot; - vote_state.process_vote(&Vote { slot: bank_slot }); + + vote_state.process_slot_vote_unchecked(bank_slot); + for vote in &vote_state.votes { Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); } @@ -220,9 +230,23 @@ impl Locktower { } } - pub fn record_vote(&mut self, slot: u64) -> Option { + pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option { let root_slot = self.lockouts.root_slot; - self.lockouts.process_vote(&Vote { slot }); + let vote = Vote { slot, hash }; + self.lockouts.process_vote_unchecked(&vote); + + // vote_state doesn't keep around the hashes, so we save them in recent_votes + self.recent_votes.push_back(vote); + let slots = self + .lockouts + .votes + .iter() + .skip(self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES)) + .map(|vote| vote.slot) + .collect::>(); + self.recent_votes + .retain(|vote| slots.iter().any(|slot| vote.slot == *slot)); + datapoint_info!( "locktower-vote", ("latest", slot, i64), @@ -236,10 +260,7 @@ impl Locktower { } pub fn recent_votes(&self) -> Vec { - let start = self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES); - (start..self.lockouts.votes.len()) - .map(|i| Vote::new(self.lockouts.votes[i].slot)) - .collect() + self.recent_votes.iter().cloned().collect::>() } pub fn root(&self) -> Option { @@ -269,7 +290,7 @@ impl Locktower { pub fn is_locked_out(&self, slot: u64, descendants: &HashMap>) -> bool { let mut lockouts = self.lockouts.clone(); - lockouts.process_vote(&Vote { slot }); + lockouts.process_slot_vote_unchecked(slot); for vote in &lockouts.votes { if vote.slot == slot { continue; @@ -291,7 +312,7 @@ impl Locktower { stake_lockouts: &HashMap, ) -> bool { let mut lockouts = self.lockouts.clone(); - lockouts.process_vote(&Vote { slot }); + lockouts.process_slot_vote_unchecked(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) { @@ -386,7 +407,7 @@ mod test { account.lamports = *lamports; let mut vote_state = VoteState::default(); for slot in *votes { - vote_state.process_vote(&Vote { slot: *slot }); + vote_state.process_slot_vote_unchecked(*slot); } vote_state .serialize(&mut account.data) @@ -431,7 +452,7 @@ mod test { 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); + locktower.record_vote(i as u64, Hash::default()); ancestors.insert(i as u64, (0..i as u64).into_iter().collect()); } assert_eq!(locktower.lockouts.root_slot, Some(0)); @@ -569,7 +590,7 @@ mod test { #[test] fn test_check_already_voted() { let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); - locktower.record_vote(0); + locktower.record_vote(0, Hash::default()); assert!(locktower.has_voted(0)); assert!(!locktower.has_voted(1)); } @@ -580,8 +601,8 @@ mod test { let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())] .into_iter() .collect(); - locktower.record_vote(0); - locktower.record_vote(1); + locktower.record_vote(0, Hash::default()); + locktower.record_vote(1, Hash::default()); assert!(locktower.is_locked_out(0, &descendants)); } @@ -591,7 +612,7 @@ mod test { let descendants = vec![(0, vec![1].into_iter().collect())] .into_iter() .collect(); - locktower.record_vote(0); + locktower.record_vote(0, Hash::default()); assert!(!locktower.is_locked_out(1, &descendants)); } @@ -605,8 +626,8 @@ mod test { ] .into_iter() .collect(); - locktower.record_vote(0); - locktower.record_vote(1); + locktower.record_vote(0, Hash::default()); + locktower.record_vote(1, Hash::default()); assert!(locktower.is_locked_out(2, &descendants)); } @@ -616,10 +637,10 @@ mod test { let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())] .into_iter() .collect(); - locktower.record_vote(0); - locktower.record_vote(1); + locktower.record_vote(0, Hash::default()); + locktower.record_vote(1, Hash::default()); assert!(!locktower.is_locked_out(4, &descendants)); - locktower.record_vote(4); + locktower.record_vote(4, Hash::default()); 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); @@ -638,7 +659,7 @@ mod test { )] .into_iter() .collect(); - locktower.record_vote(0); + locktower.record_vote(0, Hash::default()); assert!(!locktower.check_vote_stake_threshold(1, &stakes)); } #[test] @@ -653,7 +674,7 @@ mod test { )] .into_iter() .collect(); - locktower.record_vote(0); + locktower.record_vote(0, Hash::default()); assert!(locktower.check_vote_stake_threshold(1, &stakes)); } @@ -669,9 +690,9 @@ mod test { )] .into_iter() .collect(); - locktower.record_vote(0); - locktower.record_vote(1); - locktower.record_vote(2); + locktower.record_vote(0, Hash::default()); + locktower.record_vote(1, Hash::default()); + locktower.record_vote(2, Hash::default()); assert!(locktower.check_vote_stake_threshold(6, &stakes)); } @@ -679,7 +700,7 @@ mod 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); + locktower.record_vote(0, Hash::default()); assert!(!locktower.check_vote_stake_threshold(1, &stakes)); } @@ -770,7 +791,7 @@ mod test { // threshold check let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64; for vote in &locktower_votes { - locktower.record_vote(*vote); + locktower.record_vote(*vote, Hash::default()); } let stakes_lockouts = locktower.collect_vote_lockouts( vote_to_evaluate, @@ -791,9 +812,11 @@ mod test { fn vote_and_check_recent(num_votes: usize) { let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); let start = num_votes.saturating_sub(MAX_RECENT_VOTES); - let expected: Vec<_> = (start..num_votes).map(|i| Vote::new(i as u64)).collect(); + let expected: Vec<_> = (start..num_votes) + .map(|i| Vote::new(i as u64, Hash::default())) + .collect(); for i in 0..num_votes { - locktower.record_vote(i as u64); + locktower.record_vote(i as u64, Hash::default()); } assert_eq!(expected, locktower.recent_votes()) } diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 5708335618..34792c5806 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -302,7 +302,7 @@ impl ReplayStage { where T: 'static + KeypairUtil + Send + Sync, { - if let Some(new_root) = locktower.record_vote(bank.slot()) { + if let Some(new_root) = locktower.record_vote(bank.slot(), bank.hash()) { // get the root bank before squash let root_bank = bank_forks .read() diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 8974ac6782..1b765ce2f3 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -206,14 +206,14 @@ mod tests { use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_vote_api::vote_state::{self, Vote}; + use solana_vote_api::vote_state; #[test] fn test_stake_delegate_stake() { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(&Vote::new(i)); + vote_state.process_slot_vote_unchecked(i); } let vote_pubkey = vote_keypair.pubkey(); @@ -272,7 +272,7 @@ mod tests { // put a credit in the vote_state while vote_state.credits() == 0 { - vote_state.process_vote(&Vote::new(vote_i)); + vote_state.process_slot_vote_unchecked(vote_i); vote_i += 1; } // this guy can't collect now, not enough stake to get paid on 1 credit @@ -291,7 +291,7 @@ mod tests { // put more credit in the vote_state while vote_state.credits() < 10 { - vote_state.process_vote(&Vote::new(vote_i)); + vote_state.process_slot_vote_unchecked(vote_i); vote_i += 1; } vote_state.commission = 0; @@ -318,7 +318,7 @@ mod tests { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(&Vote::new(i)); + vote_state.process_slot_vote_unchecked(i); } let vote_pubkey = vote_keypair.pubkey(); @@ -364,7 +364,7 @@ mod tests { ); // move the vote account forward - vote_state.process_vote(&Vote::new(1000)); + vote_state.process_slot_vote_unchecked(1000); vote_keyed_account.set_state(&vote_state).unwrap(); // now, no lamports in the pool! @@ -392,7 +392,7 @@ mod tests { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(&Vote::new(i)); + vote_state.process_slot_vote_unchecked(i); } let vote_pubkey = vote_keypair.pubkey(); @@ -421,7 +421,7 @@ mod tests { let mut vote_state = VoteState::default(); for i in 0..100 { // go back in time, previous state had 1000 votes - vote_state.process_vote(&Vote::new(i)); + vote_state.process_slot_vote_unchecked(i); } vote_keyed_account.set_state(&vote_state).unwrap(); // voter credits lower than stake_delegate credits... TODO: is this an error? diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index 6b14710232..11b74ee04f 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -127,7 +127,29 @@ pub fn process_instruction( } VoteInstruction::Vote(votes) => { datapoint_warn!("vote-native", ("count", 1, i64)); - vote_state::process_votes(me, other_signers, &votes) + // TODO: remove me when Bank does this + let valid_votes = votes + .iter() + .map(|vote| (vote.slot, vote.hash)) + .collect::>(); + + use bincode::serialized_size; + use solana_sdk::account::Account; + use solana_sdk::account_utils::State; + use solana_sdk::syscall::slot_hashes; + let mut valid_votes_account = Account::new( + 0, + serialized_size(&valid_votes).unwrap() as usize, + &Pubkey::default(), + ); + valid_votes_account.set_state(&valid_votes).unwrap(); + // END TODO: remove me when Bank does this + vote_state::process_votes( + me, + &mut KeyedAccount::new(&slot_hashes::id(), false, &mut valid_votes_account), + other_signers, + &votes, + ) } } } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 201fcfb829..4207c34cff 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -2,11 +2,14 @@ //! Receive and processes votes from validators use crate::id; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; +use log::*; use serde_derive::{Deserialize, Serialize}; use solana_sdk::account::{Account, KeyedAccount}; use solana_sdk::account_utils::State; +use solana_sdk::hash::Hash; use solana_sdk::instruction::InstructionError; use solana_sdk::pubkey::Pubkey; +use solana_sdk::syscall::slot_hashes; use std::collections::VecDeque; // Maximum number of votes to keep around @@ -15,14 +18,15 @@ pub const INITIAL_LOCKOUT: usize = 2; #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Vote { - // TODO: add signature of the state here as well /// A vote for height slot pub slot: u64, + // signature of the bank's state at given slot + pub hash: Hash, } impl Vote { - pub fn new(slot: u64) -> Self { - Self { slot } + pub fn new(slot: u64, hash: Hash) -> Self { + Self { slot, hash } } } @@ -122,11 +126,11 @@ impl VoteState { } } - pub fn process_votes(&mut self, votes: &[Vote]) { - votes.iter().for_each(|v| self.process_vote(v));; + pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(u64, Hash)]) { + votes.iter().for_each(|v| self.process_vote(v, slot_hashes)); } - pub fn process_vote(&mut self, vote: &Vote) { + pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(u64, Hash)]) { // Ignore votes for slots earlier than we already have votes for if self .votes @@ -136,6 +140,18 @@ impl VoteState { return; } + // drop votes for which there is no matching slot and hash + if !slot_hashes + .iter() + .any(|(slot, hash)| vote.slot == *slot && vote.hash == *hash) + { + warn!( + "dropping vote {:?}, no matching slot/hash combination", + vote + ); + return; + } + let vote = Lockout::new(&vote); // TODO: Integrity checks @@ -152,6 +168,13 @@ impl VoteState { self.double_lockouts(); } + pub fn process_vote_unchecked(&mut self, vote: &Vote) { + self.process_vote(vote, &[(vote.slot, vote.hash)]); + } + pub fn process_slot_vote_unchecked(&mut self, slot: u64) { + self.process_vote_unchecked(&Vote::new(slot, Hash::default())); + } + pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> { if position < self.votes.len() { let pos = self.votes.len() - 1 - position; @@ -235,6 +258,7 @@ pub fn initialize_account( pub fn process_votes( vote_account: &mut KeyedAccount, + slot_hashes_account: &mut KeyedAccount, other_signers: &[KeyedAccount], votes: &[Vote], ) -> Result<(), InstructionError> { @@ -244,6 +268,12 @@ pub fn process_votes( return Err(InstructionError::UninitializedAccount); } + if !slot_hashes::check_id(slot_hashes_account.unsigned_key()) { + return Err(InstructionError::InvalidArgument); + } + + let slot_hashes: Vec<(u64, Hash)> = slot_hashes_account.state()?; + let authorized = Some(&vote_state.authorized_voter_id); // find a signer that matches the authorized_voter_id if vote_account.signer_key() != authorized @@ -254,7 +284,7 @@ pub fn process_votes( return Err(InstructionError::MissingRequiredSignature); } - vote_state.process_votes(&votes); + vote_state.process_votes(&votes, &slot_hashes); vote_account.set_state(&vote_state) } @@ -287,30 +317,24 @@ pub fn create_bootstrap_leader_account( // will be forced to select it as the leader for height 0 let mut vote_account = create_account(&vote_id, &node_id, commission, lamports); - vote(&vote_id, &mut vote_account, &Vote::new(0)).unwrap(); + let mut vote_state: VoteState = vote_account.state().unwrap(); + // TODO: get a hash for slot 0? + vote_state.process_slot_vote_unchecked(0); - let vote_state = vote_account.state().expect("account.state()"); + vote_account.set_state(&vote_state).unwrap(); (vote_account, vote_state) } -// utility function, used by Bank, tests -pub fn vote( - vote_id: &Pubkey, - vote_account: &mut Account, - vote: &Vote, -) -> Result { - process_votes( - &mut KeyedAccount::new(vote_id, true, vote_account), - &[], - &[vote.clone()], - )?; - vote_account.state() -} - #[cfg(test)] mod tests { use super::*; use crate::vote_state; + use bincode::serialized_size; + use solana_sdk::account::Account; + use solana_sdk::account_utils::State; + use solana_sdk::hash::hash; + use solana_sdk::syscall; + use solana_sdk::syscall::slot_hashes; const MAX_RECENT_VOTES: usize = 16; @@ -339,6 +363,45 @@ mod tests { ) } + fn create_test_slot_hashes_account(slot_hashes: &[(u64, Hash)]) -> (Pubkey, Account) { + let mut slot_hashes_account = Account::new( + 0, + serialized_size(&slot_hashes).unwrap() as usize, + &syscall::id(), + ); + slot_hashes_account + .set_state(&slot_hashes.to_vec()) + .unwrap(); + (slot_hashes::id(), slot_hashes_account) + } + + fn simulate_process_vote( + vote_id: &Pubkey, + vote_account: &mut Account, + vote: &Vote, + slot_hashes: &[(u64, Hash)], + ) -> Result { + let (slot_hashes_id, mut slot_hashes_account) = + create_test_slot_hashes_account(slot_hashes); + + process_votes( + &mut KeyedAccount::new(vote_id, true, vote_account), + &mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account), + &[], + &[vote.clone()], + )?; + vote_account.state() + } + + /// exercises all the keyed accounts stuff + fn simulate_process_vote_unchecked( + vote_id: &Pubkey, + vote_account: &mut Account, + vote: &Vote, + ) -> Result { + simulate_process_vote(vote_id, vote_account, vote, &[(vote.slot, vote.hash)]) + } + #[test] fn test_vote_create_bootstrap_leader_account() { let vote_id = Pubkey::new_rand(); @@ -346,7 +409,7 @@ mod tests { vote_state::create_bootstrap_leader_account(&vote_id, &Pubkey::new_rand(), 0, 100); assert_eq!(vote_state.votes.len(), 1); - assert_eq!(vote_state.votes[0], Lockout::new(&Vote::new(0))); + assert_eq!(vote_state.votes[0], Lockout::new(&Vote::default())); } #[test] @@ -374,21 +437,62 @@ mod tests { fn test_vote() { let (vote_id, mut vote_account) = create_test_account(); - let vote = Vote::new(1); - let vote_state = vote_state::vote(&vote_id, &mut vote_account, &vote).unwrap(); + let vote = Vote::new(1, Hash::default()); + let vote_state = + simulate_process_vote_unchecked(&vote_id, &mut vote_account, &vote).unwrap(); assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]); assert_eq!(vote_state.credits(), 0); } + #[test] + fn test_vote_slot_hashes() { + let (vote_id, mut vote_account) = create_test_account(); + + let hash = hash(&[0u8]); + let vote = Vote::new(0, hash); + + // wrong hash + let vote_state = + simulate_process_vote(&vote_id, &mut vote_account, &vote, &[(0, Hash::default())]) + .unwrap(); + assert_eq!(vote_state.votes.len(), 0); + + // wrong slot + let vote_state = + simulate_process_vote(&vote_id, &mut vote_account, &vote, &[(1, hash)]).unwrap(); + assert_eq!(vote_state.votes.len(), 0); + + // empty slot_hashes + let vote_state = simulate_process_vote(&vote_id, &mut vote_account, &vote, &[]).unwrap(); + assert_eq!(vote_state.votes.len(), 0); + + // this one would work, but the wrong account is passed for slot_hashes_id + let (_slot_hashes_id, mut slot_hashes_account) = + create_test_slot_hashes_account(&[(vote.slot, vote.hash)]); + assert_eq!( + process_votes( + &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &mut KeyedAccount::new(&Pubkey::default(), false, &mut slot_hashes_account), + &[], + &[vote.clone()], + ), + Err(InstructionError::InvalidArgument) + ); + } + #[test] fn test_vote_signature() { let (vote_id, mut vote_account) = create_test_account(); - let vote = vec![Vote::new(1)]; + let vote = vec![Vote::new(1, Hash::default())]; + + let (slot_hashes_id, mut slot_hashes_account) = + create_test_slot_hashes_account(&[(1, Hash::default())]); // unsigned let res = process_votes( &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account), &[], &vote, ); @@ -397,6 +501,7 @@ mod tests { // unsigned let res = process_votes( &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account), &[], &vote, ); @@ -430,18 +535,22 @@ mod tests { assert_eq!(res, Ok(())); // not signed by authorized voter - let vote = vec![Vote::new(2)]; + let vote = vec![Vote::new(2, Hash::default())]; + let (slot_hashes_id, mut slot_hashes_account) = + create_test_slot_hashes_account(&[(2, Hash::default())]); let res = process_votes( &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account), &[], &vote, ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); // signed by authorized voter - let vote = vec![Vote::new(2)]; + let vote = vec![Vote::new(2, Hash::default())]; let res = process_votes( &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account), &[KeyedAccount::new( &authorized_voter_id, true, @@ -457,7 +566,11 @@ mod tests { let vote_id = Pubkey::new_rand(); let mut vote_account = Account::new(100, VoteState::size_of(), &id()); - let res = vote_state::vote(&vote_id, &mut vote_account, &Vote::new(1)); + let res = simulate_process_vote_unchecked( + &vote_id, + &mut vote_account, + &Vote::new(1, Hash::default()), + ); assert_eq!(res, Err(InstructionError::UninitializedAccount)); } @@ -468,7 +581,7 @@ mod tests { let mut vote_state: VoteState = vote_account.state().unwrap(); for i in 0..(MAX_LOCKOUT_HISTORY + 1) { - vote_state.process_vote(&Vote::new((INITIAL_LOCKOUT as usize * i) as u64)); + vote_state.process_slot_vote_unchecked((INITIAL_LOCKOUT as usize * i) as u64); } // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY @@ -480,14 +593,11 @@ mod tests { // the root_slot should change to the // second vote let top_vote = vote_state.votes.front().unwrap().slot; - vote_state.process_vote(&Vote::new( - vote_state.votes.back().unwrap().expiration_slot(), - )); + vote_state.process_slot_vote_unchecked(vote_state.votes.back().unwrap().expiration_slot()); assert_eq!(Some(top_vote), vote_state.root_slot); // Expire everything except the first vote - let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot()); - vote_state.process_vote(&vote); + vote_state.process_slot_vote_unchecked(vote_state.votes.front().unwrap().expiration_slot()); // First vote and new vote are both stored for a total of 2 votes assert_eq!(vote_state.votes.len(), 2); } @@ -498,8 +608,7 @@ mod tests { let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..3 { - let vote = Vote::new(i as u64); - vote_state.process_vote(&vote); + vote_state.process_slot_vote_unchecked(i as u64); } check_lockouts(&vote_state); @@ -507,17 +616,17 @@ mod tests { // Expire the third vote (which was a vote for slot 2). The height of the // vote stack is unchanged, so none of the previous votes should have // doubled in lockout - vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 1) as u64)); + vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 1) as u64); check_lockouts(&vote_state); // Vote again, this time the vote stack depth increases, so the lockouts should // double for everybody - vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 2) as u64)); + vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 2) as u64); check_lockouts(&vote_state); // Vote again, this time the vote stack depth increases, so the lockouts should // double for everybody - vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 3) as u64)); + vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 3) as u64); check_lockouts(&vote_state); } @@ -527,15 +636,14 @@ mod tests { let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..3 { - let vote = Vote::new(i as u64); - vote_state.process_vote(&vote); + vote_state.process_slot_vote_unchecked(i as u64); } assert_eq!(vote_state.votes[0].confirmation_count, 3); // Expire the second and third votes let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1; - vote_state.process_vote(&Vote::new(expire_slot)); + vote_state.process_slot_vote_unchecked(expire_slot); assert_eq!(vote_state.votes.len(), 2); // Check that the old votes expired @@ -543,7 +651,7 @@ mod tests { assert_eq!(vote_state.votes[1].slot, expire_slot); // Process one more vote - vote_state.process_vote(&Vote::new(expire_slot + 1)); + vote_state.process_slot_vote_unchecked(expire_slot + 1); // Confirmation count for the older first vote should remain unchanged assert_eq!(vote_state.votes[0].confirmation_count, 3); @@ -559,16 +667,16 @@ mod tests { let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..MAX_LOCKOUT_HISTORY { - vote_state.process_vote(&Vote::new(i as u64)); + vote_state.process_slot_vote_unchecked(i as u64); } assert_eq!(vote_state.credits, 0); - vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1)); + vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 1); assert_eq!(vote_state.credits, 1); - vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2)); + vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 2); assert_eq!(vote_state.credits(), 2); - vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3)); + vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 3); assert_eq!(vote_state.credits(), 3); } @@ -576,9 +684,9 @@ mod tests { fn test_duplicate_vote() { let voter_id = Pubkey::new_rand(); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); - vote_state.process_vote(&Vote::new(0)); - vote_state.process_vote(&Vote::new(1)); - vote_state.process_vote(&Vote::new(0)); + vote_state.process_slot_vote_unchecked(0); + vote_state.process_slot_vote_unchecked(1); + vote_state.process_slot_vote_unchecked(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()); @@ -589,7 +697,7 @@ mod tests { let voter_id = Pubkey::new_rand(); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..MAX_LOCKOUT_HISTORY { - vote_state.process_vote(&Vote::new(i as u64)); + vote_state.process_slot_vote_unchecked(i as u64); } for i in 0..(MAX_LOCKOUT_HISTORY - 1) { assert_eq!( @@ -613,7 +721,7 @@ mod tests { fn recent_votes(vote_state: &VoteState) -> Vec { let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES); (start..vote_state.votes.len()) - .map(|i| Vote::new(vote_state.votes.get(i).unwrap().slot)) + .map(|i| Vote::new(vote_state.votes.get(i).unwrap().slot, Hash::default())) .collect() } @@ -626,17 +734,20 @@ mod tests { let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0); // process some votes on account a - let votes_a: Vec<_> = (0..5).into_iter().map(|i| Vote::new(i)).collect(); - vote_state_a.process_votes(&votes_a); + (0..5) + .into_iter() + .for_each(|i| vote_state_a.process_slot_vote_unchecked(i as u64)); assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync let votes: Vec<_> = (0..MAX_RECENT_VOTES) .into_iter() - .map(|i| Vote::new(i as u64)) + .map(|i| Vote::new(i as u64, Hash::default())) .collect(); - vote_state_a.process_votes(&votes); - vote_state_b.process_votes(&votes); + let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect(); + + vote_state_a.process_votes(&votes, &slot_hashes); + vote_state_b.process_votes(&votes, &slot_hashes); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); } diff --git a/sdk/src/packet.rs b/sdk/src/packet.rs index a28761155b..ba0a9cac92 100644 --- a/sdk/src/packet.rs +++ b/sdk/src/packet.rs @@ -1,2 +1,5 @@ /// Maximum over-the-wire size of a Transaction -pub const PACKET_DATA_SIZE: usize = 512; +/// 1280 is IPv6 minimum MTU +/// 40 bytes is the size of the IPv6 header +/// 8 bytes is the size of the fragment header +pub const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; diff --git a/sdk/src/syscall/mod.rs b/sdk/src/syscall/mod.rs index 929c150d07..4cc764e05f 100644 --- a/sdk/src/syscall/mod.rs +++ b/sdk/src/syscall/mod.rs @@ -2,7 +2,7 @@ //! use crate::pubkey::Pubkey; -pub mod slothashes; +pub mod slot_hashes; /// "Sysca11111111111111111111111111111111111111" /// owner pubkey for syscall accounts diff --git a/sdk/src/syscall/slothashes.rs b/sdk/src/syscall/slot_hashes.rs similarity index 94% rename from sdk/src/syscall/slothashes.rs rename to sdk/src/syscall/slot_hashes.rs index 5f3447f2e9..35085be81f 100644 --- a/sdk/src/syscall/slothashes.rs +++ b/sdk/src/syscall/slot_hashes.rs @@ -17,6 +17,10 @@ pub fn id() -> Pubkey { Pubkey::new(&SYSCALL_SLOT_HASHES_ID) } +pub fn check_id(pubkey: &Pubkey) -> bool { + pubkey.as_ref() == SYSCALL_SLOT_HASHES_ID +} + use crate::account_utils::State; use std::ops::{Deref, DerefMut}; #[derive(Serialize, Deserialize)]