add bank hash to votes (#4381)

This commit is contained in:
Rob Walker 2019-05-21 21:45:38 -07:00 committed by GitHub
parent de6838da78
commit 578c2ad3ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 304 additions and 103 deletions

View File

@ -82,3 +82,41 @@ impl Service for ClusterInfoVoteListener {
Ok(()) 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::<Vec<_>>();
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);
}
}

View File

@ -4,13 +4,15 @@ use hashbrown::{HashMap, HashSet};
use solana_metrics::datapoint_info; use solana_metrics::datapoint_info;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY}; use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY};
use std::collections::VecDeque;
use std::sync::Arc; use std::sync::Arc;
pub const VOTE_THRESHOLD_DEPTH: usize = 8; pub const VOTE_THRESHOLD_DEPTH: usize = 8;
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64; pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
const MAX_RECENT_VOTES: usize = 16; pub const MAX_RECENT_VOTES: usize = 16;
#[derive(Default)] #[derive(Default)]
pub struct EpochStakes { pub struct EpochStakes {
@ -33,6 +35,7 @@ pub struct Locktower {
threshold_depth: usize, threshold_depth: usize,
threshold_size: f64, threshold_size: f64,
lockouts: VoteState, lockouts: VoteState,
recent_votes: VecDeque<Vote>,
} }
impl EpochStakes { impl EpochStakes {
@ -83,6 +86,7 @@ impl Locktower {
threshold_depth: VOTE_THRESHOLD_DEPTH, threshold_depth: VOTE_THRESHOLD_DEPTH,
threshold_size: VOTE_THRESHOLD_SIZE, threshold_size: VOTE_THRESHOLD_SIZE,
lockouts: VoteState::default(), lockouts: VoteState::default(),
recent_votes: VecDeque::default(),
}; };
let bank = locktower.find_heaviest_bank(bank_forks).unwrap(); let bank = locktower.find_heaviest_bank(bank_forks).unwrap();
@ -96,6 +100,7 @@ impl Locktower {
threshold_depth, threshold_depth,
threshold_size, threshold_size,
lockouts: VoteState::default(), lockouts: VoteState::default(),
recent_votes: VecDeque::default(),
} }
} }
pub fn collect_vote_lockouts<F>( pub fn collect_vote_lockouts<F>(
@ -113,8 +118,11 @@ impl Locktower {
if lamports == 0 { if lamports == 0 {
continue; continue;
} }
let mut vote_state: VoteState = VoteState::deserialize(&account.data) let vote_state = VoteState::from(&account);
.expect("bank should always have valid VoteState data"); if vote_state.is_none() {
continue;
}
let mut vote_state = vote_state.unwrap();
if key == self.epoch_stakes.delegate_id if key == self.epoch_stakes.delegate_id
|| vote_state.node_id == 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; 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 { for vote in &vote_state.votes {
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
} }
@ -220,9 +230,23 @@ impl Locktower {
} }
} }
pub fn record_vote(&mut self, slot: u64) -> Option<u64> { pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
let root_slot = self.lockouts.root_slot; 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::<Vec<_>>();
self.recent_votes
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
datapoint_info!( datapoint_info!(
"locktower-vote", "locktower-vote",
("latest", slot, i64), ("latest", slot, i64),
@ -236,10 +260,7 @@ impl Locktower {
} }
pub fn recent_votes(&self) -> Vec<Vote> { pub fn recent_votes(&self) -> Vec<Vote> {
let start = self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES); self.recent_votes.iter().cloned().collect::<Vec<_>>()
(start..self.lockouts.votes.len())
.map(|i| Vote::new(self.lockouts.votes[i].slot))
.collect()
} }
pub fn root(&self) -> Option<u64> { pub fn root(&self) -> Option<u64> {
@ -269,7 +290,7 @@ impl Locktower {
pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool { pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool {
let mut lockouts = self.lockouts.clone(); let mut lockouts = self.lockouts.clone();
lockouts.process_vote(&Vote { slot }); lockouts.process_slot_vote_unchecked(slot);
for vote in &lockouts.votes { for vote in &lockouts.votes {
if vote.slot == slot { if vote.slot == slot {
continue; continue;
@ -291,7 +312,7 @@ impl Locktower {
stake_lockouts: &HashMap<u64, StakeLockout>, stake_lockouts: &HashMap<u64, StakeLockout>,
) -> bool { ) -> bool {
let mut lockouts = self.lockouts.clone(); 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); let vote = lockouts.nth_recent_vote(self.threshold_depth);
if let Some(vote) = vote { if let Some(vote) = vote {
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) { if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
@ -386,7 +407,7 @@ mod test {
account.lamports = *lamports; account.lamports = *lamports;
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for slot in *votes { for slot in *votes {
vote_state.process_vote(&Vote { slot: *slot }); vote_state.process_slot_vote_unchecked(*slot);
} }
vote_state vote_state
.serialize(&mut account.data) .serialize(&mut account.data)
@ -431,7 +452,7 @@ mod test {
let mut locktower = Locktower::new(epoch_stakes, 0, 0.67); let mut locktower = Locktower::new(epoch_stakes, 0, 0.67);
let mut ancestors = HashMap::new(); let mut ancestors = HashMap::new();
for i in 0..(MAX_LOCKOUT_HISTORY + 1) { 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()); ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
} }
assert_eq!(locktower.lockouts.root_slot, Some(0)); assert_eq!(locktower.lockouts.root_slot, Some(0));
@ -569,7 +590,7 @@ mod test {
#[test] #[test]
fn test_check_already_voted() { fn test_check_already_voted() {
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67); 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(0));
assert!(!locktower.has_voted(1)); assert!(!locktower.has_voted(1));
} }
@ -580,8 +601,8 @@ mod test {
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())] let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
locktower.record_vote(1); locktower.record_vote(1, Hash::default());
assert!(locktower.is_locked_out(0, &descendants)); assert!(locktower.is_locked_out(0, &descendants));
} }
@ -591,7 +612,7 @@ mod test {
let descendants = vec![(0, vec![1].into_iter().collect())] let descendants = vec![(0, vec![1].into_iter().collect())]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
assert!(!locktower.is_locked_out(1, &descendants)); assert!(!locktower.is_locked_out(1, &descendants));
} }
@ -605,8 +626,8 @@ mod test {
] ]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
locktower.record_vote(1); locktower.record_vote(1, Hash::default());
assert!(locktower.is_locked_out(2, &descendants)); 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())] let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
locktower.record_vote(1); locktower.record_vote(1, Hash::default());
assert!(!locktower.is_locked_out(4, &descendants)); 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].slot, 0);
assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2); assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2);
assert_eq!(locktower.lockouts.votes[1].slot, 4); assert_eq!(locktower.lockouts.votes[1].slot, 4);
@ -638,7 +659,7 @@ mod test {
)] )]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
assert!(!locktower.check_vote_stake_threshold(1, &stakes)); assert!(!locktower.check_vote_stake_threshold(1, &stakes));
} }
#[test] #[test]
@ -653,7 +674,7 @@ mod test {
)] )]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
assert!(locktower.check_vote_stake_threshold(1, &stakes)); assert!(locktower.check_vote_stake_threshold(1, &stakes));
} }
@ -669,9 +690,9 @@ mod test {
)] )]
.into_iter() .into_iter()
.collect(); .collect();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
locktower.record_vote(1); locktower.record_vote(1, Hash::default());
locktower.record_vote(2); locktower.record_vote(2, Hash::default());
assert!(locktower.check_vote_stake_threshold(6, &stakes)); assert!(locktower.check_vote_stake_threshold(6, &stakes));
} }
@ -679,7 +700,7 @@ mod test {
fn test_check_vote_threshold_above_threshold_no_stake() { fn test_check_vote_threshold_above_threshold_no_stake() {
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = HashMap::new(); let stakes = HashMap::new();
locktower.record_vote(0); locktower.record_vote(0, Hash::default());
assert!(!locktower.check_vote_stake_threshold(1, &stakes)); assert!(!locktower.check_vote_stake_threshold(1, &stakes));
} }
@ -770,7 +791,7 @@ mod test {
// threshold check // threshold check
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64; let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
for vote in &locktower_votes { for vote in &locktower_votes {
locktower.record_vote(*vote); locktower.record_vote(*vote, Hash::default());
} }
let stakes_lockouts = locktower.collect_vote_lockouts( let stakes_lockouts = locktower.collect_vote_lockouts(
vote_to_evaluate, vote_to_evaluate,
@ -791,9 +812,11 @@ mod test {
fn vote_and_check_recent(num_votes: usize) { fn vote_and_check_recent(num_votes: usize) {
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67); let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let start = num_votes.saturating_sub(MAX_RECENT_VOTES); 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 { 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()) assert_eq!(expected, locktower.recent_votes())
} }

View File

@ -302,7 +302,7 @@ impl ReplayStage {
where where
T: 'static + KeypairUtil + Send + Sync, 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 // get the root bank before squash
let root_bank = bank_forks let root_bank = bank_forks
.read() .read()

View File

@ -206,14 +206,14 @@ mod tests {
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_vote_api::vote_state::{self, Vote}; use solana_vote_api::vote_state;
#[test] #[test]
fn test_stake_delegate_stake() { fn test_stake_delegate_stake() {
let vote_keypair = Keypair::new(); let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..1000 { 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(); let vote_pubkey = vote_keypair.pubkey();
@ -272,7 +272,7 @@ mod tests {
// put a credit in the vote_state // put a credit in the vote_state
while vote_state.credits() == 0 { while vote_state.credits() == 0 {
vote_state.process_vote(&Vote::new(vote_i)); vote_state.process_slot_vote_unchecked(vote_i);
vote_i += 1; vote_i += 1;
} }
// this guy can't collect now, not enough stake to get paid on 1 credit // 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 // put more credit in the vote_state
while vote_state.credits() < 10 { 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_i += 1;
} }
vote_state.commission = 0; vote_state.commission = 0;
@ -318,7 +318,7 @@ mod tests {
let vote_keypair = Keypair::new(); let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..1000 { 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(); let vote_pubkey = vote_keypair.pubkey();
@ -364,7 +364,7 @@ mod tests {
); );
// move the vote account forward // 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(); vote_keyed_account.set_state(&vote_state).unwrap();
// now, no lamports in the pool! // now, no lamports in the pool!
@ -392,7 +392,7 @@ mod tests {
let vote_keypair = Keypair::new(); let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..1000 { 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(); let vote_pubkey = vote_keypair.pubkey();
@ -421,7 +421,7 @@ mod tests {
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..100 { for i in 0..100 {
// go back in time, previous state had 1000 votes // 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(); vote_keyed_account.set_state(&vote_state).unwrap();
// voter credits lower than stake_delegate credits... TODO: is this an error? // voter credits lower than stake_delegate credits... TODO: is this an error?

View File

@ -127,7 +127,29 @@ pub fn process_instruction(
} }
VoteInstruction::Vote(votes) => { VoteInstruction::Vote(votes) => {
datapoint_warn!("vote-native", ("count", 1, i64)); 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::<Vec<_>>();
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,
)
} }
} }
} }

View File

@ -2,11 +2,14 @@
//! Receive and processes votes from validators //! Receive and processes votes from validators
use crate::id; use crate::id;
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
use log::*;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use solana_sdk::account::{Account, KeyedAccount}; use solana_sdk::account::{Account, KeyedAccount};
use solana_sdk::account_utils::State; use solana_sdk::account_utils::State;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::InstructionError; use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::syscall::slot_hashes;
use std::collections::VecDeque; use std::collections::VecDeque;
// Maximum number of votes to keep around // 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)] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote { pub struct Vote {
// TODO: add signature of the state here as well
/// A vote for height slot /// A vote for height slot
pub slot: u64, pub slot: u64,
// signature of the bank's state at given slot
pub hash: Hash,
} }
impl Vote { impl Vote {
pub fn new(slot: u64) -> Self { pub fn new(slot: u64, hash: Hash) -> Self {
Self { slot } Self { slot, hash }
} }
} }
@ -122,11 +126,11 @@ impl VoteState {
} }
} }
pub fn process_votes(&mut self, votes: &[Vote]) { pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(u64, Hash)]) {
votes.iter().for_each(|v| self.process_vote(v));; 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 // Ignore votes for slots earlier than we already have votes for
if self if self
.votes .votes
@ -136,6 +140,18 @@ impl VoteState {
return; 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); let vote = Lockout::new(&vote);
// TODO: Integrity checks // TODO: Integrity checks
@ -152,6 +168,13 @@ impl VoteState {
self.double_lockouts(); 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> { pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
if position < self.votes.len() { if position < self.votes.len() {
let pos = self.votes.len() - 1 - position; let pos = self.votes.len() - 1 - position;
@ -235,6 +258,7 @@ pub fn initialize_account(
pub fn process_votes( pub fn process_votes(
vote_account: &mut KeyedAccount, vote_account: &mut KeyedAccount,
slot_hashes_account: &mut KeyedAccount,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
votes: &[Vote], votes: &[Vote],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
@ -244,6 +268,12 @@ pub fn process_votes(
return Err(InstructionError::UninitializedAccount); 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); let authorized = Some(&vote_state.authorized_voter_id);
// find a signer that matches the authorized_voter_id // find a signer that matches the authorized_voter_id
if vote_account.signer_key() != authorized if vote_account.signer_key() != authorized
@ -254,7 +284,7 @@ pub fn process_votes(
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
vote_state.process_votes(&votes); vote_state.process_votes(&votes, &slot_hashes);
vote_account.set_state(&vote_state) 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 // will be forced to select it as the leader for height 0
let mut vote_account = create_account(&vote_id, &node_id, commission, lamports); 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) (vote_account, vote_state)
} }
// utility function, used by Bank, tests
pub fn vote(
vote_id: &Pubkey,
vote_account: &mut Account,
vote: &Vote,
) -> Result<VoteState, InstructionError> {
process_votes(
&mut KeyedAccount::new(vote_id, true, vote_account),
&[],
&[vote.clone()],
)?;
vote_account.state()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::vote_state; 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; 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<VoteState, InstructionError> {
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<VoteState, InstructionError> {
simulate_process_vote(vote_id, vote_account, vote, &[(vote.slot, vote.hash)])
}
#[test] #[test]
fn test_vote_create_bootstrap_leader_account() { fn test_vote_create_bootstrap_leader_account() {
let vote_id = Pubkey::new_rand(); 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); 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.len(), 1);
assert_eq!(vote_state.votes[0], Lockout::new(&Vote::new(0))); assert_eq!(vote_state.votes[0], Lockout::new(&Vote::default()));
} }
#[test] #[test]
@ -374,21 +437,62 @@ mod tests {
fn test_vote() { fn test_vote() {
let (vote_id, mut vote_account) = create_test_account(); let (vote_id, mut vote_account) = create_test_account();
let vote = Vote::new(1); let vote = Vote::new(1, Hash::default());
let vote_state = vote_state::vote(&vote_id, &mut vote_account, &vote).unwrap(); 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.votes, vec![Lockout::new(&vote)]);
assert_eq!(vote_state.credits(), 0); 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] #[test]
fn test_vote_signature() { fn test_vote_signature() {
let (vote_id, mut vote_account) = create_test_account(); 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 // unsigned
let res = process_votes( let res = process_votes(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account), &mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
&[], &[],
&vote, &vote,
); );
@ -397,6 +501,7 @@ mod tests {
// unsigned // unsigned
let res = process_votes( let res = process_votes(
&mut KeyedAccount::new(&vote_id, true, &mut vote_account), &mut KeyedAccount::new(&vote_id, true, &mut vote_account),
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
&[], &[],
&vote, &vote,
); );
@ -430,18 +535,22 @@ mod tests {
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
// not signed by authorized voter // 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( let res = process_votes(
&mut KeyedAccount::new(&vote_id, true, &mut vote_account), &mut KeyedAccount::new(&vote_id, true, &mut vote_account),
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
&[], &[],
&vote, &vote,
); );
assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
// signed by authorized voter // signed by authorized voter
let vote = vec![Vote::new(2)]; let vote = vec![Vote::new(2, Hash::default())];
let res = process_votes( let res = process_votes(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account), &mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
&[KeyedAccount::new( &[KeyedAccount::new(
&authorized_voter_id, &authorized_voter_id,
true, true,
@ -457,7 +566,11 @@ mod tests {
let vote_id = Pubkey::new_rand(); let vote_id = Pubkey::new_rand();
let mut vote_account = Account::new(100, VoteState::size_of(), &id()); 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)); assert_eq!(res, Err(InstructionError::UninitializedAccount));
} }
@ -468,7 +581,7 @@ mod tests {
let mut vote_state: VoteState = vote_account.state().unwrap(); let mut vote_state: VoteState = vote_account.state().unwrap();
for i in 0..(MAX_LOCKOUT_HISTORY + 1) { 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 // 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 // the root_slot should change to the
// second vote // second vote
let top_vote = vote_state.votes.front().unwrap().slot; let top_vote = vote_state.votes.front().unwrap().slot;
vote_state.process_vote(&Vote::new( vote_state.process_slot_vote_unchecked(vote_state.votes.back().unwrap().expiration_slot());
vote_state.votes.back().unwrap().expiration_slot(),
));
assert_eq!(Some(top_vote), vote_state.root_slot); assert_eq!(Some(top_vote), vote_state.root_slot);
// Expire everything except the first vote // Expire everything except the first vote
let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot()); vote_state.process_slot_vote_unchecked(vote_state.votes.front().unwrap().expiration_slot());
vote_state.process_vote(&vote);
// First vote and new vote are both stored for a total of 2 votes // First vote and new vote are both stored for a total of 2 votes
assert_eq!(vote_state.votes.len(), 2); 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); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..3 { for i in 0..3 {
let vote = Vote::new(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
vote_state.process_vote(&vote);
} }
check_lockouts(&vote_state); 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 // 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 // vote stack is unchanged, so none of the previous votes should have
// doubled in lockout // 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); check_lockouts(&vote_state);
// Vote again, this time the vote stack depth increases, so the lockouts should // Vote again, this time the vote stack depth increases, so the lockouts should
// double for everybody // 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); check_lockouts(&vote_state);
// Vote again, this time the vote stack depth increases, so the lockouts should // Vote again, this time the vote stack depth increases, so the lockouts should
// double for everybody // 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); check_lockouts(&vote_state);
} }
@ -527,15 +636,14 @@ mod tests {
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..3 { for i in 0..3 {
let vote = Vote::new(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
vote_state.process_vote(&vote);
} }
assert_eq!(vote_state.votes[0].confirmation_count, 3); assert_eq!(vote_state.votes[0].confirmation_count, 3);
// Expire the second and third votes // Expire the second and third votes
let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1; 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); assert_eq!(vote_state.votes.len(), 2);
// Check that the old votes expired // Check that the old votes expired
@ -543,7 +651,7 @@ mod tests {
assert_eq!(vote_state.votes[1].slot, expire_slot); assert_eq!(vote_state.votes[1].slot, expire_slot);
// Process one more vote // 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 // Confirmation count for the older first vote should remain unchanged
assert_eq!(vote_state.votes[0].confirmation_count, 3); 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); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..MAX_LOCKOUT_HISTORY { 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); 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); 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); 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); assert_eq!(vote_state.credits(), 3);
} }
@ -576,9 +684,9 @@ mod tests {
fn test_duplicate_vote() { fn test_duplicate_vote() {
let voter_id = Pubkey::new_rand(); let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
vote_state.process_vote(&Vote::new(0)); vote_state.process_slot_vote_unchecked(0);
vote_state.process_vote(&Vote::new(1)); vote_state.process_slot_vote_unchecked(1);
vote_state.process_vote(&Vote::new(0)); 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(0).unwrap().slot, 1);
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0); assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
assert!(vote_state.nth_recent_vote(2).is_none()); assert!(vote_state.nth_recent_vote(2).is_none());
@ -589,7 +697,7 @@ mod tests {
let voter_id = Pubkey::new_rand(); let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..MAX_LOCKOUT_HISTORY { 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) { for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
assert_eq!( assert_eq!(
@ -613,7 +721,7 @@ mod tests {
fn recent_votes(vote_state: &VoteState) -> Vec<Vote> { fn recent_votes(vote_state: &VoteState) -> Vec<Vote> {
let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES); let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
(start..vote_state.votes.len()) (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() .collect()
} }
@ -626,17 +734,20 @@ mod tests {
let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0); let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0);
// process some votes on account a // process some votes on account a
let votes_a: Vec<_> = (0..5).into_iter().map(|i| Vote::new(i)).collect(); (0..5)
vote_state_a.process_votes(&votes_a); .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)); 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 // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
let votes: Vec<_> = (0..MAX_RECENT_VOTES) let votes: Vec<_> = (0..MAX_RECENT_VOTES)
.into_iter() .into_iter()
.map(|i| Vote::new(i as u64)) .map(|i| Vote::new(i as u64, Hash::default()))
.collect(); .collect();
vote_state_a.process_votes(&votes); let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect();
vote_state_b.process_votes(&votes);
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)); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
} }

View File

@ -1,2 +1,5 @@
/// Maximum over-the-wire size of a Transaction /// 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;

View File

@ -2,7 +2,7 @@
//! //!
use crate::pubkey::Pubkey; use crate::pubkey::Pubkey;
pub mod slothashes; pub mod slot_hashes;
/// "Sysca11111111111111111111111111111111111111" /// "Sysca11111111111111111111111111111111111111"
/// owner pubkey for syscall accounts /// owner pubkey for syscall accounts

View File

@ -17,6 +17,10 @@ pub fn id() -> Pubkey {
Pubkey::new(&SYSCALL_SLOT_HASHES_ID) 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 crate::account_utils::State;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]