#[cfg(feature = "dev-context-only-utils")]
pub mod handler;
#[cfg(not(feature = "dev-context-only-utils"))]
pub(crate) mod handler;
pub use solana_vote_interface::state::{vote_state_versions::*, *};
use {
handler::{VoteStateHandle, VoteStateHandler, VoteStateTargetVersion},
log::*,
solana_account::{AccountSharedData, WritableAccount},
solana_clock::{Clock, Epoch, Slot},
solana_epoch_schedule::EpochSchedule,
solana_hash::Hash,
solana_instruction::error::InstructionError,
solana_pubkey::Pubkey,
solana_rent::Rent,
solana_slot_hashes::SlotHash,
solana_transaction_context::{BorrowedInstructionAccount, IndexOfAccount, InstructionContext},
solana_vote_interface::{error::VoteError, program::id},
std::{
cmp::Ordering,
collections::{HashSet, VecDeque},
},
};
enum PreserveBehaviorInHandlerHelper {
V3 { check_initialized: bool },
V4,
}
impl PreserveBehaviorInHandlerHelper {
fn new(target_version: VoteStateTargetVersion, check_initialized: bool) -> Self {
match target_version {
VoteStateTargetVersion::V3 => Self::V3 { check_initialized },
VoteStateTargetVersion::V4 => Self::V4,
}
}
}
fn get_vote_state_handler_checked(
vote_account: &BorrowedInstructionAccount,
preserve_behavior: PreserveBehaviorInHandlerHelper,
) -> Result<VoteStateHandler, InstructionError> {
match preserve_behavior {
PreserveBehaviorInHandlerHelper::V3 { check_initialized } => {
let vote_state = VoteStateV3::deserialize(vote_account.get_data())?;
if check_initialized && vote_state.is_uninitialized() {
return Err(InstructionError::UninitializedAccount);
}
Ok(VoteStateHandler::new_v3(vote_state))
}
PreserveBehaviorInHandlerHelper::V4 => {
let versioned = VoteStateVersions::deserialize(vote_account.get_data())?;
if versioned.is_uninitialized() {
return Err(InstructionError::UninitializedAccount);
}
let vote_state =
handler::try_convert_to_vote_state_v4(versioned, vote_account.get_key())?;
Ok(VoteStateHandler::new_v4(vote_state))
}
}
}
fn check_and_filter_proposed_vote_state(
vote_state: &VoteStateHandler,
proposed_lockouts: &mut VecDeque<Lockout>,
proposed_root: &mut Option<Slot>,
proposed_hash: Hash,
slot_hashes: &[(Slot, Hash)],
) -> Result<(), VoteError> {
if proposed_lockouts.is_empty() {
return Err(VoteError::EmptySlots);
}
let last_proposed_slot = proposed_lockouts
.back()
.expect("must be nonempty, checked above")
.slot();
if let Some(last_vote_slot) = vote_state.votes().back().map(|lockout| lockout.slot()) {
if last_proposed_slot <= last_vote_slot {
return Err(VoteError::VoteTooOld);
}
}
if slot_hashes.is_empty() {
return Err(VoteError::SlotsMismatch);
}
let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
if last_proposed_slot < earliest_slot_hash_in_history {
return Err(VoteError::VoteTooOld);
}
if let Some(root) = *proposed_root {
if root < earliest_slot_hash_in_history {
*proposed_root = vote_state.root_slot();
for vote in vote_state.votes().iter().rev() {
if vote.slot() <= root {
*proposed_root = Some(vote.slot());
break;
}
}
}
}
let mut root_to_check = *proposed_root;
let mut proposed_lockouts_index = 0;
let mut slot_hashes_index = slot_hashes.len();
let mut proposed_lockouts_indices_to_filter = vec![];
while proposed_lockouts_index < proposed_lockouts.len() && slot_hashes_index > 0 {
let proposed_vote_slot = if let Some(root) = root_to_check {
root
} else {
proposed_lockouts[proposed_lockouts_index].slot()
};
if root_to_check.is_none()
&& proposed_lockouts_index > 0
&& proposed_vote_slot
<= proposed_lockouts[proposed_lockouts_index.checked_sub(1).expect(
"`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`",
)]
.slot()
{
return Err(VoteError::SlotsNotOrdered);
}
let ancestor_slot = slot_hashes[slot_hashes_index
.checked_sub(1)
.expect("`slot_hashes_index` is positive when computing `ancestor_slot`")]
.0;
match proposed_vote_slot.cmp(&ancestor_slot) {
Ordering::Less => {
if slot_hashes_index == slot_hashes.len() {
if proposed_vote_slot >= earliest_slot_hash_in_history {
return Err(VoteError::AssertionFailed);
}
if !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
proposed_lockouts_indices_to_filter.push(proposed_lockouts_index);
}
if let Some(new_proposed_root) = root_to_check {
assert_eq!(new_proposed_root, proposed_vote_slot);
if new_proposed_root >= earliest_slot_hash_in_history {
return Err(VoteError::AssertionFailed);
}
root_to_check = None;
} else {
proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
"`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
`proposed_vote_slot` is too old to be in SlotHashes history",
);
}
continue;
} else {
if root_to_check.is_some() {
return Err(VoteError::RootOnDifferentFork);
} else {
return Err(VoteError::SlotsMismatch);
}
}
}
Ordering::Greater => {
slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
"`slot_hashes_index` is positive when finding newer slots in SlotHashes \
history",
);
continue;
}
Ordering::Equal => {
if root_to_check.is_some() {
root_to_check = None;
} else {
proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
"`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when match \
is found in SlotHashes history",
);
slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
"`slot_hashes_index` is positive when match is found in SlotHashes history",
);
}
}
}
}
if proposed_lockouts_index != proposed_lockouts.len() {
return Err(VoteError::SlotsMismatch);
}
assert_eq!(last_proposed_slot, slot_hashes[slot_hashes_index].0);
if slot_hashes[slot_hashes_index].1 != proposed_hash {
warn!(
"{} dropped vote {:?} root {:?} failed to match hash {} {}",
vote_state.node_pubkey(),
proposed_lockouts,
proposed_root,
proposed_hash,
slot_hashes[slot_hashes_index].1
);
#[cfg(feature = "metrics")]
inc_new_counter_info!("dropped-vote-hash", 1);
return Err(VoteError::SlotHashMismatch);
}
let mut proposed_lockouts_index = 0;
let mut filter_votes_index = 0;
proposed_lockouts.retain(|_lockout| {
let should_retain = if filter_votes_index == proposed_lockouts_indices_to_filter.len() {
true
} else if proposed_lockouts_index == proposed_lockouts_indices_to_filter[filter_votes_index]
{
filter_votes_index = filter_votes_index.checked_add(1).unwrap();
false
} else {
true
};
proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
"`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when filtering out \
irrelevant votes",
);
should_retain
});
Ok(())
}
fn check_slots_are_valid<T: VoteStateHandle>(
vote_state: &T,
vote_slots: &[Slot],
vote_hash: &Hash,
slot_hashes: &[(Slot, Hash)],
) -> Result<(), VoteError> {
let mut i = 0;
let mut j = slot_hashes.len();
while i < vote_slots.len() && j > 0 {
if vote_state
.last_voted_slot()
.is_some_and(|last_voted_slot| vote_slots[i] <= last_voted_slot)
{
i = i
.checked_add(1)
.expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots");
continue;
}
if vote_slots[i] != slot_hashes[j.checked_sub(1).expect("`j` is positive")].0 {
j = j
.checked_sub(1)
.expect("`j` is positive when finding newer slots");
continue;
}
i = i
.checked_add(1)
.expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found");
j = j
.checked_sub(1)
.expect("`j` is positive when hash is found");
}
if j == slot_hashes.len() {
debug!(
"{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
vote_state.node_pubkey(),
vote_slots,
vote_hash,
slot_hashes
);
return Err(VoteError::VoteTooOld);
}
if i != vote_slots.len() {
info!(
"{} dropped vote slots {:?} failed to match slot hashes: {:?}",
vote_state.node_pubkey(),
vote_slots,
slot_hashes,
);
return Err(VoteError::SlotsMismatch);
}
if &slot_hashes[j].1 != vote_hash {
warn!(
"{} dropped vote slots {:?} failed to match hash {} {}",
vote_state.node_pubkey(),
vote_slots,
vote_hash,
slot_hashes[j].1
);
return Err(VoteError::SlotHashMismatch);
}
Ok(())
}
pub fn process_new_vote_state(
vote_state: &mut VoteStateHandler,
mut new_state: VecDeque<LandedVote>,
new_root: Option<Slot>,
timestamp: Option<i64>,
epoch: Epoch,
current_slot: Slot,
) -> Result<(), VoteError> {
assert!(!new_state.is_empty());
if new_state.len() > MAX_LOCKOUT_HISTORY {
return Err(VoteError::TooManyVotes);
}
match (new_root, vote_state.root_slot()) {
(Some(new_root), Some(current_root)) => {
if new_root < current_root {
return Err(VoteError::RootRollBack);
}
}
(None, Some(_)) => {
return Err(VoteError::RootRollBack);
}
_ => (),
}
let mut previous_vote: Option<&LandedVote> = None;
for vote in &new_state {
if vote.confirmation_count() == 0 {
return Err(VoteError::ZeroConfirmations);
} else if vote.confirmation_count() > MAX_LOCKOUT_HISTORY as u32 {
return Err(VoteError::ConfirmationTooLarge);
} else if let Some(new_root) = new_root {
if vote.slot() <= new_root
&&
new_root != Slot::default()
{
return Err(VoteError::SlotSmallerThanRoot);
}
}
if let Some(previous_vote) = previous_vote {
if previous_vote.slot() >= vote.slot() {
return Err(VoteError::SlotsNotOrdered);
} else if previous_vote.confirmation_count() <= vote.confirmation_count() {
return Err(VoteError::ConfirmationsNotOrdered);
} else if vote.slot() > previous_vote.lockout.last_locked_out_slot() {
return Err(VoteError::NewVoteStateLockoutMismatch);
}
}
previous_vote = Some(vote);
}
let mut current_vote_state_index: usize = 0;
let mut new_vote_state_index = 0;
let mut earned_credits = 0_u64;
if let Some(new_root) = new_root {
for current_vote in vote_state.votes() {
if current_vote.slot() <= new_root {
earned_credits = earned_credits
.checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index))
.expect("`earned_credits` does not overflow");
current_vote_state_index = current_vote_state_index.checked_add(1).expect(
"`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
processing new root",
);
continue;
}
break;
}
}
while current_vote_state_index < vote_state.votes().len()
&& new_vote_state_index < new_state.len()
{
let current_vote = &vote_state.votes()[current_vote_state_index];
let new_vote = &mut new_state[new_vote_state_index];
match current_vote.slot().cmp(&new_vote.slot()) {
Ordering::Less => {
if current_vote.lockout.last_locked_out_slot() >= new_vote.slot() {
return Err(VoteError::LockoutConflict);
}
current_vote_state_index = current_vote_state_index.checked_add(1).expect(
"`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
less than proposed",
);
}
Ordering::Equal => {
if new_vote.confirmation_count() < current_vote.confirmation_count() {
return Err(VoteError::ConfirmationRollBack);
}
new_vote.latency = vote_state.votes()[current_vote_state_index].latency;
current_vote_state_index = current_vote_state_index.checked_add(1).expect(
"`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
equal to proposed",
);
new_vote_state_index = new_vote_state_index.checked_add(1).expect(
"`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
equal to proposed",
);
}
Ordering::Greater => {
new_vote_state_index = new_vote_state_index.checked_add(1).expect(
"`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
greater than proposed",
);
}
}
}
for new_vote in new_state.iter_mut() {
if new_vote.latency == 0 {
new_vote.latency = handler::compute_vote_latency(new_vote.slot(), current_slot);
}
}
if vote_state.root_slot() != new_root {
vote_state.increment_credits(epoch, earned_credits);
}
if let Some(timestamp) = timestamp {
let last_slot = new_state.back().unwrap().slot();
vote_state.process_timestamp(last_slot, timestamp)?;
}
vote_state.set_root_slot(new_root);
vote_state.set_votes(new_state);
Ok(())
}
pub fn process_vote_unfiltered<T: VoteStateHandle>(
vote_state: &mut T,
vote_slots: &[Slot],
vote: &Vote,
slot_hashes: &[SlotHash],
epoch: Epoch,
current_slot: Slot,
) -> Result<(), VoteError> {
check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?;
vote_slots
.iter()
.for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot));
Ok(())
}
pub fn process_vote(
vote_state: &mut VoteStateHandler,
vote: &Vote,
slot_hashes: &[SlotHash],
epoch: Epoch,
current_slot: Slot,
) -> Result<(), VoteError> {
if vote.slots.is_empty() {
return Err(VoteError::EmptySlots);
}
let earliest_slot_in_history = slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
let vote_slots = vote
.slots
.iter()
.filter(|slot| **slot >= earliest_slot_in_history)
.cloned()
.collect::<Vec<Slot>>();
if vote_slots.is_empty() {
return Err(VoteError::VotesTooOldAllFiltered);
}
process_vote_unfiltered(
vote_state,
&vote_slots,
vote,
slot_hashes,
epoch,
current_slot,
)
}
pub fn process_vote_unchecked<T: VoteStateHandle>(
vote_state: &mut T,
vote: Vote,
) -> Result<(), VoteError> {
if vote.slots.is_empty() {
return Err(VoteError::EmptySlots);
}
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
process_vote_unfiltered(
vote_state,
&vote.slots,
&vote,
&slot_hashes,
vote_state.current_epoch(),
0,
)
}
#[cfg(test)]
pub fn process_slot_votes_unchecked<T: VoteStateHandle>(vote_state: &mut T, slots: &[Slot]) {
for slot in slots {
process_slot_vote_unchecked(vote_state, *slot);
}
}
pub fn process_slot_vote_unchecked<T: VoteStateHandle>(vote_state: &mut T, slot: Slot) {
let _ = process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
}
pub fn authorize<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
authorized: &Pubkey,
vote_authorize: VoteAuthorize,
signers: &HashSet<Pubkey, S>,
clock: &Clock,
) -> Result<(), InstructionError> {
let mut vote_state = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, false),
)?;
match vote_authorize {
VoteAuthorize::Voter => {
let authorized_withdrawer_signer =
verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
vote_state.set_new_authorized_voter(
authorized,
clock.epoch,
clock
.leader_schedule_epoch
.checked_add(1)
.ok_or(InstructionError::InvalidAccountData)?,
|epoch_authorized_voter| {
if authorized_withdrawer_signer {
Ok(())
} else {
verify_authorized_signer(&epoch_authorized_voter, signers)
}
},
)?;
}
VoteAuthorize::Withdrawer => {
verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
vote_state.set_authorized_withdrawer(*authorized);
}
}
vote_state.set_vote_account_state(vote_account)
}
pub fn update_validator_identity<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
node_pubkey: &Pubkey,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, false),
)?;
verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
verify_authorized_signer(node_pubkey, signers)?;
vote_state.set_node_pubkey(*node_pubkey);
vote_state.set_block_revenue_collector(*node_pubkey);
vote_state.set_vote_account_state(vote_account)
}
pub fn update_commission<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
commission: u8,
signers: &HashSet<Pubkey, S>,
epoch_schedule: &EpochSchedule,
clock: &Clock,
) -> Result<(), InstructionError> {
let vote_state_result = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, false),
);
let enforce_commission_update_rule = if let Ok(decoded_vote_state) = &vote_state_result {
commission > decoded_vote_state.commission()
} else {
true
};
if enforce_commission_update_rule && !is_commission_update_allowed(clock.slot, epoch_schedule) {
return Err(VoteError::CommissionUpdateTooLate.into());
}
let mut vote_state = vote_state_result?;
verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
vote_state.set_commission(commission);
vote_state.set_vote_account_state(vote_account)
}
pub fn is_commission_update_allowed(slot: Slot, epoch_schedule: &EpochSchedule) -> bool {
if let Some(relative_slot) = slot
.saturating_sub(epoch_schedule.first_normal_slot)
.checked_rem(epoch_schedule.slots_per_epoch)
{
relative_slot.saturating_mul(2) <= epoch_schedule.slots_per_epoch
} else {
true
}
}
fn verify_authorized_signer<S: std::hash::BuildHasher>(
authorized: &Pubkey,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
if signers.contains(authorized) {
Ok(())
} else {
Err(InstructionError::MissingRequiredSignature)
}
}
pub fn withdraw<S: std::hash::BuildHasher>(
instruction_context: &InstructionContext,
vote_account_index: IndexOfAccount,
target_version: VoteStateTargetVersion,
lamports: u64,
to_account_index: IndexOfAccount,
signers: &HashSet<Pubkey, S>,
rent_sysvar: &Rent,
clock: &Clock,
) -> Result<(), InstructionError> {
let mut vote_account =
instruction_context.try_borrow_instruction_account(vote_account_index)?;
let vote_state = get_vote_state_handler_checked(
&vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, false),
)?;
verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
let remaining_balance = vote_account
.get_lamports()
.checked_sub(lamports)
.ok_or(InstructionError::InsufficientFunds)?;
if remaining_balance == 0 {
let reject_active_vote_account_close = vote_state
.epoch_credits()
.last()
.map(|(last_epoch_with_credits, _, _)| {
let current_epoch = clock.epoch;
current_epoch.saturating_sub(*last_epoch_with_credits) < 2
})
.unwrap_or(false);
if reject_active_vote_account_close {
return Err(VoteError::ActiveVoteAccountClose.into());
} else {
VoteStateHandler::deinitialize_vote_account_state(&mut vote_account, target_version)?;
}
} else {
let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
if remaining_balance < min_rent_exempt_balance {
return Err(InstructionError::InsufficientFunds);
}
}
vote_account.checked_sub_lamports(lamports)?;
drop(vote_account);
let mut to_account = instruction_context.try_borrow_instruction_account(to_account_index)?;
to_account.checked_add_lamports(lamports)?;
Ok(())
}
pub fn initialize_account<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
vote_init: &VoteInit,
signers: &HashSet<Pubkey, S>,
clock: &Clock,
) -> Result<(), InstructionError> {
VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
let versioned = vote_account.get_state::<VoteStateVersions>()?;
if !versioned.is_uninitialized() {
return Err(InstructionError::AccountAlreadyInitialized);
}
verify_authorized_signer(&vote_init.node_pubkey, signers)?;
VoteStateHandler::init_vote_account_state(vote_account, vote_init, clock, target_version)
}
pub fn process_vote_with_account<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
slot_hashes: &[SlotHash],
clock: &Clock,
vote: &Vote,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)?;
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
verify_authorized_signer(&authorized_voter, signers)?;
process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?;
if let Some(timestamp) = vote.timestamp {
vote.slots
.iter()
.max()
.ok_or(VoteError::EmptySlots)
.and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
}
vote_state.set_vote_account_state(vote_account)
}
pub fn process_vote_state_update<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
slot_hashes: &[SlotHash],
clock: &Clock,
vote_state_update: VoteStateUpdate,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)?;
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
verify_authorized_signer(&authorized_voter, signers)?;
do_process_vote_state_update(
&mut vote_state,
slot_hashes,
clock.epoch,
clock.slot,
vote_state_update,
)?;
vote_state.set_vote_account_state(vote_account)
}
pub fn do_process_vote_state_update(
vote_state: &mut VoteStateHandler,
slot_hashes: &[SlotHash],
epoch: u64,
slot: u64,
mut vote_state_update: VoteStateUpdate,
) -> Result<(), VoteError> {
check_and_filter_proposed_vote_state(
vote_state,
&mut vote_state_update.lockouts,
&mut vote_state_update.root,
vote_state_update.hash,
slot_hashes,
)?;
process_new_vote_state(
vote_state,
vote_state_update
.lockouts
.iter()
.map(|lockout| LandedVote::from(*lockout))
.collect(),
vote_state_update.root,
vote_state_update.timestamp,
epoch,
slot,
)
}
pub fn process_tower_sync<S: std::hash::BuildHasher>(
vote_account: &mut BorrowedInstructionAccount,
target_version: VoteStateTargetVersion,
slot_hashes: &[SlotHash],
clock: &Clock,
tower_sync: TowerSync,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = get_vote_state_handler_checked(
vote_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)?;
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
verify_authorized_signer(&authorized_voter, signers)?;
do_process_tower_sync(
&mut vote_state,
slot_hashes,
clock.epoch,
clock.slot,
tower_sync,
)?;
vote_state.set_vote_account_state(vote_account)
}
fn do_process_tower_sync(
vote_state: &mut VoteStateHandler,
slot_hashes: &[SlotHash],
epoch: u64,
slot: u64,
mut tower_sync: TowerSync,
) -> Result<(), VoteError> {
check_and_filter_proposed_vote_state(
vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
slot_hashes,
)?;
process_new_vote_state(
vote_state,
tower_sync
.lockouts
.iter()
.map(|lockout| LandedVote::from(*lockout))
.collect(),
tower_sync.root,
tower_sync.timestamp,
epoch,
slot,
)
}
pub fn create_v3_account_with_authorized(
node_pubkey: &Pubkey,
authorized_voter: &Pubkey,
authorized_withdrawer: &Pubkey,
commission: u8,
lamports: u64,
) -> AccountSharedData {
let mut vote_account = AccountSharedData::new(lamports, VoteStateV3::size_of(), &id());
let vote_state = VoteStateV3::new(
&VoteInit {
node_pubkey: *node_pubkey,
authorized_voter: *authorized_voter,
authorized_withdrawer: *authorized_withdrawer,
commission,
},
&Clock::default(),
);
VoteStateV3::serialize(
&VoteStateVersions::V3(Box::new(vote_state)),
vote_account.data_as_mut_slice(),
)
.unwrap();
vote_account
}
pub fn create_v4_account_with_authorized(
node_pubkey: &Pubkey,
authorized_voter: &Pubkey,
authorized_withdrawer: &Pubkey,
bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
inflation_rewards_commission_bps: u16,
lamports: u64,
) -> AccountSharedData {
let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id());
let vote_state = handler::create_new_vote_state_v4_for_tests(
node_pubkey,
authorized_voter,
authorized_withdrawer,
bls_pubkey_compressed,
inflation_rewards_commission_bps,
);
VoteStateV4::serialize(
&VoteStateVersions::V4(Box::new(vote_state)),
vote_account.data_as_mut_slice(),
)
.unwrap();
vote_account
}
#[allow(clippy::arithmetic_side_effects)]
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
solana_account::{AccountSharedData, ReadableAccount},
solana_clock::DEFAULT_SLOTS_PER_EPOCH,
solana_sha256_hasher::hash,
solana_transaction_context::{InstructionAccount, TransactionContext},
solana_vote_interface::authorized_voters::AuthorizedVoters,
test_case::test_case,
};
const MAX_RECENT_VOTES: usize = 16;
fn vote_state_new_for_test(
vote_pubkey: &Pubkey,
target_version: VoteStateTargetVersion,
) -> VoteStateHandler {
let auth_pubkey = solana_pubkey::new_rand();
let vote_init = VoteInit {
node_pubkey: solana_pubkey::new_rand(),
authorized_voter: auth_pubkey,
authorized_withdrawer: auth_pubkey,
commission: 0,
};
let clock = Clock::default();
match target_version {
VoteStateTargetVersion::V3 => {
VoteStateHandler::new_v3(VoteStateV3::new(&vote_init, &clock))
}
VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(
handler::create_new_vote_state_v4(vote_pubkey, &vote_init, &clock),
),
}
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_vote_state_upgrade_from_1_14_11(target_version: VoteStateTargetVersion) {
let vote_pubkey = solana_pubkey::new_rand();
let mut vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
vote_state.increment_credits(0, 100);
assert_eq!(
vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 0, 1, |_pubkey| Ok(())),
Ok(())
);
vote_state.increment_credits(1, 200);
assert_eq!(
vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 1, 2, |_pubkey| Ok(())),
Ok(())
);
vote_state.increment_credits(2, 300);
assert_eq!(
vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 2, 3, |_pubkey| Ok(())),
Ok(())
);
vec![
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135,
]
.into_iter()
.for_each(|v| vote_state.process_next_vote_slot(v, 4, 0));
let vote_state_v1_14_11 = match target_version {
VoteStateTargetVersion::V3 => {
VoteState1_14_11::from(vote_state.as_ref_v3().clone())
}
VoteStateTargetVersion::V4 => {
VoteState1_14_11 {
node_pubkey: *vote_state.node_pubkey(),
authorized_withdrawer: *vote_state.authorized_withdrawer(),
commission: vote_state.commission(),
votes: vote_state
.votes()
.iter()
.map(|landed_vote| (*landed_vote).into())
.collect(),
root_slot: vote_state.root_slot(),
authorized_voters: vote_state.authorized_voters().clone(),
epoch_credits: vote_state.epoch_credits().clone(),
last_timestamp: vote_state.last_timestamp().clone(),
prior_voters: CircBuf::default(), }
}
};
let version1_14_11_serialized =
bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(vote_state_v1_14_11)))
.unwrap();
let version1_14_11_serialized_len = version1_14_11_serialized.len();
let rent = Rent::default();
let lamports = rent.minimum_balance(version1_14_11_serialized_len);
let mut vote_account =
AccountSharedData::new(lamports, version1_14_11_serialized_len, &id());
vote_account.set_data_from_slice(&version1_14_11_serialized);
let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
let mut transaction_context = TransactionContext::new(
vec![(id(), processor_account), (vote_pubkey, vote_account)],
rent.clone(),
0,
0,
);
transaction_context
.configure_next_instruction_for_tests(
0,
vec![InstructionAccount::new(1, false, true)],
vec![],
)
.unwrap();
let instruction_context = transaction_context.get_next_instruction_context().unwrap();
let mut borrowed_account = instruction_context
.try_borrow_instruction_account(0)
.unwrap();
let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
let converted_vote_state = get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap();
assert!(vote_state == converted_vote_state);
let vote_state = converted_vote_state;
match target_version {
VoteStateTargetVersion::V3 => {
assert_eq!(
vote_state
.clone()
.set_vote_account_state(&mut borrowed_account),
Ok(())
);
let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
}
VoteStateTargetVersion::V4 => {
assert_eq!(
vote_state
.clone()
.set_vote_account_state(&mut borrowed_account),
Err(InstructionError::AccountNotRentExempt)
);
}
}
let converted_vote_state = get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap();
assert!(vote_state == converted_vote_state);
let vote_state = converted_vote_state;
let space = match target_version {
VoteStateTargetVersion::V3 => VoteStateV3::size_of(),
VoteStateTargetVersion::V4 => VoteStateV4::size_of(), };
assert_eq!(
borrowed_account.set_lamports(rent.minimum_balance(space)),
Ok(())
);
assert_eq!(
vote_state
.clone()
.set_vote_account_state(&mut borrowed_account),
Ok(())
);
let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
match target_version {
VoteStateTargetVersion::V3 => {
assert_matches!(vote_state_version, VoteStateVersions::V3(_));
}
VoteStateTargetVersion::V4 => {
assert_matches!(vote_state_version, VoteStateVersions::V4(_));
}
}
let converted_vote_state = get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap();
assert_eq!(vote_state, converted_vote_state);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_vote_lockout(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
}
assert_eq!(vote_state.votes().len(), MAX_LOCKOUT_HISTORY);
assert_eq!(vote_state.root_slot(), Some(0));
check_lockouts(&vote_state);
let top_vote = vote_state.votes().front().unwrap().slot();
let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
process_slot_vote_unchecked(&mut vote_state, slot);
assert_eq!(Some(top_vote), vote_state.root_slot());
let slot = vote_state
.votes()
.front()
.unwrap()
.lockout
.last_locked_out_slot();
process_slot_vote_unchecked(&mut vote_state, slot);
assert_eq!(vote_state.votes().len(), 2);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_update_commission(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
let node_pubkey = *vote_state.node_pubkey();
let withdrawer_pubkey = *vote_state.authorized_withdrawer();
vote_state.set_commission(10);
let serialized = vote_state.serialize();
let serialized_len = serialized.len();
let rent = Rent::default();
let lamports = rent.minimum_balance(serialized_len);
let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
vote_account.set_data_from_slice(&serialized);
let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
let mut transaction_context = TransactionContext::new(
vec![(id(), processor_account), (node_pubkey, vote_account)],
rent,
0,
0,
);
transaction_context
.configure_next_instruction_for_tests(
0,
vec![InstructionAccount::new(1, false, true)],
vec![],
)
.unwrap();
let instruction_context = transaction_context.get_next_instruction_context().unwrap();
let mut borrowed_account = instruction_context
.try_borrow_instruction_account(0)
.unwrap();
let epoch_schedule = std::sync::Arc::new(EpochSchedule::without_warmup());
let first_half_clock = std::sync::Arc::new(Clock {
slot: epoch_schedule.slots_per_epoch / 4,
..Clock::default()
});
let second_half_clock = std::sync::Arc::new(Clock {
slot: (epoch_schedule.slots_per_epoch * 3) / 4,
..Clock::default()
});
let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
10
);
assert_matches!(
update_commission(
&mut borrowed_account,
target_version,
11,
&signers,
&epoch_schedule,
&first_half_clock,
),
Ok(())
);
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
11
);
assert_matches!(
update_commission(
&mut borrowed_account,
target_version,
12,
&signers,
&epoch_schedule,
&second_half_clock,
),
Err(_)
);
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
11
);
assert_matches!(
update_commission(
&mut borrowed_account,
target_version,
10,
&signers,
&epoch_schedule,
&first_half_clock,
),
Ok(())
);
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
10
);
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
10
);
assert_matches!(
update_commission(
&mut borrowed_account,
target_version,
9,
&signers,
&epoch_schedule,
&second_half_clock,
),
Ok(())
);
assert_eq!(
get_vote_state_handler_checked(
&borrowed_account,
PreserveBehaviorInHandlerHelper::new(target_version, true),
)
.unwrap()
.commission(),
9
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_vote_double_lockout_after_expiration(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
for i in 0..3 {
process_slot_vote_unchecked(&mut vote_state, i as u64);
}
check_lockouts(&vote_state);
process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
check_lockouts(&vote_state);
process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
check_lockouts(&vote_state);
process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
check_lockouts(&vote_state);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_expire_multiple_votes(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
for i in 0..3 {
process_slot_vote_unchecked(&mut vote_state, i as u64);
}
assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
let expire_slot =
vote_state.votes()[1].slot() + vote_state.votes()[1].lockout.lockout() + 1;
process_slot_vote_unchecked(&mut vote_state, expire_slot);
assert_eq!(vote_state.votes().len(), 2);
assert_eq!(vote_state.votes()[0].slot(), 0);
assert_eq!(vote_state.votes()[1].slot(), expire_slot);
process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);
assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
assert_eq!(vote_state.votes()[1].confirmation_count(), 2);
assert_eq!(vote_state.votes()[2].confirmation_count(), 1);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_vote_credits(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
for i in 0..MAX_LOCKOUT_HISTORY {
process_slot_vote_unchecked(&mut vote_state, i as u64);
}
assert_eq!(vote_state.credits(), 0);
process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
assert_eq!(vote_state.credits(), 1);
process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
assert_eq!(vote_state.credits(), 2);
process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
assert_eq!(vote_state.credits(), 3);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_duplicate_vote(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
process_slot_vote_unchecked(&mut vote_state, 0);
process_slot_vote_unchecked(&mut vote_state, 1);
process_slot_vote_unchecked(&mut vote_state, 0);
assert_eq!(vote_state.nth_recent_lockout(0).unwrap().slot(), 1);
assert_eq!(vote_state.nth_recent_lockout(1).unwrap().slot(), 0);
assert!(vote_state.nth_recent_lockout(2).is_none());
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_nth_recent_lockout(target_version: VoteStateTargetVersion) {
let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
for i in 0..MAX_LOCKOUT_HISTORY {
process_slot_vote_unchecked(&mut vote_state, i as u64);
}
for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
assert_eq!(
vote_state.nth_recent_lockout(i).unwrap().slot() as usize,
MAX_LOCKOUT_HISTORY - i - 1,
);
}
assert!(vote_state.nth_recent_lockout(MAX_LOCKOUT_HISTORY).is_none());
}
fn check_lockouts(vote_state: &VoteStateHandler) {
let votes = vote_state.votes();
for (i, vote) in votes.iter().enumerate() {
let num_votes = votes
.len()
.checked_sub(i)
.expect("`i` is less than `vote_state.votes().len()`");
assert_eq!(
vote.lockout.lockout(),
INITIAL_LOCKOUT.pow(num_votes as u32) as u64
);
}
}
fn recent_votes(vote_state: &VoteStateHandler) -> Vec<Vote> {
let votes = vote_state.votes();
let start = votes.len().saturating_sub(MAX_RECENT_VOTES);
(start..votes.len())
.map(|i| Vote::new(vec![votes.get(i).unwrap().slot()], Hash::default()))
.collect()
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_process_missed_votes(target_version: VoteStateTargetVersion) {
let mut vote_state_a = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
let mut vote_state_b = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
(0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
let vote = Vote::new(slots, Hash::default());
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
assert_eq!(
process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0),
Ok(())
);
assert_eq!(
process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0),
Ok(())
);
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_vote_skips_old_vote(mut vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Ok(())
);
let recent = recent_votes(&vote_state);
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Err(VoteError::VoteTooOld)
);
assert_eq!(recent, recent_votes(&vote_state));
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_vote_empty_slot_hashes(vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
Err(VoteError::VoteTooOld)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_new_vote(vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Ok(())
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_bad_hash(vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Err(VoteError::SlotHashMismatch)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_bad_slot(vote_state: VoteStateHandler) {
let vote = Vote::new(vec![1], Hash::default());
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Err(VoteError::SlotsMismatch)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_duplicate_vote(mut vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Ok(())
);
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Err(VoteError::VoteTooOld)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_next_vote(mut vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Ok(())
);
let vote = Vote::new(vec![0, 1], Hash::default());
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Ok(())
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_check_slots_are_valid_next_vote_only(mut vote_state: VoteStateHandler) {
let vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Ok(())
);
let vote = Vote::new(vec![1], Hash::default());
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
assert_eq!(
check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
Ok(())
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_vote_empty_slots(mut vote_state: VoteStateHandler) {
let vote = Vote::new(vec![], Hash::default());
assert_eq!(
process_vote(&mut vote_state, &vote, &[], 0, 0),
Err(VoteError::EmptySlots)
);
}
pub fn process_new_vote_state_from_lockouts(
vote_state: &mut VoteStateHandler,
new_state: VecDeque<Lockout>,
new_root: Option<Slot>,
timestamp: Option<i64>,
epoch: Epoch,
) -> Result<(), VoteError> {
process_new_vote_state(
vote_state,
new_state.into_iter().map(LandedVote::from).collect(),
new_root,
timestamp,
epoch,
0,
)
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_vote_state_update_increment_credits(mut vote_state: VoteStateHandler) {
let test_vote_groups: Vec<Vec<Slot>> = vec![
vec![1, 2, 3, 4, 5, 6, 7, 8],
vec![
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
],
vec![32],
vec![33],
vec![34, 35],
vec![36, 37, 38],
vec![
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68,
],
vec![
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
],
vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
vec![200, 201],
vec![
202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
218, 219, 220, 221, 222, 223, 224, 225, 226,
],
vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
];
for vote_group in test_vote_groups {
let mut vote_state_after_vote = vote_state.clone();
process_vote_unchecked(
&mut vote_state_after_vote,
Vote {
slots: vote_group.clone(),
hash: Hash::new_unique(),
timestamp: None,
},
)
.unwrap();
assert_eq!(
process_new_vote_state(
&mut vote_state,
vote_state_after_vote.votes().clone(),
vote_state_after_vote.root_slot(),
None,
0,
0,
),
Ok(())
);
assert_eq!(
vote_state.epoch_credits(),
vote_state_after_vote.epoch_credits()
);
}
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_timely_credits(target_version: VoteStateTargetVersion) {
let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
(
vec![1, 2, 3, 4, 5, 6, 7, 8],
9,
0,
),
(
vec![
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31,
],
34,
0,
),
(
vec![32],
35,
10,
),
(
vec![33],
36,
10 + 11, ),
(
vec![34, 35],
37,
21 + 12 + 13, ),
(
vec![36, 37, 38],
39,
46 + 14 + 15 + 16, ),
(
vec![
39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
],
69,
91 + 16
+ 9 + 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 15
+ 15
+ 15
+ 16
+ 15
+ 16, ),
(
vec![
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
],
100,
327 + 16
+ 14 + 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 16, ),
(
vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
130,
508 + ((74 - 69) + 1), ),
(
vec![200, 201],
202,
514,
),
(
vec![
202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
218, 219, 220, 221, 222, 223, 224, 225, 226,
],
227,
514 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13, ),
(
vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
237,
613 + 3 + 4 + 5 + 6 + 16 + 16 + 1 + 1 + 1 + 1, ),
];
let new_vote_state = || match target_version {
VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
};
for i in 0..test_vote_groups.len() {
let mut vote_state_1 = new_vote_state();
let mut vote_state_2 = new_vote_state();
test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
let vote = Vote {
slots: vote_group.0.clone(), hash: Hash::new_unique(),
timestamp: None,
};
let slot_hashes: Vec<_> =
vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
assert_eq!(
process_vote(
&mut vote_state_1,
&vote,
&slot_hashes,
0,
vote_group.1, ),
Ok(())
);
assert_eq!(
process_new_vote_state(
&mut vote_state_2,
vote_state_1.votes().clone(),
vote_state_1.root_slot(),
None,
0,
vote_group.1, ),
Ok(())
);
});
let vote_group = &test_vote_groups[i];
assert_eq!(vote_state_1.credits(), vote_group.2 as u64); assert_eq!(vote_state_2.credits(), vote_group.2 as u64); }
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_retroactive_voting_timely_credits(mut vote_state: VoteStateHandler) {
#[allow(clippy::type_complexity)]
let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
(
vec![(7, 4), (8, 3), (9, 2), (10, 1)],
11,
None,
0,
),
(
vec![
(1, 10),
(2, 9),
(3, 8),
(4, 7),
(5, 6),
(6, 5),
(7, 4),
(8, 3),
(9, 2),
(10, 1),
],
12,
None,
0,
),
(
vec![
(11, 31),
(12, 30),
(13, 29),
(14, 28),
(15, 27),
(16, 26),
(17, 25),
(18, 24),
(19, 23),
(20, 22),
(21, 21),
(22, 20),
(23, 19),
(24, 18),
(25, 17),
(26, 16),
(27, 15),
(28, 14),
(29, 13),
(30, 12),
(31, 11),
(32, 10),
(33, 9),
(34, 8),
(35, 7),
(36, 6),
(37, 5),
(38, 4),
(39, 3),
(40, 2),
(41, 1),
],
42,
Some(10),
7 + 8 + 9 + 10 + 11 + 12 + 14 + 15 + 16 + 16,
),
];
test_vote_state_updates
.iter()
.for_each(|proposed_vote_state| {
let new_state = proposed_vote_state
.0 .iter()
.map(|(slot, confirmation_count)| LandedVote {
latency: 0,
lockout: Lockout::new_with_confirmation_count(*slot, *confirmation_count),
})
.collect::<VecDeque<LandedVote>>();
assert_eq!(
process_new_vote_state(
&mut vote_state,
new_state,
proposed_vote_state.2, None,
0,
proposed_vote_state.1, ),
Ok(())
);
assert_eq!(vote_state.credits(), proposed_vote_state.3 as u64);
});
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_too_many_votes(mut vote_state1: VoteStateHandler) {
let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
.map(|slot| {
Lockout::new_with_confirmation_count(
slot as Slot,
(MAX_LOCKOUT_HISTORY - slot + 1) as u32,
)
})
.collect();
let current_epoch = vote_state1.current_epoch();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::TooManyVotes)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_root_rollback(mut vote_state1: VoteStateHandler) {
for i in 0..MAX_LOCKOUT_HISTORY + 2 {
process_slot_vote_unchecked(&mut vote_state1, i as Slot);
}
assert_eq!(vote_state1.root_slot().unwrap(), 1);
let mut vote_state2 = vote_state1.clone();
process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);
let lesser_root = Some(0);
let current_epoch = vote_state2.current_epoch();
assert_eq!(
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
lesser_root,
None,
current_epoch,
0,
),
Err(VoteError::RootRollBack)
);
let none_root = None;
assert_eq!(
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
none_root,
None,
current_epoch,
0,
),
Err(VoteError::RootRollBack)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_zero_confirmations(mut vote_state1: VoteStateHandler) {
let current_epoch = vote_state1.current_epoch();
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 0),
Lockout::new_with_confirmation_count(1, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::ZeroConfirmations)
);
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 2),
Lockout::new_with_confirmation_count(1, 0),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::ZeroConfirmations)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_confirmations_too_large(initial_vote_state: VoteStateHandler) {
let mut vote_state1 = initial_vote_state.clone();
let current_epoch = vote_state1.current_epoch();
let good_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
0,
MAX_LOCKOUT_HISTORY as u32,
)]
.into_iter()
.collect();
process_new_vote_state_from_lockouts(
&mut vote_state1,
good_votes,
None,
None,
current_epoch,
)
.unwrap();
let mut vote_state1 = initial_vote_state;
let bad_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
0,
MAX_LOCKOUT_HISTORY as u32 + 1,
)]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::ConfirmationTooLarge)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_slot_smaller_than_root(mut vote_state1: VoteStateHandler) {
let current_epoch = vote_state1.current_epoch();
let root_slot = 5;
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(root_slot, 2),
Lockout::new_with_confirmation_count(root_slot + 1, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
Some(root_slot),
None,
current_epoch,
),
Err(VoteError::SlotSmallerThanRoot)
);
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(root_slot - 1, 2),
Lockout::new_with_confirmation_count(root_slot + 1, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
Some(root_slot),
None,
current_epoch,
),
Err(VoteError::SlotSmallerThanRoot)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_slots_not_ordered(mut vote_state1: VoteStateHandler) {
let current_epoch = vote_state1.current_epoch();
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(1, 2),
Lockout::new_with_confirmation_count(0, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::SlotsNotOrdered)
);
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(1, 2),
Lockout::new_with_confirmation_count(1, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::SlotsNotOrdered)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_confirmations_not_ordered(mut vote_state1: VoteStateHandler) {
let current_epoch = vote_state1.current_epoch();
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 1),
Lockout::new_with_confirmation_count(1, 2),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::ConfirmationsNotOrdered)
);
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 1),
Lockout::new_with_confirmation_count(1, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::ConfirmationsNotOrdered)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_new_vote_state_lockout_mismatch(
mut vote_state1: VoteStateHandler,
) {
let current_epoch = vote_state1.current_epoch();
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 2),
Lockout::new_with_confirmation_count(7, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
None,
None,
current_epoch,
),
Err(VoteError::NewVoteStateLockoutMismatch)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_confirmation_rollback(mut vote_state1: VoteStateHandler) {
let current_epoch = vote_state1.current_epoch();
let votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 4),
Lockout::new_with_confirmation_count(1, 3),
]
.into_iter()
.collect();
process_new_vote_state_from_lockouts(&mut vote_state1, votes, None, None, current_epoch)
.unwrap();
let votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(0, 4),
Lockout::new_with_confirmation_count(1, 2),
Lockout::new_with_confirmation_count(2, 1),
]
.into_iter()
.collect();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
votes,
None,
None,
current_epoch,
),
Err(VoteError::ConfirmationRollBack)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_root_progress(mut vote_state1: VoteStateHandler) {
for i in 0..MAX_LOCKOUT_HISTORY {
process_slot_vote_unchecked(&mut vote_state1, i as u64);
}
assert!(vote_state1.root_slot().is_none());
let mut vote_state2 = vote_state1.clone();
for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
assert_ne!(vote_state1.root_slot(), vote_state2.root_slot());
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
vote_state2.root_slot(),
None,
vote_state2.current_epoch(),
0,
)
.unwrap();
assert_eq!(vote_state1, vote_state2);
}
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_same_slot_but_not_common_ancestor(
initial_vote_state: VoteStateHandler,
) {
let mut vote_state1 = initial_vote_state.clone();
process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
assert_eq!(
vote_state1
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 5]
);
let mut vote_state2 = initial_vote_state;
process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
assert_eq!(
vote_state2
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 2, 3, 5, 7]
);
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
vote_state2.root_slot(),
None,
vote_state2.current_epoch(),
0,
)
.unwrap();
assert_eq!(vote_state1, vote_state2);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_lockout_violation(initial_vote_state: VoteStateHandler) {
let mut vote_state1 = initial_vote_state.clone();
process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
assert_eq!(
vote_state1
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 2, 4, 5]
);
let mut vote_state2 = initial_vote_state;
process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
assert_eq!(
vote_state2
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 2, 3, 5, 7]
);
assert_eq!(
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
vote_state2.root_slot(),
None,
vote_state2.current_epoch(),
0,
),
Err(VoteError::LockoutConflict)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_lockout_violation2(initial_vote_state: VoteStateHandler) {
let mut vote_state1 = initial_vote_state.clone();
process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
assert_eq!(
vote_state1
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 5, 6, 7]
);
let mut vote_state2 = initial_vote_state;
process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
assert_eq!(
vote_state2
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 2, 3, 5, 6, 8]
);
assert_eq!(
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
vote_state2.root_slot(),
None,
vote_state2.current_epoch(),
0,
),
Err(VoteError::LockoutConflict)
);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_state_expired_ancestor_not_removed(mut vote_state1: VoteStateHandler) {
process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
assert_eq!(
vote_state1
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 9]
);
let mut vote_state2 = vote_state1.clone();
process_slot_vote_unchecked(&mut vote_state2, 10);
assert_eq!(vote_state2.votes()[0].slot(), 1);
assert_eq!(vote_state2.votes()[0].lockout.last_locked_out_slot(), 9);
assert_eq!(
vote_state2
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![1, 9, 10]
);
process_new_vote_state(
&mut vote_state1,
vote_state2.votes().clone(),
vote_state2.root_slot(),
None,
vote_state2.current_epoch(),
0,
)
.unwrap();
assert_eq!(vote_state1, vote_state2,);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_process_new_vote_current_state_contains_bigger_slots(
mut vote_state1: VoteStateHandler,
) {
process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
assert_eq!(
vote_state1
.votes()
.iter()
.map(|vote| vote.slot())
.collect::<Vec<Slot>>(),
vec![6, 7, 8]
);
let bad_votes: VecDeque<Lockout> = vec![
Lockout::new_with_confirmation_count(2, 5),
Lockout::new_with_confirmation_count(14, 1),
]
.into_iter()
.collect();
let root = Some(1);
let current_epoch = vote_state1.current_epoch();
assert_eq!(
process_new_vote_state_from_lockouts(
&mut vote_state1,
bad_votes,
root,
None,
current_epoch,
),
Err(VoteError::LockoutConflict)
);
let good_votes: VecDeque<LandedVote> = vec![
Lockout::new_with_confirmation_count(2, 5).into(),
Lockout::new_with_confirmation_count(15, 1).into(),
]
.into_iter()
.collect();
let current_epoch = vote_state1.current_epoch();
process_new_vote_state(
&mut vote_state1,
good_votes.clone(),
root,
None,
current_epoch,
0,
)
.unwrap();
assert_eq!(*vote_state1.votes(), good_votes);
}
#[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
#[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
fn test_filter_old_votes(mut vote_state: VoteStateHandler) {
let old_vote_slot = 1;
let vote = Vote::new(vec![old_vote_slot], Hash::default());
let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
assert_eq!(
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
Err(VoteError::VotesTooOldAllFiltered)
);
let vote_slot = 2;
let vote_slot_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == vote_slot)
.unwrap()
.1;
let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap();
assert_eq!(
vote_state
.votes()
.iter()
.map(|vote| vote.lockout)
.collect::<Vec<Lockout>>(),
vec![Lockout::new_with_confirmation_count(vote_slot, 1)]
);
}
fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
slots
.iter()
.rev()
.map(|x| (*x, Hash::new_unique()))
.collect()
}
fn build_vote_state(
target_version: VoteStateTargetVersion,
vote_slots: Vec<Slot>,
slot_hashes: &[(Slot, Hash)],
) -> VoteStateHandler {
let mut vote_state = match target_version {
VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
};
if !vote_slots.is_empty() {
let vote_hash = slot_hashes
.iter()
.find(|(slot, _hash)| slot == vote_slots.last().unwrap())
.unwrap()
.1;
let vote = Vote::new(vote_slots, vote_hash);
process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0)
.unwrap();
}
vote_state
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_empty(target_version: VoteStateTargetVersion) {
let empty_slot_hashes = build_slot_hashes(vec![]);
let empty_vote_state = build_vote_state(target_version, vec![], &empty_slot_hashes);
let mut tower_sync = TowerSync::from(vec![]);
assert_eq!(
check_and_filter_proposed_vote_state(
&empty_vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&empty_slot_hashes
),
Err(VoteError::EmptySlots),
);
let mut tower_sync = TowerSync::from(vec![(0, 1)]);
assert_eq!(
check_and_filter_proposed_vote_state(
&empty_vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&empty_slot_hashes
),
Err(VoteError::SlotsMismatch),
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_too_old(target_version: VoteStateTargetVersion) {
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
let latest_vote = 4;
let vote_state = build_vote_state(target_version, vec![1, 2, 3, latest_vote], &slot_hashes);
let mut tower_sync = TowerSync::from(vec![(latest_vote, 1)]);
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::VoteTooOld),
);
let earliest_slot_in_history = latest_vote + 2;
let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
let mut tower_sync = TowerSync::from(vec![(earliest_slot_in_history - 1, 1)]);
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::VoteTooOld),
);
}
fn run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version: VoteStateTargetVersion,
earliest_slot_in_history: Slot,
current_vote_state_slots: Vec<Slot>,
current_vote_state_root: Option<Slot>,
proposed_slots_and_lockouts: Vec<(Slot, u32)>,
proposed_root: Slot,
expected_root: Option<Slot>,
expected_vote_state: Vec<Lockout>,
) {
assert!(proposed_root < earliest_slot_in_history);
assert_eq!(
expected_root,
current_vote_state_slots
.iter()
.rev()
.find(|slot| **slot <= proposed_root)
.cloned()
);
let latest_slot_in_history = proposed_slots_and_lockouts
.last()
.unwrap()
.0
.max(earliest_slot_in_history);
let mut slot_hashes = build_slot_hashes(
(current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
.collect::<Vec<Slot>>(),
);
let mut vote_state =
build_vote_state(target_version, current_vote_state_slots, &slot_hashes);
vote_state.set_root_slot(current_vote_state_root);
slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
assert!(!proposed_slots_and_lockouts.is_empty());
let proposed_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == proposed_slots_and_lockouts.last().unwrap().0)
.unwrap()
.1;
let mut tower_sync = TowerSync::from(proposed_slots_and_lockouts);
tower_sync.hash = proposed_hash;
tower_sync.root = Some(proposed_root);
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(tower_sync.root, expected_root);
assert!(
do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync.clone(),).is_ok()
);
assert_eq!(vote_state.root_slot(), expected_root);
assert_eq!(
vote_state
.votes()
.iter()
.map(|vote| vote.lockout)
.collect::<Vec<Lockout>>(),
expected_vote_state,
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version: VoteStateTargetVersion,
) {
let earliest_slot_in_history = 5;
let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
let current_vote_state_root = None;
let proposed_slots_and_lockouts = vec![(5, 1)];
let proposed_root = 4;
let expected_root = Some(4);
let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
let earliest_slot_in_history = 5;
let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
let current_vote_state_root = Some(0);
let proposed_slots_and_lockouts = vec![(5, 1)];
let proposed_root = 4;
let expected_root = Some(4);
let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
let earliest_slot_in_history = 5;
let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
let current_vote_state_root = Some(0);
let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
let proposed_root = 3;
let expected_root = Some(3);
let expected_vote_state = vec![
Lockout::new_with_confirmation_count(4, 2),
Lockout::new_with_confirmation_count(5, 1),
];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
let earliest_slot_in_history = 5;
let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
let current_vote_state_root = Some(0);
let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
let proposed_root = 3;
let expected_root = Some(2);
let expected_vote_state = vec![
Lockout::new_with_confirmation_count(4, 2),
Lockout::new_with_confirmation_count(5, 1),
];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
let earliest_slot_in_history = 4;
let current_vote_state_slots: Vec<Slot> = vec![3, 4];
let current_vote_state_root = None;
let proposed_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
let proposed_root = 2;
let expected_root = None;
let expected_vote_state = vec![
Lockout::new_with_confirmation_count(3, 3),
Lockout::new_with_confirmation_count(4, 2),
Lockout::new_with_confirmation_count(5, 1),
];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
let earliest_slot_in_history = 4;
let current_vote_state_slots: Vec<Slot> = vec![];
let current_vote_state_root = None;
let proposed_slots_and_lockouts = vec![(5, 1)];
let proposed_root = 2;
let expected_root = None;
let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
run_test_check_and_filter_proposed_vote_state_older_than_history_root(
target_version,
earliest_slot_in_history,
current_vote_state_slots,
current_vote_state_root,
proposed_slots_and_lockouts,
proposed_root,
expected_root,
expected_vote_state,
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slots_not_ordered(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
let vote_state = build_vote_state(target_version, vec![1], &slot_hashes);
let vote_slot = 3;
let vote_slot_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == vote_slot)
.unwrap()
.1;
let mut tower_sync = TowerSync::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::SlotsNotOrdered),
);
let mut tower_sync = TowerSync::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::SlotsNotOrdered),
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
let mut vote_state = build_vote_state(target_version, vec![1, 2, 3, 4], &slot_hashes);
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 tower_sync = TowerSync::from(vec![
(1, 4),
(missing_older_than_history_slot, 2),
(vote_slot, 3),
]);
tower_sync.hash = vote_slot_hash;
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(
tower_sync
.clone()
.lockouts
.into_iter()
.collect::<Vec<Lockout>>(),
vec![
Lockout::new_with_confirmation_count(1, 4),
Lockout::new_with_confirmation_count(vote_slot, 3)
]
);
assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_older_than_history_slots_not_filtered(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![4]);
let mut vote_state = build_vote_state(target_version, vec![4], &slot_hashes);
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 tower_sync =
TowerSync::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
tower_sync.hash = vote_slot_hash;
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(tower_sync.lockouts.len(), 2);
assert_eq!(
tower_sync
.clone()
.lockouts
.into_iter()
.collect::<Vec<Lockout>>(),
vec![
Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
Lockout::new_with_confirmation_count(vote_slot, 2)
]
);
assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered_and_not_filtered(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![6]);
let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
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 tower_sync = TowerSync::from(vec![
(missing_older_than_history_slot, 4),
(existing_older_than_history_slot, 3),
(12, 2),
(vote_slot, 1),
]);
tower_sync.hash = vote_slot_hash;
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(tower_sync.lockouts.len(), 3);
assert_eq!(
tower_sync
.clone()
.lockouts
.into_iter()
.collect::<Vec<Lockout>>(),
vec![
Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
Lockout::new_with_confirmation_count(12, 2),
Lockout::new_with_confirmation_count(vote_slot, 1)
]
);
assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slot_not_on_fork(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
let missing_vote_slot = 3;
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 tower_sync = TowerSync::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::SlotsMismatch),
);
let missing_vote_slot = 7;
let mut tower_sync = TowerSync::from(vec![
(2, 5),
(4, 4),
(6, 3),
(missing_vote_slot, 2),
(vote_slot, 1),
]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::SlotsMismatch),
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_root_on_different_fork(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
let vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
let new_root = 3;
let vote_slot = 8;
assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
let vote_slot_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == vote_slot)
.unwrap()
.1;
let mut tower_sync = TowerSync::from(vec![(vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
tower_sync.root = Some(new_root);
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::RootOnDifferentFork),
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slot_newer_than_slot_history(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
let vote_slot_hash = Hash::new_unique();
let mut tower_sync = TowerSync::from(vec![(8, 2), (missing_vote_slot, 3)]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes
),
Err(VoteError::SlotsMismatch),
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slot_all_slot_hashes_in_update_ok(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
let mut vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
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 tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(
tower_sync
.clone()
.lockouts
.into_iter()
.collect::<Vec<Lockout>>(),
vec![
Lockout::new_with_confirmation_count(2, 4),
Lockout::new_with_confirmation_count(4, 3),
Lockout::new_with_confirmation_count(6, 2),
Lockout::new_with_confirmation_count(vote_slot, 1)
]
);
assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slot_some_slot_hashes_in_update_ok(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
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 tower_sync = TowerSync::from(vec![(4, 2), (vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
)
.unwrap();
assert_eq!(
tower_sync
.clone()
.lockouts
.into_iter()
.collect::<Vec<Lockout>>(),
vec![
Lockout::new_with_confirmation_count(4, 2),
Lockout::new_with_confirmation_count(vote_slot, 1)
]
);
assert_eq!(
do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,),
Err(VoteError::LockoutConflict)
);
}
#[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
#[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
fn test_check_and_filter_proposed_vote_state_slot_hash_mismatch(
target_version: VoteStateTargetVersion,
) {
let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
let vote_slot_hash = Hash::new_unique();
let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
tower_sync.hash = vote_slot_hash;
assert_eq!(
check_and_filter_proposed_vote_state(
&vote_state,
&mut tower_sync.lockouts,
&mut tower_sync.root,
tower_sync.hash,
&slot_hashes,
),
Err(VoteError::SlotHashMismatch),
);
}
#[test_case(0, true; "first slot")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
#[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
fn test_epoch_half_check(slot: Slot, expected_allowed: bool) {
let epoch_schedule = EpochSchedule::without_warmup();
assert_eq!(
is_commission_update_allowed(slot, &epoch_schedule),
expected_allowed
);
}
#[test]
fn test_warmup_epoch_half_check_with_warmup() {
let epoch_schedule = EpochSchedule::default();
let first_normal_slot = epoch_schedule.first_normal_slot;
assert!(is_commission_update_allowed(0, &epoch_schedule));
assert!(is_commission_update_allowed(
first_normal_slot - 1,
&epoch_schedule
));
}
#[test_case(0, true; "first slot")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
#[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
#[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
fn test_epoch_half_check_with_warmup(slot: Slot, expected_allowed: bool) {
let epoch_schedule = EpochSchedule::default();
let first_normal_slot = epoch_schedule.first_normal_slot;
assert_eq!(
is_commission_update_allowed(first_normal_slot.saturating_add(slot), &epoch_schedule),
expected_allowed
);
}
#[test]
fn test_create_v4_account_with_authorized() {
let node_pubkey = Pubkey::new_unique();
let authorized_voter = Pubkey::new_unique();
let authorized_withdrawer = Pubkey::new_unique();
let bls_pubkey_compressed = [42; 48];
let inflation_rewards_commission_bps = 10000;
let lamports = 100;
let vote_account = create_v4_account_with_authorized(
&node_pubkey,
&authorized_voter,
&authorized_withdrawer,
Some(bls_pubkey_compressed),
inflation_rewards_commission_bps,
lamports,
);
assert_eq!(vote_account.lamports(), lamports);
assert_eq!(vote_account.owner(), &id());
assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
let vote_state_v4 = VoteStateV4::deserialize(vote_account.data(), &node_pubkey).unwrap();
assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
assert_eq!(
vote_state_v4.authorized_voters,
AuthorizedVoters::new(0, authorized_voter)
);
assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
assert_eq!(
vote_state_v4.bls_pubkey_compressed,
Some(bls_pubkey_compressed)
);
assert_eq!(
vote_state_v4.inflation_rewards_commission_bps,
inflation_rewards_commission_bps
);
}
#[test]
fn test_update_validator_identity_syncs_block_revenue_collector() {
let vote_state =
vote_state_new_for_test(&solana_pubkey::new_rand(), VoteStateTargetVersion::V4);
let node_pubkey = *vote_state.node_pubkey();
let withdrawer_pubkey = *vote_state.authorized_withdrawer();
let serialized = vote_state.serialize();
let serialized_len = serialized.len();
let rent = Rent::default();
let lamports = rent.minimum_balance(serialized_len);
let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
vote_account.set_data_from_slice(&serialized);
let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
let mut transaction_context = TransactionContext::new(
vec![(id(), processor_account), (node_pubkey, vote_account)],
rent,
0,
0,
);
transaction_context
.configure_next_instruction_for_tests(
0,
vec![InstructionAccount::new(1, false, true)],
vec![],
)
.unwrap();
let instruction_context = transaction_context.get_next_instruction_context().unwrap();
let mut borrowed_account = instruction_context
.try_borrow_instruction_account(0)
.unwrap();
let new_node_pubkey = solana_pubkey::new_rand();
let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
.into_iter()
.collect();
update_validator_identity(
&mut borrowed_account,
VoteStateTargetVersion::V4,
&new_node_pubkey,
&signers,
)
.unwrap();
let vote_state =
VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
assert_eq!(vote_state.node_pubkey, new_node_pubkey);
assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
let new_node_pubkey = solana_pubkey::new_rand();
let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
.into_iter()
.collect();
update_validator_identity(
&mut borrowed_account,
VoteStateTargetVersion::V4,
&new_node_pubkey,
&signers,
)
.unwrap();
let vote_state =
VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
assert_eq!(vote_state.node_pubkey, new_node_pubkey);
assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
}
}