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(())
}
}
#[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_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<Vote>,
}
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<F>(
@ -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<u64> {
pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
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!(
"locktower-vote",
("latest", slot, i64),
@ -236,10 +260,7 @@ impl Locktower {
}
pub fn recent_votes(&self) -> Vec<Vote> {
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::<Vec<_>>()
}
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 {
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<u64, StakeLockout>,
) -> 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())
}

View File

@ -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()

View File

@ -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?

View File

@ -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::<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
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<VoteState, InstructionError> {
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<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]
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<Vote> {
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));
}

View File

@ -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;

View File

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

View File

@ -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)]