Add checks to vote state updates to handle updates outside of SlotHash history (#22358)
This commit is contained in:
@ -10,6 +10,7 @@ use {
|
|||||||
feature_set::FeatureSet,
|
feature_set::FeatureSet,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
|
keyed_account::KeyedAccount,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
slot_hashes::{SlotHashes, MAX_ENTRIES},
|
slot_hashes::{SlotHashes, MAX_ENTRIES},
|
||||||
sysvar,
|
sysvar,
|
||||||
@ -17,38 +18,35 @@ use {
|
|||||||
},
|
},
|
||||||
solana_vote_program::{
|
solana_vote_program::{
|
||||||
vote_instruction::VoteInstruction,
|
vote_instruction::VoteInstruction,
|
||||||
vote_state::{Vote, VoteInit, VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY},
|
vote_state::{
|
||||||
|
self, Vote, VoteInit, VoteState, VoteStateUpdate, VoteStateVersions,
|
||||||
|
MAX_LOCKOUT_HISTORY,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::sync::Arc,
|
std::{cell::RefCell, collections::HashSet, sync::Arc},
|
||||||
test::Bencher,
|
test::Bencher,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `feature` can be used to change vote program behavior per bench run.
|
struct VoteComponents {
|
||||||
fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
slot_hashes: SlotHashes,
|
||||||
// vote accounts are usually almost full of votes in normal operation
|
clock: Clock,
|
||||||
let num_initial_votes = MAX_LOCKOUT_HISTORY;
|
signers: HashSet<Pubkey>,
|
||||||
let num_vote_slots: usize = 4;
|
authority_pubkey: Pubkey,
|
||||||
let last_vote_slot = num_initial_votes
|
vote_pubkey: Pubkey,
|
||||||
.saturating_add(num_vote_slots)
|
vote_account: Account,
|
||||||
.saturating_sub(1);
|
}
|
||||||
let last_vote_hash = Hash::new_unique();
|
|
||||||
|
|
||||||
|
fn create_components(num_initial_votes: Slot) -> VoteComponents {
|
||||||
let clock = Clock::default();
|
let clock = Clock::default();
|
||||||
let mut slot_hashes = SlotHashes::new(&[]);
|
let mut slot_hashes = SlotHashes::new(&[]);
|
||||||
for i in 0..MAX_ENTRIES {
|
for i in 0..MAX_ENTRIES {
|
||||||
// slot hashes is full in normal operation
|
// slot hashes is full in normal operation
|
||||||
slot_hashes.add(
|
slot_hashes.add(i as Slot, Hash::new_unique());
|
||||||
i as Slot,
|
|
||||||
if i == last_vote_slot {
|
|
||||||
last_vote_hash
|
|
||||||
} else {
|
|
||||||
Hash::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let vote_pubkey = Pubkey::new_unique();
|
let vote_pubkey = Pubkey::new_unique();
|
||||||
let authority_pubkey = Pubkey::new_unique();
|
let authority_pubkey = Pubkey::new_unique();
|
||||||
|
let signers: HashSet<Pubkey> = vec![authority_pubkey].into_iter().collect();
|
||||||
let vote_account = {
|
let vote_account = {
|
||||||
let mut vote_state = VoteState::new(
|
let mut vote_state = VoteState::new(
|
||||||
&VoteInit {
|
&VoteInit {
|
||||||
@ -60,12 +58,11 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
|||||||
&clock,
|
&clock,
|
||||||
);
|
);
|
||||||
|
|
||||||
for next_vote_slot in 0..num_initial_votes as u64 {
|
for next_vote_slot in 0..num_initial_votes {
|
||||||
vote_state.process_next_vote_slot(next_vote_slot, 0);
|
vote_state.process_next_vote_slot(next_vote_slot, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||||
let versioned = VoteStateVersions::new_current(vote_state);
|
let versioned = VoteStateVersions::new_current(vote_state.clone());
|
||||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||||
|
|
||||||
Account {
|
Account {
|
||||||
@ -76,22 +73,53 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
|||||||
rent_epoch: 0,
|
rent_epoch: 0,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
VoteComponents {
|
||||||
|
slot_hashes,
|
||||||
|
clock,
|
||||||
|
signers,
|
||||||
|
authority_pubkey,
|
||||||
|
vote_pubkey,
|
||||||
|
vote_account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `feature` can be used to change vote program behavior per bench run.
|
||||||
|
fn do_bench_process_vote_instruction(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
||||||
|
// vote accounts are usually almost full of votes in normal operation
|
||||||
|
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;
|
||||||
|
|
||||||
|
let VoteComponents {
|
||||||
|
slot_hashes,
|
||||||
|
clock,
|
||||||
|
authority_pubkey,
|
||||||
|
vote_pubkey,
|
||||||
|
vote_account,
|
||||||
|
..
|
||||||
|
} = create_components(num_initial_votes);
|
||||||
|
|
||||||
let slot_hashes_account = create_account_for_test(&slot_hashes);
|
let slot_hashes_account = create_account_for_test(&slot_hashes);
|
||||||
let clock_account = create_account_for_test(&clock);
|
let clock_account = create_account_for_test(&clock);
|
||||||
let authority_account = Account::default();
|
let authority_account = Account::default();
|
||||||
|
|
||||||
let mut sysvar_cache = SysvarCache::default();
|
|
||||||
sysvar_cache.set_clock(clock);
|
|
||||||
sysvar_cache.set_slot_hashes(slot_hashes);
|
|
||||||
|
|
||||||
let mut feature_set = FeatureSet::all_enabled();
|
let mut feature_set = FeatureSet::all_enabled();
|
||||||
if let Some(feature) = feature {
|
if let Some(feature) = feature {
|
||||||
feature_set.activate(&feature, 0);
|
feature_set.activate(&feature, 0);
|
||||||
}
|
}
|
||||||
let feature_set = Arc::new(feature_set);
|
let feature_set = Arc::new(feature_set);
|
||||||
|
|
||||||
|
let num_vote_slots = 4;
|
||||||
|
let last_vote_slot = num_initial_votes
|
||||||
|
.saturating_add(num_vote_slots)
|
||||||
|
.saturating_sub(1);
|
||||||
|
let last_vote_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == last_vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
|
||||||
let vote_ix_data = bincode::serialize(&VoteInstruction::Vote(Vote::new(
|
let vote_ix_data = bincode::serialize(&VoteInstruction::Vote(Vote::new(
|
||||||
(num_initial_votes as u64..).take(num_vote_slots).collect(),
|
(num_initial_votes..=last_vote_slot).collect(),
|
||||||
last_vote_hash,
|
last_vote_hash,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -120,6 +148,10 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut sysvar_cache = SysvarCache::default();
|
||||||
|
sysvar_cache.set_clock(clock);
|
||||||
|
sysvar_cache.set_slot_hashes(slot_hashes);
|
||||||
|
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
let mut transaction_context = TransactionContext::new(
|
let mut transaction_context = TransactionContext::new(
|
||||||
vec![
|
vec![
|
||||||
@ -153,18 +185,127 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let first_instruction_account = 1;
|
let first_instruction_account = 1;
|
||||||
assert_eq!(
|
assert!(solana_vote_program::vote_processor::process_instruction(
|
||||||
solana_vote_program::vote_processor::process_instruction(
|
first_instruction_account,
|
||||||
first_instruction_account,
|
&instruction.data,
|
||||||
&instruction.data,
|
&mut invoke_context
|
||||||
&mut invoke_context
|
)
|
||||||
),
|
.is_ok());
|
||||||
Ok(())
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
|
/// `feature` can be used to change vote program behavior per bench run.
|
||||||
|
fn do_bench_process_vote(bencher: &mut Bencher, feature: Option<Pubkey>) {
|
||||||
|
// vote accounts are usually almost full of votes in normal operation
|
||||||
|
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;
|
||||||
|
|
||||||
|
let VoteComponents {
|
||||||
|
slot_hashes,
|
||||||
|
clock,
|
||||||
|
signers,
|
||||||
|
vote_pubkey,
|
||||||
|
vote_account,
|
||||||
|
..
|
||||||
|
} = create_components(num_initial_votes);
|
||||||
|
|
||||||
|
let num_vote_slots = 4;
|
||||||
|
let last_vote_slot = num_initial_votes
|
||||||
|
.saturating_add(num_vote_slots)
|
||||||
|
.saturating_sub(1);
|
||||||
|
let last_vote_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == last_vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let vote = Vote::new(
|
||||||
|
(num_initial_votes..=last_vote_slot).collect(),
|
||||||
|
last_vote_hash,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut feature_set = FeatureSet::all_enabled();
|
||||||
|
if let Some(feature) = feature {
|
||||||
|
feature_set.activate(&feature, 0);
|
||||||
|
}
|
||||||
|
let feature_set = Arc::new(feature_set);
|
||||||
|
|
||||||
|
bencher.iter(|| {
|
||||||
|
let vote_account = RefCell::new(AccountSharedData::from(vote_account.clone()));
|
||||||
|
let keyed_account = KeyedAccount::new(&vote_pubkey, true, &vote_account);
|
||||||
|
assert!(vote_state::process_vote(
|
||||||
|
&keyed_account,
|
||||||
|
&slot_hashes,
|
||||||
|
&clock,
|
||||||
|
&vote,
|
||||||
|
&signers,
|
||||||
|
&feature_set,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_bench_process_vote_state_update(bencher: &mut Bencher) {
|
||||||
|
// vote accounts are usually almost full of votes in normal operation
|
||||||
|
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;
|
||||||
|
|
||||||
|
let VoteComponents {
|
||||||
|
slot_hashes,
|
||||||
|
clock,
|
||||||
|
signers,
|
||||||
|
vote_pubkey,
|
||||||
|
vote_account,
|
||||||
|
..
|
||||||
|
} = create_components(num_initial_votes);
|
||||||
|
|
||||||
|
let num_vote_slots = MAX_LOCKOUT_HISTORY as Slot;
|
||||||
|
let last_vote_slot = num_initial_votes
|
||||||
|
.saturating_add(num_vote_slots)
|
||||||
|
.saturating_sub(1);
|
||||||
|
let last_vote_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == last_vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let slots_and_lockouts: Vec<(Slot, u32)> =
|
||||||
|
((num_initial_votes.saturating_add(1)..=last_vote_slot).zip((1u32..=31).rev())).collect();
|
||||||
|
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(slots_and_lockouts);
|
||||||
|
vote_state_update.root = Some(num_initial_votes);
|
||||||
|
|
||||||
|
vote_state_update.hash = last_vote_hash;
|
||||||
|
|
||||||
|
bencher.iter(|| {
|
||||||
|
let vote_account = RefCell::new(AccountSharedData::from(vote_account.clone()));
|
||||||
|
let keyed_account = KeyedAccount::new(&vote_pubkey, true, &vote_account);
|
||||||
|
let vote_state_update = vote_state_update.clone();
|
||||||
|
assert!(vote_state::process_vote_state_update(
|
||||||
|
&keyed_account,
|
||||||
|
&slot_hashes,
|
||||||
|
&clock,
|
||||||
|
vote_state_update,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
#[ignore]
|
||||||
fn bench_process_vote_instruction(bencher: &mut Bencher) {
|
fn bench_process_vote_instruction(bencher: &mut Bencher) {
|
||||||
do_bench(bencher, None);
|
do_bench_process_vote_instruction(bencher, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benches a specific type of vote instruction
|
||||||
|
#[bench]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_process_vote(bencher: &mut Bencher) {
|
||||||
|
do_bench_process_vote(bencher, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benches a specific type of vote instruction
|
||||||
|
#[bench]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_process_vote_state_update(bencher: &mut Bencher) {
|
||||||
|
do_bench_process_vote_state_update(bencher);
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,24 @@ pub struct VoteStateUpdate {
|
|||||||
pub timestamp: Option<UnixTimestamp>,
|
pub timestamp: Option<UnixTimestamp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Vec<(Slot, u32)>> for VoteStateUpdate {
|
||||||
|
fn from(recent_slots: Vec<(Slot, u32)>) -> Self {
|
||||||
|
let lockouts: VecDeque<Lockout> = recent_slots
|
||||||
|
.into_iter()
|
||||||
|
.map(|(slot, confirmation_count)| Lockout {
|
||||||
|
slot,
|
||||||
|
confirmation_count,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
lockouts,
|
||||||
|
root: None,
|
||||||
|
hash: Hash::default(),
|
||||||
|
timestamp: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl VoteStateUpdate {
|
impl VoteStateUpdate {
|
||||||
pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
|
pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -301,6 +319,13 @@ impl VoteState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns if the vote state contains a slot `candidate_slot`
|
||||||
|
pub fn contains_slot(&self, candidate_slot: Slot) -> bool {
|
||||||
|
self.votes
|
||||||
|
.binary_search_by(|lockout| lockout.slot.cmp(&candidate_slot))
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_max_sized_vote_state() -> VoteState {
|
fn get_max_sized_vote_state() -> VoteState {
|
||||||
let mut authorized_voters = AuthorizedVoters::default();
|
let mut authorized_voters = AuthorizedVoters::default();
|
||||||
for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
|
for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
|
||||||
@ -316,14 +341,198 @@ impl VoteState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_update_vote_state_slots_are_valid(
|
||||||
|
&self,
|
||||||
|
vote_state_update: &mut VoteStateUpdate,
|
||||||
|
slot_hashes: &[(Slot, Hash)],
|
||||||
|
) -> Result<(), VoteError> {
|
||||||
|
if vote_state_update.lockouts.is_empty() {
|
||||||
|
return Err(VoteError::EmptySlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the vote state update is not new enough, return
|
||||||
|
if let Some(last_vote_slot) = self.votes.back().map(|lockout| lockout.slot) {
|
||||||
|
if vote_state_update.lockouts.back().unwrap().slot <= last_vote_slot {
|
||||||
|
return Err(VoteError::VoteTooOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_vote_state_update_slot = vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.back()
|
||||||
|
.expect("must be nonempty, checked above")
|
||||||
|
.slot;
|
||||||
|
|
||||||
|
if slot_hashes.is_empty() {
|
||||||
|
return Err(VoteError::SlotsMismatch);
|
||||||
|
}
|
||||||
|
let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
|
||||||
|
|
||||||
|
// Check if the proposed vote is too old to be in the SlotHash history
|
||||||
|
if last_vote_state_update_slot < earliest_slot_hash_in_history {
|
||||||
|
// If this is the last slot in the vote update, it must be in SlotHashes,
|
||||||
|
// otherwise we have no way of confirming if the hash matches
|
||||||
|
return Err(VoteError::VoteTooOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the proposed root is too old
|
||||||
|
if let Some(new_proposed_root) = vote_state_update.root {
|
||||||
|
// If the root is less than the earliest slot hash in the history such that we
|
||||||
|
// cannot verify whether the slot was actually was on this fork, set the root
|
||||||
|
// to the current vote state root for safety.
|
||||||
|
if earliest_slot_hash_in_history > new_proposed_root {
|
||||||
|
vote_state_update.root = self.root_slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// index into the new proposed vote state's slots, starting at the oldest
|
||||||
|
// slot
|
||||||
|
let mut vote_state_update_index = 0;
|
||||||
|
|
||||||
|
// index into the slot_hashes, starting at the oldest known
|
||||||
|
// slot hash
|
||||||
|
let mut slot_hashes_index = slot_hashes.len();
|
||||||
|
|
||||||
|
let mut vote_state_update_indexes_to_filter = vec![];
|
||||||
|
|
||||||
|
// Note:
|
||||||
|
//
|
||||||
|
// 1) `vote_state_update.lockouts` is sorted from oldest/smallest vote to newest/largest
|
||||||
|
// vote, due to the way votes are applied to the vote state (newest votes
|
||||||
|
// pushed to the back).
|
||||||
|
//
|
||||||
|
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
|
||||||
|
// the oldest/smallest vote
|
||||||
|
//
|
||||||
|
// Unlike for vote updates, vote state updates here can't only check votes older than the last vote
|
||||||
|
// because have to ensure that every slot is actually part of the history, not just the most
|
||||||
|
// recent ones
|
||||||
|
while vote_state_update_index < vote_state_update.lockouts.len() && slot_hashes_index > 0 {
|
||||||
|
let proposed_vote_slot = vote_state_update.lockouts[vote_state_update_index].slot;
|
||||||
|
if vote_state_update_index > 0
|
||||||
|
&& proposed_vote_slot
|
||||||
|
<= vote_state_update.lockouts[vote_state_update_index - 1].slot
|
||||||
|
{
|
||||||
|
return Err(VoteError::SlotsNotOrdered);
|
||||||
|
}
|
||||||
|
let ancestor_slot = slot_hashes[slot_hashes_index - 1].0;
|
||||||
|
|
||||||
|
// Find if this slot in the proposed vote state exists in the SlotHashes history
|
||||||
|
// to confirm if it was a valid ancestor on this fork
|
||||||
|
match proposed_vote_slot.cmp(&ancestor_slot) {
|
||||||
|
Ordering::Less => {
|
||||||
|
if slot_hashes_index == slot_hashes.len() {
|
||||||
|
// The vote slot does not exist in the SlotHashes history because it's too old,
|
||||||
|
// i.e. older than the oldest slot in the history.
|
||||||
|
assert!(proposed_vote_slot < earliest_slot_hash_in_history);
|
||||||
|
if !self.contains_slot(proposed_vote_slot) {
|
||||||
|
// If the vote slot is both:
|
||||||
|
// 1) Too old
|
||||||
|
// 2) Doesn't already exist in vote state
|
||||||
|
//
|
||||||
|
// Then filter it out
|
||||||
|
vote_state_update_indexes_to_filter.push(vote_state_update_index);
|
||||||
|
}
|
||||||
|
vote_state_update_index += 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// If the vote slot is new enough to be in the slot history,
|
||||||
|
// but is not part of the slot history, then it must belong to another fork,
|
||||||
|
// which means this vote state update is invalid.
|
||||||
|
return Err(VoteError::SlotsMismatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
// Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
|
||||||
|
slot_hashes_index -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
// Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
|
||||||
|
// in `vote_state_update.lockouts` and continue.
|
||||||
|
vote_state_update_index += 1;
|
||||||
|
slot_hashes_index -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vote_state_update_index != vote_state_update.lockouts.len() {
|
||||||
|
// The last vote slot in the update did not exist in SlotHashes
|
||||||
|
return Err(VoteError::SlotsMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This assertion must be true at this point because we can assume by now:
|
||||||
|
// 1) vote_state_update_index == vote_state_update.lockouts.len()
|
||||||
|
// 2) last_vote_state_update_slot >= earliest_slot_hash_in_history
|
||||||
|
// 3) !vote_state_update.lockouts.is_empty()
|
||||||
|
//
|
||||||
|
// 1) implies that during the last iteration of the loop above,
|
||||||
|
// `vote_state_update_index` was equal to `vote_state_update.lockouts.len() - 1`,
|
||||||
|
// and was then incremented to `vote_state_update.lockouts.len()`.
|
||||||
|
// This means in that last loop iteration,
|
||||||
|
// `proposed_vote_slot ==
|
||||||
|
// vote_state_update.lockouts[vote_state_update.lockouts.len() - 1] ==
|
||||||
|
// last_vote_state_update_slot`.
|
||||||
|
//
|
||||||
|
// Then we know the last comparison `match proposed_vote_slot.cmp(&ancestor_slot)`
|
||||||
|
// is equivalent to `match last_vote_state_update_slot.cmp(&ancestor_slot)`. The result
|
||||||
|
// of this match to increment `vote_state_update_index` must have been either:
|
||||||
|
//
|
||||||
|
// 1) The Equal case ran, in which case then we know this assertion must be true
|
||||||
|
// 2) The Less case ran, and more specifically the case
|
||||||
|
// `proposed_vote_slot < earliest_slot_hash_in_history` ran, which is equivalent to
|
||||||
|
// `last_vote_state_update_slot < earliest_slot_hash_in_history`, but this is impossible
|
||||||
|
// due to assumption 3) above.
|
||||||
|
assert_eq!(
|
||||||
|
last_vote_state_update_slot,
|
||||||
|
slot_hashes[slot_hashes_index].0
|
||||||
|
);
|
||||||
|
|
||||||
|
if slot_hashes[slot_hashes_index].1 != vote_state_update.hash {
|
||||||
|
// This means the newest vote in the slot has a match that
|
||||||
|
// doesn't match the expected hash for that slot on this
|
||||||
|
// fork
|
||||||
|
warn!(
|
||||||
|
"{} dropped vote {:?} failed to match hash {} {}",
|
||||||
|
self.node_pubkey,
|
||||||
|
vote_state_update,
|
||||||
|
vote_state_update.hash,
|
||||||
|
slot_hashes[slot_hashes_index].1
|
||||||
|
);
|
||||||
|
inc_new_counter_info!("dropped-vote-hash", 1);
|
||||||
|
return Err(VoteError::SlotHashMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the irrelevant votes
|
||||||
|
let mut vote_state_update_index = 0;
|
||||||
|
let mut filter_votes_index = 0;
|
||||||
|
vote_state_update.lockouts.retain(|_lockout| {
|
||||||
|
let should_retain = if filter_votes_index == vote_state_update_indexes_to_filter.len() {
|
||||||
|
true
|
||||||
|
} else if vote_state_update_index
|
||||||
|
== vote_state_update_indexes_to_filter[filter_votes_index]
|
||||||
|
{
|
||||||
|
filter_votes_index += 1;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
vote_state_update_index += 1;
|
||||||
|
should_retain
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn check_slots_are_valid(
|
fn check_slots_are_valid(
|
||||||
&self,
|
&self,
|
||||||
vote_slots: &[Slot],
|
vote_slots: &[Slot],
|
||||||
vote_hash: &Hash,
|
vote_hash: &Hash,
|
||||||
slot_hashes: &[(Slot, Hash)],
|
slot_hashes: &[(Slot, Hash)],
|
||||||
) -> Result<(), VoteError> {
|
) -> Result<(), VoteError> {
|
||||||
// index into the vote's slots, sarting at the newest
|
// index into the vote's slots, starting at the oldest
|
||||||
// known slot
|
// slot
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
// index into the slot_hashes, starting at the oldest known
|
// index into the slot_hashes, starting at the oldest known
|
||||||
@ -334,7 +543,7 @@ impl VoteState {
|
|||||||
//
|
//
|
||||||
// 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
|
// 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
|
||||||
// vote, due to the way votes are applied to the vote state (newest votes
|
// vote, due to the way votes are applied to the vote state (newest votes
|
||||||
// pushed to the back), but `slot_hashes` is sorted smallest to largest.
|
// pushed to the back).
|
||||||
//
|
//
|
||||||
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
|
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
|
||||||
// the oldest/smallest vote
|
// the oldest/smallest vote
|
||||||
@ -396,7 +605,7 @@ impl VoteState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
//`Ensure check_slots_are_valid()` runs on the slots in `new_state`
|
//`Ensure check_update_vote_state_slots_are_valid()` runs on the slots in `new_state`
|
||||||
// before `process_new_vote_state()` is called
|
// before `process_new_vote_state()` is called
|
||||||
|
|
||||||
// This function should guarantee the following about `new_state`:
|
// This function should guarantee the following about `new_state`:
|
||||||
@ -445,12 +654,6 @@ impl VoteState {
|
|||||||
return Err(VoteError::TooManyVotes);
|
return Err(VoteError::TooManyVotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check_slots_are_valid()` ensures we don't process any states
|
|
||||||
// that are older than the current state
|
|
||||||
if !self.votes.is_empty() {
|
|
||||||
assert!(new_state.back().unwrap().slot > self.votes.back().unwrap().slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
match (new_root, self.root_slot) {
|
match (new_root, self.root_slot) {
|
||||||
(Some(new_root), Some(current_root)) => {
|
(Some(new_root), Some(current_root)) => {
|
||||||
if new_root < current_root {
|
if new_root < current_root {
|
||||||
@ -1047,22 +1250,11 @@ pub fn process_vote_state_update<S: std::hash::BuildHasher>(
|
|||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
slot_hashes: &[SlotHash],
|
slot_hashes: &[SlotHash],
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
vote_state_update: VoteStateUpdate,
|
mut vote_state_update: VoteStateUpdate,
|
||||||
signers: &HashSet<Pubkey, S>,
|
signers: &HashSet<Pubkey, S>,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
||||||
{
|
vote_state.check_update_vote_state_slots_are_valid(&mut vote_state_update, slot_hashes)?;
|
||||||
let vote = Vote {
|
|
||||||
slots: vote_state_update
|
|
||||||
.lockouts
|
|
||||||
.iter()
|
|
||||||
.map(|lockout| lockout.slot)
|
|
||||||
.collect(),
|
|
||||||
hash: vote_state_update.hash,
|
|
||||||
timestamp: vote_state_update.timestamp,
|
|
||||||
};
|
|
||||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, slot_hashes)?;
|
|
||||||
}
|
|
||||||
vote_state.process_new_vote_state(
|
vote_state.process_new_vote_state(
|
||||||
vote_state_update.lockouts,
|
vote_state_update.lockouts,
|
||||||
vote_state_update.root,
|
vote_state_update.root,
|
||||||
@ -3208,6 +3400,191 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
|
||||||
|
slots
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|x| (*x, Hash::new_unique()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_vote_state(vote_slots: Vec<Slot>, slot_hashes: &[(Slot, Hash)]) -> VoteState {
|
||||||
|
let mut vote_state = VoteState::default();
|
||||||
|
|
||||||
|
if !vote_slots.is_empty() {
|
||||||
|
let vote_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| slot == vote_slots.last().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
vote_state
|
||||||
|
.process_vote(&Vote::new(vote_slots, vote_hash), slot_hashes, 0, None)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
vote_state
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_empty() {
|
||||||
|
let empty_slot_hashes = build_slot_hashes(vec![]);
|
||||||
|
let empty_vote_state = build_vote_state(vec![], &empty_slot_hashes);
|
||||||
|
|
||||||
|
// Test with empty vote state update, should return EmptySlots error
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![]);
|
||||||
|
assert_eq!(
|
||||||
|
empty_vote_state.check_update_vote_state_slots_are_valid(
|
||||||
|
&mut vote_state_update,
|
||||||
|
&empty_slot_hashes
|
||||||
|
),
|
||||||
|
Err(VoteError::EmptySlots),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with non-empty vote state update, should return SlotsMismatch since nothing exists in SlotHashes
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(0, 1)]);
|
||||||
|
assert_eq!(
|
||||||
|
empty_vote_state.check_update_vote_state_slots_are_valid(
|
||||||
|
&mut vote_state_update,
|
||||||
|
&empty_slot_hashes
|
||||||
|
),
|
||||||
|
Err(VoteError::SlotsMismatch),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_too_old() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
|
||||||
|
let latest_vote = 4;
|
||||||
|
let vote_state = build_vote_state(vec![1, 2, 3, latest_vote], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a vote for a slot less than the latest vote in the vote_state,
|
||||||
|
// should return error `VoteTooOld`
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(latest_vote, 1)]);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::VoteTooOld),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with a vote state update where the latest slot `X` in the update is
|
||||||
|
// 1) Less than the earliest slot in slot_hashes history, AND
|
||||||
|
// 2) `X` > latest_vote
|
||||||
|
let earliest_slot_in_history = latest_vote + 2;
|
||||||
|
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(earliest_slot_in_history - 1, 1)]);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::VoteTooOld),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_older_than_history_root() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
|
||||||
|
let mut vote_state = build_vote_state(vec![1, 2, 3, 4], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where the root is less than `earliest_slot_in_history`.
|
||||||
|
// Root slot in the `vote_state_update` should be updated to match the root slot in the
|
||||||
|
// current vote state
|
||||||
|
let earliest_slot_in_history = 5;
|
||||||
|
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 6, 7, 8]);
|
||||||
|
let earliest_slot_in_history_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == earliest_slot_in_history)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(earliest_slot_in_history, 1)]);
|
||||||
|
vote_state_update.hash = earliest_slot_in_history_hash;
|
||||||
|
vote_state_update.root = Some(earliest_slot_in_history - 1);
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
assert!(vote_state.root_slot.is_none());
|
||||||
|
assert_eq!(vote_state_update.root, vote_state.root_slot);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where the root is less than `earliest_slot_in_history`.
|
||||||
|
// Root slot in the `vote_state_update` should be updated to match the root slot in the
|
||||||
|
// current vote state
|
||||||
|
vote_state.root_slot = Some(0);
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(earliest_slot_in_history, 1)]);
|
||||||
|
vote_state_update.hash = earliest_slot_in_history_hash;
|
||||||
|
vote_state_update.root = Some(earliest_slot_in_history - 1);
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(vote_state.root_slot, Some(0));
|
||||||
|
assert_eq!(vote_state_update.root, vote_state.root_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slots_not_ordered() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
|
||||||
|
let vote_state = build_vote_state(vec![1], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where the slots are out of order
|
||||||
|
let vote_slot = 3;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotsNotOrdered),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there are multiples of the same slot
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotsNotOrdered),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_older_than_history_slots_filtered() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
|
||||||
|
let vote_state = build_vote_state(vec![1, 2, 3, 4], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there:
|
||||||
|
// 1) Exists a slot less than `earliest_slot_in_history`
|
||||||
|
// 2) This slot does not exist in the vote state already
|
||||||
|
// This slot should be filtered out
|
||||||
|
let earliest_slot_in_history = 11;
|
||||||
|
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
|
||||||
|
let vote_slot = 12;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let missing_older_than_history_slot = earliest_slot_in_history - 1;
|
||||||
|
let mut vote_state_update =
|
||||||
|
VoteStateUpdate::from(vec![(missing_older_than_history_slot, 2), (vote_slot, 3)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Check the earlier slot was filtered out
|
||||||
|
assert_eq!(
|
||||||
|
vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<Lockout>>(),
|
||||||
|
vec![Lockout {
|
||||||
|
slot: vote_slot,
|
||||||
|
confirmation_count: 3,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_minimum_balance() {
|
fn test_minimum_balance() {
|
||||||
let rent = solana_sdk::rent::Rent::default();
|
let rent = solana_sdk::rent::Rent::default();
|
||||||
@ -3215,4 +3592,287 @@ mod tests {
|
|||||||
// golden, may need updating when vote_state grows
|
// golden, may need updating when vote_state grows
|
||||||
assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
|
assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_older_than_history_slots_not_filtered() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
|
||||||
|
let vote_state = build_vote_state(vec![1, 2, 3, 4], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there:
|
||||||
|
// 1) Exists a slot less than `earliest_slot_in_history`
|
||||||
|
// 2) This slot exists in the vote state already
|
||||||
|
// This slot should *NOT* be filtered out
|
||||||
|
let earliest_slot_in_history = 11;
|
||||||
|
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
|
||||||
|
let vote_slot = 12;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let existing_older_than_history_slot = 4;
|
||||||
|
let mut vote_state_update =
|
||||||
|
VoteStateUpdate::from(vec![(existing_older_than_history_slot, 2), (vote_slot, 3)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
// Check the earlier slot was *NOT* filtered out
|
||||||
|
assert_eq!(vote_state_update.lockouts.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<Lockout>>(),
|
||||||
|
vec![
|
||||||
|
Lockout {
|
||||||
|
slot: existing_older_than_history_slot,
|
||||||
|
confirmation_count: 2,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: vote_slot,
|
||||||
|
confirmation_count: 3,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_older_than_history_slots_filtered_and_not_filtered() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 6]);
|
||||||
|
let vote_state = build_vote_state(vec![1, 2, 3, 6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there exists both a slot:
|
||||||
|
// 1) Less than `earliest_slot_in_history`
|
||||||
|
// 2) This slot exists in the vote state already
|
||||||
|
// which should not be filtered
|
||||||
|
//
|
||||||
|
// AND a slot that
|
||||||
|
//
|
||||||
|
// 1) Less than `earliest_slot_in_history`
|
||||||
|
// 2) This slot does not exist in the vote state already
|
||||||
|
// which should be filtered
|
||||||
|
let earliest_slot_in_history = 11;
|
||||||
|
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
|
||||||
|
let vote_slot = 14;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let missing_older_than_history_slot = 4;
|
||||||
|
let existing_older_than_history_slot = 6;
|
||||||
|
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![
|
||||||
|
(missing_older_than_history_slot, 4),
|
||||||
|
(existing_older_than_history_slot, 3),
|
||||||
|
(12, 2),
|
||||||
|
(vote_slot, 1),
|
||||||
|
]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(vote_state_update.lockouts.len(), 3);
|
||||||
|
assert_eq!(
|
||||||
|
vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<Lockout>>(),
|
||||||
|
vec![
|
||||||
|
Lockout {
|
||||||
|
slot: existing_older_than_history_slot,
|
||||||
|
confirmation_count: 3,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: 12,
|
||||||
|
confirmation_count: 2,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: vote_slot,
|
||||||
|
confirmation_count: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slot_not_on_fork() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
|
||||||
|
let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there:
|
||||||
|
// 1) Exists a slot not in the slot hashes history
|
||||||
|
// 2) The slot is greater than the earliest slot in the history
|
||||||
|
// Thus this slot is not part of the fork and the update should be rejected
|
||||||
|
// with error `SlotsMismatch`
|
||||||
|
let missing_vote_slot = 3;
|
||||||
|
|
||||||
|
// Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
|
||||||
|
// errors
|
||||||
|
let vote_slot = vote_state.votes.back().unwrap().slot + 2;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let mut vote_state_update =
|
||||||
|
VoteStateUpdate::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotsMismatch),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test where some earlier vote slots exist in the history, but others don't
|
||||||
|
let missing_vote_slot = 7;
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![
|
||||||
|
(2, 5),
|
||||||
|
(4, 4),
|
||||||
|
(6, 3),
|
||||||
|
(missing_vote_slot, 2),
|
||||||
|
(vote_slot, 1),
|
||||||
|
]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotsMismatch),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slot_newer_than_slot_history() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
|
||||||
|
let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where there:
|
||||||
|
// 1) The last slot in the update is a slot not in the slot hashes history
|
||||||
|
// 2) The slot is greater than the newest slot in the slot history
|
||||||
|
// Thus this slot is not part of the fork and the update should be rejected
|
||||||
|
// with error `SlotsMismatch`
|
||||||
|
let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
|
||||||
|
let vote_slot_hash = Hash::new_unique();
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(8, 2), (missing_vote_slot, 3)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotsMismatch),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slot_all_slot_hashes_in_update_ok() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
|
||||||
|
let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where every slot in the history is
|
||||||
|
// in the update
|
||||||
|
|
||||||
|
// Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
|
||||||
|
// errors
|
||||||
|
let vote_slot = vote_state.votes.back().unwrap().slot + 2;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let mut vote_state_update =
|
||||||
|
VoteStateUpdate::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Nothing in the update should have been filtered out
|
||||||
|
assert_eq!(
|
||||||
|
vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<Lockout>>(),
|
||||||
|
vec![
|
||||||
|
Lockout {
|
||||||
|
slot: 2,
|
||||||
|
confirmation_count: 4,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: 4,
|
||||||
|
confirmation_count: 3,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: 6,
|
||||||
|
confirmation_count: 2,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: vote_slot,
|
||||||
|
confirmation_count: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slot_some_slot_hashes_in_update_ok() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
|
||||||
|
let vote_state = build_vote_state(vec![6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where every only some slots in the history are
|
||||||
|
// in the update, and others slots in the history are missing.
|
||||||
|
|
||||||
|
// Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
|
||||||
|
// errors
|
||||||
|
let vote_slot = vote_state.votes.back().unwrap().slot + 2;
|
||||||
|
let vote_slot_hash = slot_hashes
|
||||||
|
.iter()
|
||||||
|
.find(|(slot, _hash)| *slot == vote_slot)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
let mut vote_state_update = VoteStateUpdate::from(vec![(4, 2), (vote_slot, 1)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Nothing in the update should have been filtered out
|
||||||
|
assert_eq!(
|
||||||
|
vote_state_update
|
||||||
|
.lockouts
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<Lockout>>(),
|
||||||
|
vec![
|
||||||
|
Lockout {
|
||||||
|
slot: 4,
|
||||||
|
confirmation_count: 2,
|
||||||
|
},
|
||||||
|
Lockout {
|
||||||
|
slot: vote_slot,
|
||||||
|
confirmation_count: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_update_vote_state_slot_hash_mismatch() {
|
||||||
|
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
|
||||||
|
let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);
|
||||||
|
|
||||||
|
// Test with a `vote_state_update` where the hash is mismatched
|
||||||
|
|
||||||
|
// Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
|
||||||
|
// errors
|
||||||
|
let vote_slot = vote_state.votes.back().unwrap().slot + 2;
|
||||||
|
let vote_slot_hash = Hash::new_unique();
|
||||||
|
let mut vote_state_update =
|
||||||
|
VoteStateUpdate::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
|
||||||
|
vote_state_update.hash = vote_slot_hash;
|
||||||
|
assert_eq!(
|
||||||
|
vote_state
|
||||||
|
.check_update_vote_state_slots_are_valid(&mut vote_state_update, &slot_hashes),
|
||||||
|
Err(VoteError::SlotHashMismatch),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user