//! Vote state, vote program
//! Receive and processes votes from validators
pub use miraland_program::vote::state::{vote_state_versions::*, *};
use {
    log::*,
    miraland_metrics::datapoint_debug,
    serde_derive::{Deserialize, Serialize},
    miraland_program::vote::{error::VoteError, program::id},
    miraland_sdk::{
        account::{AccountSharedData, ReadableAccount, WritableAccount},
        clock::{Epoch, Slot, UnixTimestamp},
        epoch_schedule::EpochSchedule,
        feature_set::{self, FeatureSet},
        hash::Hash,
        instruction::InstructionError,
        pubkey::Pubkey,
        rent::Rent,
        slot_hashes::SlotHash,
        sysvar::clock::Clock,
        transaction_context::{
            BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
        },
    },
    std::{
        cmp::Ordering,
        collections::{HashSet, VecDeque},
        fmt::Debug,
    },
};

#[frozen_abi(digest = "2AuJFjx7SYrJ2ugCfH1jFh3Lr9UHMEPfKwwk1NcjqND1")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiEnumVisitor, AbiExample)]
pub enum VoteTransaction {
    Vote(Vote),
    VoteStateUpdate(VoteStateUpdate),
    #[serde(with = "serde_compact_vote_state_update")]
    CompactVoteStateUpdate(VoteStateUpdate),
}

impl VoteTransaction {
    pub fn slots(&self) -> Vec<Slot> {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.clone(),
            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.slots(),
            VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.slots(),
        }
    }

    pub fn slot(&self, i: usize) -> Slot {
        match self {
            VoteTransaction::Vote(vote) => vote.slots[i],
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts[i].slot()
            }
        }
    }

    pub fn len(&self) -> usize {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.len(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts.len()
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.is_empty(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts.is_empty()
            }
        }
    }

    pub fn hash(&self) -> Hash {
        match self {
            VoteTransaction::Vote(vote) => vote.hash,
            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
            VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.hash,
        }
    }

    pub fn timestamp(&self) -> Option<UnixTimestamp> {
        match self {
            VoteTransaction::Vote(vote) => vote.timestamp,
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.timestamp
            }
        }
    }

    pub fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
        match self {
            VoteTransaction::Vote(vote) => vote.timestamp = ts,
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.timestamp = ts
            }
        }
    }

    pub fn last_voted_slot(&self) -> Option<Slot> {
        match self {
            VoteTransaction::Vote(vote) => vote.last_voted_slot(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.last_voted_slot()
            }
        }
    }

    pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
        Some((self.last_voted_slot()?, self.hash()))
    }
}

impl From<Vote> for VoteTransaction {
    fn from(vote: Vote) -> Self {
        VoteTransaction::Vote(vote)
    }
}

impl From<VoteStateUpdate> for VoteTransaction {
    fn from(vote_state_update: VoteStateUpdate) -> Self {
        VoteTransaction::VoteStateUpdate(vote_state_update)
    }
}

// utility function, used by Stakes, tests
pub fn from<T: ReadableAccount>(account: &T) -> Option<VoteState> {
    VoteState::deserialize(account.data()).ok()
}

// utility function, used by Stakes, tests
pub fn to<T: WritableAccount>(versioned: &VoteStateVersions, account: &mut T) -> Option<()> {
    VoteState::serialize(versioned, account.data_as_mut_slice()).ok()
}

// Updates the vote account state with a new VoteState instance.  This is required temporarily during the
// upgrade of vote account state from V1_14_11 to Current.
fn set_vote_account_state(
    vote_account: &mut BorrowedAccount,
    vote_state: VoteState,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    // Only if vote_state_add_vote_latency feature is enabled should the new version of vote state be stored
    if feature_set.is_active(&feature_set::vote_state_add_vote_latency::id()) {
        // If the account is not large enough to store the vote state, then attempt a realloc to make it large enough.
        // The realloc can only proceed if the vote account has balance sufficient for rent exemption at the new size.
        if (vote_account.get_data().len() < VoteStateVersions::vote_state_size_of(true))
            && (!vote_account
                .is_rent_exempt_at_data_length(VoteStateVersions::vote_state_size_of(true))
                || vote_account
                    .set_data_length(VoteStateVersions::vote_state_size_of(true), feature_set)
                    .is_err())
        {
            // Account cannot be resized to the size of a vote state as it will not be rent exempt, or failed to be
            // resized for other reasons.  So store the V1_14_11 version.
            return vote_account.set_state(
                &VoteStateVersions::V1_14_11(Box::new(VoteState1_14_11::from(vote_state))),
                feature_set,
            );
        }
        // Vote account is large enough to store the newest version of vote state
        vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
    // Else when the vote_state_add_vote_latency feature is not enabled, then the V1_14_11 version is stored
    } else {
        vote_account.set_state(
            &VoteStateVersions::V1_14_11(Box::new(VoteState1_14_11::from(vote_state))),
            feature_set,
        )
    }
}

fn check_update_vote_state_slots_are_valid(
    vote_state: &VoteState,
    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) = vote_state.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
    let original_proposed_root = vote_state_update.root;
    if let Some(new_proposed_root) = original_proposed_root {
        // If the new proposed root `R` 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 latest vote in the current vote that's less than R.
        if earliest_slot_hash_in_history > new_proposed_root {
            vote_state_update.root = vote_state.root_slot;
            let mut prev_slot = Slot::MAX;
            let current_root = vote_state_update.root;
            for vote in vote_state.votes.iter().rev() {
                let is_slot_bigger_than_root = current_root
                    .map(|current_root| vote.slot() > current_root)
                    .unwrap_or(true);
                // Ensure we're iterating from biggest to smallest vote in the
                // current vote state
                assert!(vote.slot() < prev_slot && is_slot_bigger_than_root);
                if vote.slot() <= new_proposed_root {
                    vote_state_update.root = Some(vote.slot());
                    break;
                }
                prev_slot = vote.slot();
            }
        }
    }

    // Index into the new proposed vote state's slots, starting with the root if it exists then
    // we use this mutable root to fold checking the root slot into the below loop
    // for performance
    let mut root_to_check = vote_state_update.root;
    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 = if let Some(root) = root_to_check {
            root
        } else {
            vote_state_update.lockouts[vote_state_update_index].slot()
        };
        if root_to_check.is_none()
            && vote_state_update_index > 0
            && proposed_vote_slot
                <= vote_state_update.lockouts[vote_state_update_index.checked_sub(1).expect(
                    "`vote_state_update_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;

        // 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 !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
                        // 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);
                    }
                    if let Some(new_proposed_root) = root_to_check {
                        // 1. Because `root_to_check.is_some()`, then we know that
                        // we haven't checked the root yet in this loop, so
                        // `proposed_vote_slot` == `new_proposed_root` == `vote_state_update.root`.
                        assert_eq!(new_proposed_root, proposed_vote_slot);
                        // 2. We know from the assert earlier in the function that
                        // `proposed_vote_slot < earliest_slot_hash_in_history`,
                        // so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history`.
                        assert!(new_proposed_root < earliest_slot_hash_in_history);
                        root_to_check = None;
                    } else {
                        vote_state_update_index = vote_state_update_index.checked_add(1).expect(
                            "`vote_state_update_index` is bounded by `MAX_LOCKOUT_HISTORY` when `proposed_vote_slot` is too old to be in SlotHashes history",
                        );
                    }
                    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.
                    if root_to_check.is_some() {
                        return Err(VoteError::RootOnDifferentFork);
                    } else {
                        return Err(VoteError::SlotsMismatch);
                    }
                }
            }
            Ordering::Greater => {
                // Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
                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 => {
                // Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
                // in `vote_state_update.lockouts` and continue. If we were checking the root,
                // start checking the vote state instead.
                if root_to_check.is_some() {
                    root_to_check = None;
                } else {
                    vote_state_update_index = vote_state_update_index
                        .checked_add(1)
                        .expect("`vote_state_update_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 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 {} {}",
            vote_state.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 = filter_votes_index.checked_add(1).unwrap();
            false
        } else {
            true
        };

        vote_state_update_index = vote_state_update_index
            .checked_add(1)
            .expect("`vote_state_update_index` is bounded by `MAX_LOCKOUT_HISTORY` when filtering out irrelevant votes");
        should_retain
    });

    Ok(())
}

fn check_slots_are_valid(
    vote_state: &VoteState,
    vote_slots: &[Slot],
    vote_hash: &Hash,
    slot_hashes: &[(Slot, Hash)],
) -> Result<(), VoteError> {
    // index into the vote's slots, starting at the oldest
    // slot
    let mut i = 0;

    // index into the slot_hashes, starting at the oldest known
    // slot hash
    let mut j = slot_hashes.len();

    // Note:
    //
    // 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
    // pushed to the back).
    //
    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
    // the oldest/smallest vote
    while i < vote_slots.len() && j > 0 {
        // 1) increment `i` to find the smallest slot `s` in `vote_slots`
        // where `s` >= `last_voted_slot`
        if vote_state
            .last_voted_slot()
            .map_or(false, |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;
        }

        // 2) Find the hash for this slot `s`.
        if vote_slots[i] != slot_hashes[j.checked_sub(1).expect("`j` is positive")].0 {
            // Decrement `j` to find newer slots
            j = j
                .checked_sub(1)
                .expect("`j` is positive when finding newer slots");
            continue;
        }

        // 3) Once the hash for `s` is found, bump `s` to the next slot
        // in `vote_slots` and 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() {
        // This means we never made it to steps 2) or 3) above, otherwise
        // `j` would have been decremented at least once. This means
        // there are not slots in `vote_slots` greater than `last_voted_slot`
        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() {
        // This means there existed some slot for which we couldn't find
        // a matching slot hash in step 2)
        info!(
            "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
            vote_state.node_pubkey, vote_slots, slot_hashes,
        );
        inc_new_counter_info!("dropped-vote-slot", 1);
        return Err(VoteError::SlotsMismatch);
    }
    if &slot_hashes[j].1 != vote_hash {
        // This means the newest slot in the `vote_slots` has a match that
        // doesn't match the expected hash for that slot on this
        // fork
        warn!(
            "{} dropped vote slots {:?} failed to match hash {} {}",
            vote_state.node_pubkey, vote_slots, vote_hash, slot_hashes[j].1
        );
        inc_new_counter_info!("dropped-vote-hash", 1);
        return Err(VoteError::SlotHashMismatch);
    }
    Ok(())
}

//Ensure `check_update_vote_state_slots_are_valid(&)` runs on the slots in `new_state`
// before `process_new_vote_state()` is called

// This function should guarantee the following about `new_state`:
//
// 1) It's well ordered, i.e. the slots are sorted from smallest to largest,
// and the confirmations sorted from largest to smallest.
// 2) Confirmations `c` on any vote slot satisfy `0 < c <= MAX_LOCKOUT_HISTORY`
// 3) Lockouts are not expired by consecutive votes, i.e. for every consecutive
// `v_i`, `v_{i + 1}` satisfy `v_i.last_locked_out_slot() >= v_{i + 1}`.

// We also guarantee that compared to the current vote state, `new_state`
// introduces no rollback. This means:
//
// 1) The last slot in `new_state` is always greater than any slot in the
// current vote state.
//
// 2) From 1), this means that for every vote `s` in the current state:
//    a) If there exists an `s'` in `new_state` where `s.slot == s'.slot`, then
//    we must guarantee `s.confirmations <= s'.confirmations`
//
//    b) If there does not exist any such `s'` in `new_state`, then there exists
//    some `t` that is the smallest vote in `new_state` where `t.slot > s.slot`.
//    `t` must have expired/popped off s', so it must be guaranteed that
//    `s.last_locked_out_slot() < t`.

// Note these two above checks do not guarantee that the vote state being submitted
// is a vote state that could have been created by iteratively building a tower
// by processing one vote at a time. For instance, the tower:
//
// { slot 0, confirmations: 31 }
// { slot 1, confirmations: 30 }
//
// is a legal tower that could be submitted on top of a previously empty tower. However,
// there is no way to create this tower from the iterative process, because slot 1 would
// have to have at least one other slot on top of it, even if the first 30 votes were all
// popped off.
pub fn process_new_vote_state(
    vote_state: &mut VoteState,
    mut new_state: VecDeque<LandedVote>,
    new_root: Option<Slot>,
    timestamp: Option<i64>,
    epoch: Epoch,
    current_slot: Slot,
    feature_set: Option<&FeatureSet>,
) -> 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;

    // Check that all the votes in the new proposed state are:
    // 1) Strictly sorted from oldest to newest vote
    // 2) The confirmations are strictly decreasing
    // 3) Not zero confirmation votes
    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
                &&
                // This check is necessary because
                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L120
                // always sets a root for even empty towers, which is then hard unwrapped here
                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L776
                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);
    }

    // Find the first vote in the current vote state for a slot greater
    // than the new proposed root
    let mut current_vote_state_index: usize = 0;
    let mut new_vote_state_index = 0;

    // Accumulate credits earned by newly rooted slots.  The behavior changes with timely_vote_credits: prior to
    // this feature, there was a bug that counted a new root slot as 1 credit even if it had never been voted on.
    // timely_vote_credits fixes this bug by only awarding credits for slots actually voted on and finalized.
    let timely_vote_credits = feature_set.map_or(false, |f| {
        f.is_active(&feature_set::timely_vote_credits::id())
    });
    let mut earned_credits = if timely_vote_credits { 0_u64 } else { 1_u64 };

    if let Some(new_root) = new_root {
        for current_vote in &vote_state.votes {
            // Find the first vote in the current vote state for a slot greater
            // than the new proposed root
            if current_vote.slot() <= new_root {
                if timely_vote_credits || (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;
        }
    }

    // For any slots newly added to the new vote state, the vote latency of that slot is not provided by the
    // VoteStateUpdate instruction contents, but instead is computed from the actual latency of the VoteStateUpdate
    // instruction. This prevents other validators from manipulating their own vote latencies within their vote states
    // and forcing the rest of the cluster to accept these possibly fraudulent latency values.  If the
    // timly_vote_credits feature is not enabled then vote latency is set to 0 for new votes.
    //
    // For any slot that is in both the new state and the current state, the vote latency of the new state is taken
    // from the current state.
    //
    // Thus vote latencies are set here for any newly vote-on slots when a VoteStateUpdate instruction is received.
    // They are copied into the new vote state after every VoteStateUpdate for already voted-on slots.
    // And when voted-on slots are rooted, the vote latencies stored in the vote state of all the rooted slots is used
    // to compute credits earned.
    // All validators compute the same vote latencies because all process the same VoteStateUpdate instruction at the
    // same slot, and the only time vote latencies are ever computed is at the time that their slot is first voted on;
    // after that, the latencies are retained unaltered until the slot is rooted.

    // All the votes in our current vote state that are missing from the new vote state
    // must have been expired by later votes. Check that the lockouts match this assumption.
    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];

        // If the current slot is less than the new proposed slot, then the
        // new slot must have popped off the old slot, so check that the
        // lockouts are corrects.
        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 => {
                // The new vote state should never have less lockout than
                // the previous vote state for the same slot
                if new_vote.confirmation_count() < current_vote.confirmation_count() {
                    return Err(VoteError::ConfirmationRollBack);
                }

                // Copy the vote slot latency in from the current state to the new state
                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");
            }
        }
    }

    // `new_vote_state` passed all the checks, finalize the change by rewriting
    // our state.

    // Now set the vote latencies on new slots not in the current state.  New slots not in the current vote state will
    // have had their latency initialized to 0 by the above loop.  Those will now be updated to their actual latency.
    // If the timely_vote_credits feature is not enabled, then the latency is left as 0 for such slots, which will
    // result in 1 credit per slot when credits are calculated at the time that the slot is rooted.
    if timely_vote_credits {
        for new_vote in new_state.iter_mut() {
            if new_vote.latency == 0 {
                new_vote.latency = VoteState::compute_vote_latency(new_vote.slot(), current_slot);
            }
        }
    }

    if vote_state.root_slot != new_root {
        // Award vote credits based on the number of slots that were voted on and have reached finality
        // For each finalized slot, there was one voted-on slot in the new vote state that was responsible for
        // finalizing it. Each of those votes is awarded 1 credit.
        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.root_slot = new_root;
    vote_state.votes = new_state;

    Ok(())
}

pub fn process_vote_unfiltered(
    vote_state: &mut VoteState,
    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 VoteState,
    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,
    )
}

/// "unchecked" functions used by tests and Tower
pub fn process_vote_unchecked(vote_state: &mut VoteState, 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(vote_state: &mut VoteState, slots: &[Slot]) {
    for slot in slots {
        process_slot_vote_unchecked(vote_state, *slot);
    }
}

pub fn process_slot_vote_unchecked(vote_state: &mut VoteState, slot: Slot) {
    let _ = process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
}

/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
/// but will implicitly withdraw authorization from the previously authorized
/// key
pub fn authorize<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    authorized: &Pubkey,
    vote_authorize: VoteAuthorize,
    signers: &HashSet<Pubkey, S>,
    clock: &Clock,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    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)
                    .expect("epoch should be much less than u64::MAX"),
                |epoch_authorized_voter| {
                    // current authorized withdrawer or authorized voter must say "yay"
                    if authorized_withdrawer_signer {
                        Ok(())
                    } else {
                        verify_authorized_signer(&epoch_authorized_voter, signers)
                    }
                },
            )?;
        }
        VoteAuthorize::Withdrawer => {
            // current authorized withdrawer must say "yay"
            verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
            vote_state.authorized_withdrawer = *authorized;
        }
    }

    set_vote_account_state(vote_account, vote_state, feature_set)
}

/// Update the node_pubkey, requires signature of the authorized voter
pub fn update_validator_identity<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    node_pubkey: &Pubkey,
    signers: &HashSet<Pubkey, S>,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    // current authorized withdrawer must say "yay"
    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;

    // new node must say "yay"
    verify_authorized_signer(node_pubkey, signers)?;

    vote_state.node_pubkey = *node_pubkey;

    set_vote_account_state(vote_account, vote_state, feature_set)
}

/// Update the vote account's commission
pub fn update_commission<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    commission: u8,
    signers: &HashSet<Pubkey, S>,
    epoch_schedule: &EpochSchedule,
    clock: &Clock,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    // Decode vote state only once, and only if needed
    let mut vote_state = None;

    let enforce_commission_update_rule =
        if feature_set.is_active(&feature_set::allow_commission_decrease_at_any_time::id()) {
            if let Ok(decoded_vote_state) = vote_account.get_state::<VoteStateVersions>() {
                vote_state = Some(decoded_vote_state.convert_to_current());
                is_commission_increase(vote_state.as_ref().unwrap(), commission)
            } else {
                true
            }
        } else {
            true
        };

    #[allow(clippy::collapsible_if)]
    if enforce_commission_update_rule
        && feature_set
            .is_active(&feature_set::commission_updates_only_allowed_in_first_half_of_epoch::id())
    {
        if !is_commission_update_allowed(clock.slot, epoch_schedule) {
            return Err(VoteError::CommissionUpdateTooLate.into());
        }
    }

    let mut vote_state = match vote_state {
        Some(vote_state) => vote_state,
        None => vote_account
            .get_state::<VoteStateVersions>()?
            .convert_to_current(),
    };

    // current authorized withdrawer must say "yay"
    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;

    vote_state.commission = commission;

    set_vote_account_state(vote_account, vote_state, feature_set)
}

/// Given a proposed new commission, returns true if this would be a commission increase, false otherwise
pub fn is_commission_increase(vote_state: &VoteState, commission: u8) -> bool {
    commission > vote_state.commission
}

/// Given the current slot and epoch schedule, determine if a commission change
/// is allowed
pub fn is_commission_update_allowed(slot: Slot, epoch_schedule: &EpochSchedule) -> bool {
    // always allowed during warmup epochs
    if let Some(relative_slot) = slot
        .saturating_sub(epoch_schedule.first_normal_slot)
        .checked_rem(epoch_schedule.slots_per_epoch)
    {
        // allowed up to the midpoint of the epoch
        relative_slot.saturating_mul(2) <= epoch_schedule.slots_per_epoch
    } else {
        // no slots per epoch, just allow it, even though this should never happen
        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)
    }
}

/// Withdraw funds from the vote account
pub fn withdraw<S: std::hash::BuildHasher>(
    transaction_context: &TransactionContext,
    instruction_context: &InstructionContext,
    vote_account_index: IndexOfAccount,
    lamports: u64,
    to_account_index: IndexOfAccount,
    signers: &HashSet<Pubkey, S>,
    rent_sysvar: &Rent,
    clock: &Clock,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_account = instruction_context
        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
    let vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    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;
                // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits
                // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least
                // one full epoch since the validator has received credits.
                current_epoch.saturating_sub(*last_epoch_with_credits) < 2
            })
            .unwrap_or(false);

        if reject_active_vote_account_close {
            datapoint_debug!("vote-account-close", ("reject-active", 1, i64));
            return Err(VoteError::ActiveVoteAccountClose.into());
        } else {
            // Deinitialize upon zero-balance
            datapoint_debug!("vote-account-close", ("allow", 1, i64));
            set_vote_account_state(&mut vote_account, VoteState::default(), feature_set)?;
        }
    } 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, feature_set)?;
    drop(vote_account);
    let mut to_account = instruction_context
        .try_borrow_instruction_account(transaction_context, to_account_index)?;
    to_account.checked_add_lamports(lamports, feature_set)?;
    Ok(())
}

/// Initialize the vote_state for a vote account
/// Assumes that the account is being init as part of a account creation or balance transfer and
/// that the transaction must be signed by the staker's keys
pub fn initialize_account<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    vote_init: &VoteInit,
    signers: &HashSet<Pubkey, S>,
    clock: &Clock,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    if vote_account.get_data().len()
        != VoteStateVersions::vote_state_size_of(
            feature_set.is_active(&feature_set::vote_state_add_vote_latency::id()),
        )
    {
        return Err(InstructionError::InvalidAccountData);
    }
    let versioned = vote_account.get_state::<VoteStateVersions>()?;

    if !versioned.is_uninitialized() {
        return Err(InstructionError::AccountAlreadyInitialized);
    }

    // node must agree to accept this vote account
    verify_authorized_signer(&vote_init.node_pubkey, signers)?;

    set_vote_account_state(vote_account, VoteState::new(vote_init, clock), feature_set)
}

fn verify_and_get_vote_state<S: std::hash::BuildHasher>(
    vote_account: &BorrowedAccount,
    clock: &Clock,
    signers: &HashSet<Pubkey, S>,
) -> Result<VoteState, InstructionError> {
    let versioned = vote_account.get_state::<VoteStateVersions>()?;

    if versioned.is_uninitialized() {
        return Err(InstructionError::UninitializedAccount);
    }

    let mut vote_state = versioned.convert_to_current();
    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
    verify_authorized_signer(&authorized_voter, signers)?;

    Ok(vote_state)
}

pub fn process_vote_with_account<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    slot_hashes: &[SlotHash],
    clock: &Clock,
    vote: &Vote,
    signers: &HashSet<Pubkey, S>,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state = verify_and_get_vote_state(vote_account, clock, 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))?;
    }
    set_vote_account_state(vote_account, vote_state, feature_set)
}

pub fn process_vote_state_update<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    slot_hashes: &[SlotHash],
    clock: &Clock,
    vote_state_update: VoteStateUpdate,
    signers: &HashSet<Pubkey, S>,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
    do_process_vote_state_update(
        &mut vote_state,
        slot_hashes,
        clock.epoch,
        clock.slot,
        vote_state_update,
        Some(feature_set),
    )?;
    set_vote_account_state(vote_account, vote_state, feature_set)
}

pub fn do_process_vote_state_update(
    vote_state: &mut VoteState,
    slot_hashes: &[SlotHash],
    epoch: u64,
    slot: u64,
    mut vote_state_update: VoteStateUpdate,
    feature_set: Option<&FeatureSet>,
) -> Result<(), VoteError> {
    check_update_vote_state_slots_are_valid(vote_state, &mut vote_state_update, 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,
        feature_set,
    )
}

// This function is used:
// a. In many tests.
// b. In the genesis tool that initializes a cluster to create the bootstrap validator.
// c. In the ledger tool when creating bootstrap vote accounts.
pub fn create_account_with_authorized(
    node_pubkey: &Pubkey,
    authorized_voter: &Pubkey,
    authorized_withdrawer: &Pubkey,
    commission: u8,
    lamports: u64,
) -> AccountSharedData {
    let mut vote_account = AccountSharedData::new(lamports, VoteState::size_of(), &id());

    let vote_state = VoteState::new(
        &VoteInit {
            node_pubkey: *node_pubkey,
            authorized_voter: *authorized_voter,
            authorized_withdrawer: *authorized_withdrawer,
            commission,
        },
        &Clock::default(),
    );

    VoteState::serialize(
        &VoteStateVersions::Current(Box::new(vote_state)),
        vote_account.data_as_mut_slice(),
    )
    .unwrap();

    vote_account
}

// create_account() should be removed, use create_account_with_authorized() instead
pub fn create_account(
    vote_pubkey: &Pubkey,
    node_pubkey: &Pubkey,
    commission: u8,
    lamports: u64,
) -> AccountSharedData {
    create_account_with_authorized(node_pubkey, vote_pubkey, vote_pubkey, commission, lamports)
}

#[cfg(test)]
mod tests {
    use {
        super::*,
        crate::vote_state,
        assert_matches::assert_matches,
        miraland_sdk::{
            account::AccountSharedData, account_utils::StateMut, clock::DEFAULT_SLOTS_PER_EPOCH,
            hash::hash, transaction_context::InstructionAccount,
        },
        std::cell::RefCell,
        test_case::test_case,
    };

    const MAX_RECENT_VOTES: usize = 16;

    fn vote_state_new_for_test(auth_pubkey: &Pubkey) -> VoteState {
        VoteState::new(
            &VoteInit {
                node_pubkey: miraland_sdk::pubkey::new_rand(),
                authorized_voter: *auth_pubkey,
                authorized_withdrawer: *auth_pubkey,
                commission: 0,
            },
            &Clock::default(),
        )
    }

    fn create_test_account() -> (Pubkey, RefCell<AccountSharedData>) {
        let rent = Rent::default();
        let balance = VoteState::get_rent_exempt_reserve(&rent);
        let vote_pubkey = miraland_sdk::pubkey::new_rand();
        (
            vote_pubkey,
            RefCell::new(vote_state::create_account(
                &vote_pubkey,
                &miraland_sdk::pubkey::new_rand(),
                0,
                balance,
            )),
        )
    }

    #[test]
    fn test_vote_state_upgrade_from_1_14_11() {
        let mut feature_set = FeatureSet::default();

        // Create an initial vote account that is sized for the 1_14_11 version of vote state, and has only the
        // required lamports for rent exempt minimum at that size
        let node_pubkey = miraland_sdk::pubkey::new_rand();
        let withdrawer_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = VoteState::new(
            &VoteInit {
                node_pubkey,
                authorized_voter: withdrawer_pubkey,
                authorized_withdrawer: withdrawer_pubkey,
                commission: 10,
            },
            &Clock::default(),
        );
        // Simulate prior epochs completed with credits and each setting a new authorized voter
        vote_state.increment_credits(0, 100);
        assert_eq!(
            vote_state
                .set_new_authorized_voter(&miraland_sdk::pubkey::new_rand(), 0, 1, |_pubkey| Ok(())),
            Ok(())
        );
        vote_state.increment_credits(1, 200);
        assert_eq!(
            vote_state
                .set_new_authorized_voter(&miraland_sdk::pubkey::new_rand(), 1, 2, |_pubkey| Ok(())),
            Ok(())
        );
        vote_state.increment_credits(2, 300);
        assert_eq!(
            vote_state
                .set_new_authorized_voter(&miraland_sdk::pubkey::new_rand(), 2, 3, |_pubkey| Ok(())),
            Ok(())
        );
        // Simulate votes having occurred
        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 version1_14_11_serialized = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(
            VoteState1_14_11::from(vote_state.clone()),
        )))
        .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);

        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
        // vote account that was just created
        let processor_account = AccountSharedData::new(0, 0, &miraland_sdk::native_loader::id());
        let transaction_context = TransactionContext::new(
            vec![(id(), processor_account), (node_pubkey, vote_account)],
            rent.clone(),
            0,
            0,
        );
        let mut instruction_context = InstructionContext::default();
        instruction_context.configure(
            &[0],
            &[InstructionAccount {
                index_in_transaction: 1,
                index_in_caller: 1,
                index_in_callee: 0,
                is_signer: false,
                is_writable: true,
            }],
            &[],
        );

        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
        // state
        let mut borrowed_account = instruction_context
            .try_borrow_instruction_account(&transaction_context, 0)
            .unwrap();

        // Ensure that the vote state started out at 1_14_11
        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));

        // Convert the vote state to current as would occur during vote instructions
        let converted_vote_state = vote_state_version.convert_to_current();

        // Check to make sure that the vote_state is unchanged
        assert!(vote_state == converted_vote_state);

        let vote_state = converted_vote_state;

        // Now re-set the vote account state; because the feature is not enabled, the old 1_14_11 format should be
        // written out
        assert_eq!(
            set_vote_account_state(&mut borrowed_account, vote_state.clone(), &feature_set),
            Ok(())
        );
        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));

        // Convert the vote state to current as would occur during vote instructions
        let converted_vote_state = vote_state_version.convert_to_current();

        // Check to make sure that the vote_state is unchanged
        assert_eq!(vote_state, converted_vote_state);

        let vote_state = converted_vote_state;

        // Test that when the feature is enabled, if the vote account does not have sufficient lamports to realloc,
        // the old vote state is written out
        feature_set.activate(&feature_set::vote_state_add_vote_latency::id(), 1);
        assert_eq!(
            set_vote_account_state(&mut borrowed_account, vote_state.clone(), &feature_set),
            Ok(())
        );
        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));

        // Convert the vote state to current as would occur during vote instructions
        let converted_vote_state = vote_state_version.convert_to_current();

        // Check to make sure that the vote_state is unchanged
        assert_eq!(vote_state, converted_vote_state);

        let vote_state = converted_vote_state;

        // Test that when the feature is enabled, if the vote account does have sufficient lamports, the
        // new vote state is written out
        assert_eq!(
            borrowed_account.set_lamports(rent.minimum_balance(VoteState::size_of()), &feature_set),
            Ok(())
        );
        assert_eq!(
            set_vote_account_state(&mut borrowed_account, vote_state.clone(), &feature_set),
            Ok(())
        );
        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
        assert_matches!(vote_state_version, VoteStateVersions::Current(_));

        // Convert the vote state to current as would occur during vote instructions
        let converted_vote_state = vote_state_version.convert_to_current();

        // Check to make sure that the vote_state is unchanged
        assert_eq!(vote_state, converted_vote_state);
    }

    #[test]
    fn test_vote_lockout() {
        let (_vote_pubkey, vote_account) = create_test_account();

        let mut vote_state: VoteState =
            StateMut::<VoteStateVersions>::state(&*vote_account.borrow())
                .unwrap()
                .convert_to_current();

        for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
            process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
        }

        // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
        assert_eq!(vote_state.votes.len(), MAX_LOCKOUT_HISTORY);
        assert_eq!(vote_state.root_slot, Some(0));
        check_lockouts(&vote_state);

        // One more vote that confirms the entire stack,
        // the root_slot should change to the
        // second vote
        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);

        // Expire everything except the first vote
        let slot = vote_state
            .votes
            .front()
            .unwrap()
            .lockout
            .last_locked_out_slot();
        process_slot_vote_unchecked(&mut vote_state, slot);
        // First vote and new vote are both stored for a total of 2 votes
        assert_eq!(vote_state.votes.len(), 2);
    }

    #[test]
    fn test_update_commission() {
        let node_pubkey = Pubkey::new_unique();
        let withdrawer_pubkey = Pubkey::new_unique();
        let clock = Clock::default();
        let vote_state = VoteState::new(
            &VoteInit {
                node_pubkey,
                authorized_voter: withdrawer_pubkey,
                authorized_withdrawer: withdrawer_pubkey,
                commission: 10,
            },
            &clock,
        );

        let serialized =
            bincode::serialize(&VoteStateVersions::Current(Box::new(vote_state.clone()))).unwrap();
        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);

        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
        // vote account that was just created
        let processor_account = AccountSharedData::new(0, 0, &miraland_sdk::native_loader::id());
        let transaction_context = TransactionContext::new(
            vec![(id(), processor_account), (node_pubkey, vote_account)],
            rent,
            0,
            0,
        );
        let mut instruction_context = InstructionContext::default();
        instruction_context.configure(
            &[0],
            &[InstructionAccount {
                index_in_transaction: 1,
                index_in_caller: 1,
                index_in_callee: 0,
                is_signer: false,
                is_writable: true,
            }],
            &[],
        );

        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
        // state
        let mut borrowed_account = instruction_context
            .try_borrow_instruction_account(&transaction_context, 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 mut feature_set = FeatureSet::default();
        feature_set.activate(
            &feature_set::commission_updates_only_allowed_in_first_half_of_epoch::id(),
            1,
        );

        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();

        // Increase commission in first half of epoch -- allowed
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            10
        );
        assert_matches!(
            update_commission(
                &mut borrowed_account,
                11,
                &signers,
                &epoch_schedule,
                &first_half_clock,
                &feature_set
            ),
            Ok(())
        );
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            11
        );

        // Increase commission in second half of epoch -- disallowed
        assert_matches!(
            update_commission(
                &mut borrowed_account,
                12,
                &signers,
                &epoch_schedule,
                &second_half_clock,
                &feature_set
            ),
            Err(_)
        );
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            11
        );

        // Decrease commission in first half of epoch -- allowed
        assert_matches!(
            update_commission(
                &mut borrowed_account,
                10,
                &signers,
                &epoch_schedule,
                &first_half_clock,
                &feature_set
            ),
            Ok(())
        );
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            10
        );

        // Decrease commission in second half of epoch -- disallowed because feature_set does not allow it
        assert_matches!(
            update_commission(
                &mut borrowed_account,
                9,
                &signers,
                &epoch_schedule,
                &second_half_clock,
                &feature_set
            ),
            Err(_)
        );
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            10
        );

        // Decrease commission in second half of epoch -- allowed because feature_set allows it
        feature_set.activate(&feature_set::allow_commission_decrease_at_any_time::id(), 1);
        assert_matches!(
            update_commission(
                &mut borrowed_account,
                9,
                &signers,
                &epoch_schedule,
                &second_half_clock,
                &feature_set
            ),
            Ok(())
        );
        assert_eq!(
            borrowed_account
                .get_state::<VoteStateVersions>()
                .unwrap()
                .convert_to_current()
                .commission,
            9
        );
    }

    #[test]
    fn test_vote_double_lockout_after_expiration() {
        let voter_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        for i in 0..3 {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }

        check_lockouts(&vote_state);

        // Expire the third vote (which was a vote for slot 2). The height of the
        // vote stack is unchanged, so none of the previous votes should have
        // doubled in lockout
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
        check_lockouts(&vote_state);

        // Vote again, this time the vote stack depth increases, so the votes should
        // double for everybody
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
        check_lockouts(&vote_state);

        // Vote again, this time the vote stack depth increases, so the votes should
        // double for everybody
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
        check_lockouts(&vote_state);
    }

    #[test]
    fn test_expire_multiple_votes() {
        let voter_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        for i in 0..3 {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }

        assert_eq!(vote_state.votes[0].confirmation_count(), 3);

        // Expire the second and third votes
        let expire_slot = vote_state.votes[1].slot() + vote_state.votes[1].lockout.lockout() + 1;
        process_slot_vote_unchecked(&mut vote_state, expire_slot);
        assert_eq!(vote_state.votes.len(), 2);

        // Check that the old votes expired
        assert_eq!(vote_state.votes[0].slot(), 0);
        assert_eq!(vote_state.votes[1].slot(), expire_slot);

        // Process one more vote
        process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);

        // Confirmation count for the older first vote should remain unchanged
        assert_eq!(vote_state.votes[0].confirmation_count(), 3);

        // The later votes should still have increasing confirmation counts
        assert_eq!(vote_state.votes[1].confirmation_count(), 2);
        assert_eq!(vote_state.votes[2].confirmation_count(), 1);
    }

    #[test]
    fn test_vote_credits() {
        let voter_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        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]
    fn test_duplicate_vote() {
        let voter_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);
        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]
    fn test_nth_recent_lockout() {
        let voter_pubkey = miraland_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);
        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: &VoteState) {
        for (i, vote) in vote_state.votes.iter().enumerate() {
            let num_votes = vote_state
                .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: &VoteState) -> Vec<Vote> {
        let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
        (start..vote_state.votes.len())
            .map(|i| {
                Vote::new(
                    vec![vote_state.votes.get(i).unwrap().slot()],
                    Hash::default(),
                )
            })
            .collect()
    }

    /// check that two accounts with different data can be brought to the same state with one vote submission
    #[test]
    fn test_process_missed_votes() {
        let account_a = miraland_sdk::pubkey::new_rand();
        let mut vote_state_a = vote_state_new_for_test(&account_a);
        let account_b = miraland_sdk::pubkey::new_rand();
        let mut vote_state_b = vote_state_new_for_test(&account_b);

        // process some votes on account a
        (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));

        // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
        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]
    fn test_process_vote_skips_old_vote() {
        let mut vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_vote_empty_slot_hashes() {
        let vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
            Err(VoteError::VoteTooOld)
        );
    }

    #[test]
    fn test_check_slots_are_valid_new_vote() {
        let vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_bad_hash() {
        let vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_bad_slot() {
        let vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_duplicate_vote() {
        let mut vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_next_vote() {
        let mut vote_state = VoteState::default();

        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]
    fn test_check_slots_are_valid_next_vote_only() {
        let mut vote_state = VoteState::default();

        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]
    fn test_process_vote_empty_slots() {
        let mut vote_state = VoteState::default();

        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 VoteState,
        new_state: VecDeque<Lockout>,
        new_root: Option<Slot>,
        timestamp: Option<i64>,
        epoch: Epoch,
        feature_set: Option<&FeatureSet>,
    ) -> Result<(), VoteError> {
        process_new_vote_state(
            vote_state,
            new_state.into_iter().map(LandedVote::from).collect(),
            new_root,
            timestamp,
            epoch,
            0,
            feature_set,
        )
    }

    // Test vote credit updates after "one credit per slot" feature is enabled
    #[test]
    fn test_vote_state_update_increment_credits() {
        // Create a new Votestate
        let mut vote_state = VoteState::new(&VoteInit::default(), &Clock::default());

        // Test data: a sequence of groups of votes to simulate having been cast, after each group a vote
        // state update is compared to "normal" vote processing to ensure that credits are earned equally
        let test_vote_groups: Vec<Vec<Slot>> = vec![
            // Initial set of votes that don't dequeue any slots, so no credits earned
            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,
            ],
            // Now a single vote which should result in the first root and first credit earned
            vec![32],
            // Now another vote, should earn one credit
            vec![33],
            // Two votes in sequence
            vec![34, 35],
            // 3 votes in sequence
            vec![36, 37, 38],
            // 30 votes in sequence
            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,
            ],
            // 31 votes in sequence
            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,
            ],
            // Votes with expiry
            vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
            // More votes with expiry of a large number of votes
            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],
        ];

        let feature_set = FeatureSet::default();

        for vote_group in test_vote_groups {
            // Duplicate vote_state so that the new vote can be applied
            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();

            // Now use the resulting new vote state to perform a vote state update on vote_state
            assert_eq!(
                process_new_vote_state(
                    &mut vote_state,
                    vote_state_after_vote.votes,
                    vote_state_after_vote.root_slot,
                    None,
                    0,
                    0,
                    Some(&feature_set)
                ),
                Ok(())
            );

            // And ensure that the credits earned were the same
            assert_eq!(
                vote_state.epoch_credits,
                vote_state_after_vote.epoch_credits
            );
        }
    }

    // Test vote credit updates after "timely vote credits" feature is enabled
    #[test]
    fn test_timely_credits() {
        // Each of the following (Vec<Slot>, Slot, u32) tuples gives a set of slots to cast votes on, a slot in which
        // the vote was cast, and the number of credits that should have been earned by the vote account after this
        // and all prior votes were cast.
        let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
            // Initial set of votes that don't dequeue any slots, so no credits earned
            (
                vec![1, 2, 3, 4, 5, 6, 7, 8],
                9,
                // root: none, no credits earned
                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,
                // lockouts full
                // root: none, no credits earned
                0,
            ),
            // Now a single vote which should result in the first root and first credit earned
            (
                vec![32],
                35,
                // root: 1
                // when slot 1 was voted on in slot 9, it earned 2 credits
                2,
            ),
            // Now another vote, should earn one credit
            (
                vec![33],
                36,
                // root: 2
                // when slot 2 was voted on in slot 9, it earned 3 credits
                2 + 3, // 5
            ),
            // Two votes in sequence
            (
                vec![34, 35],
                37,
                // root: 4
                // when slots 3 and 4 were voted on in slot 9, they earned 4 and 5 credits
                5 + 4 + 5, // 14
            ),
            // 3 votes in sequence
            (
                vec![36, 37, 38],
                39,
                // root: 7
                // slots 5, 6, and 7 earned 6, 7, and 8 credits when voted in slot 9
                14 + 6 + 7 + 8, // 35
            ),
            (
                // 30 votes in sequence
                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,
                // root: 37
                // slot 8 was voted in slot 9, earning 8 credits
                // slots 9 - 25 earned 1 credit when voted in slot 34
                // slot 26, 27, 28, 29, 30, 31 earned 2, 3, 4, 5, 6, 7 credits when voted in slot 34
                // slot 32 earned 7 credits when voted in slot 35
                // slot 33 earned 7 credits when voted in slot 36
                // slot 34 and 35 earned 7 and 8 credits when voted in slot 37
                // slot 36 and 37 earned 7 and 8 credits when voted in slot 39
                35 + 8 + ((25 - 9) + 1) + 2 + 3 + 4 + 5 + 6 + 7 + 7 + 7 + 7 + 8 + 7 + 8, // 131
            ),
            // 31 votes in sequence
            (
                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,
                // root: 68
                // slot 38 earned 8 credits when voted in slot 39
                // slot 39 - 60 earned 1 credit each when voted in slot 69
                // slot 61, 62, 63, 64, 65, 66, 67, 68 earned 2, 3, 4, 5, 6, 7, 8, and 8 credits when
                //   voted in slot 69
                131 + 8 + ((60 - 39) + 1) + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 8, // 204
            ),
            // Votes with expiry
            (
                vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
                130,
                // root: 74
                // slots 96 - 114 expire
                // slots 69 - 74 earned 1 credit when voted in slot 100
                204 + ((74 - 69) + 1), // 210
            ),
            // More votes with expiry of a large number of votes
            (
                vec![200, 201],
                202,
                // root: 74
                // slots 119 - 124 expire
                210,
            ),
            (
                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,
                // root: 95
                // slot 75 - 91 earned 1 credit each when voted in slot 100
                // slot 92, 93, 94, 95 earned 2, 3, 4, 5, credits when voted in slot 100
                210 + ((91 - 75) + 1) + 2 + 3 + 4 + 5, // 241
            ),
            (
                vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
                237,
                // root: 205
                // slot 115 - 118 earned 1 credit when voted in slot 130
                // slot 200 and 201 earned 8 credits when voted in slot 202
                // slots 202 - 205 earned 1 credit when voted in slot 227
                241 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 1 + 1 + 1, // 265
            ),
        ];

        let mut feature_set = FeatureSet::default();
        feature_set.activate(&feature_set::timely_vote_credits::id(), 1);

        // For each vote group, process all vote groups leading up to it and it itself, and ensure that the number of
        // credits earned is correct for both regular votes and vote state updates
        for i in 0..test_vote_groups.len() {
            // Create a new VoteState for vote transaction
            let mut vote_state_1 = VoteState::new(&VoteInit::default(), &Clock::default());
            // Create a new VoteState for vote state update transaction
            let mut vote_state_2 = VoteState::new(&VoteInit::default(), &Clock::default());
            test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
                let vote = Vote {
                    slots: vote_group.0.clone(), //vote_group.0 is the set of slots to cast votes on
                    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 // vote_group.1 is the slot in which the vote was cast
                    ),
                    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, // vote_group.1 is the slot in which the vote was cast
                        Some(&feature_set)
                    ),
                    Ok(())
                );
            });

            // Ensure that the credits earned is correct for both vote states
            let vote_group = &test_vote_groups[i];
            assert_eq!(vote_state_1.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
            assert_eq!(vote_state_2.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
        }
    }

    #[test]
    fn test_retroactive_voting_timely_credits() {
        // Each of the following (Vec<(Slot, int)>, Slot, Option<Slot>, u32) tuples gives the following data:
        // Vec<(Slot, int)> -- the set of slots and confirmation_counts that is the VoteStateUpdate
        // Slot -- the slot in which the VoteStateUpdate occurred
        // Option<Slot> -- the root after processing the VoteStateUpdate
        // u32 -- the credits after processing the VoteStateUpdate
        #[allow(clippy::type_complexity)]
        let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
            // VoteStateUpdate to set initial vote state
            (
                vec![(7, 4), (8, 3), (9, 2), (10, 1)],
                11,
                // root: none
                None,
                // no credits earned
                0,
            ),
            // VoteStateUpdate to include the missing slots *prior to previously included slots*
            (
                vec![
                    (1, 10),
                    (2, 9),
                    (3, 8),
                    (4, 7),
                    (5, 6),
                    (6, 5),
                    (7, 4),
                    (8, 3),
                    (9, 2),
                    (10, 1),
                ],
                12,
                // root: none
                None,
                // no credits earned
                0,
            ),
            // Now a single VoteStateUpdate which roots all of the slots from 1 - 10
            (
                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,
                // root: 10
                Some(10),
                // when slots 1 - 6 were voted on in slot 12, they earned 1, 1, 1, 2, 3, and 4 credits
                // when slots 7 - 10 were voted on in slot 11, they earned 6, 7, 8, and 8 credits
                1 + 1 + 1 + 2 + 3 + 4 + 6 + 7 + 8 + 8,
            ),
        ];

        let mut feature_set = FeatureSet::default();
        feature_set.activate(&feature_set::timely_vote_credits::id(), 1);

        // Retroactive voting is only possible with VoteStateUpdate transactions, which is why Vote transactions are
        // not tested here

        // Initial vote state
        let mut vote_state = VoteState::new(&VoteInit::default(), &Clock::default());

        // Process the vote state updates in sequence and ensure that the credits earned after each is processed is
        // correct
        test_vote_state_updates
            .iter()
            .for_each(|vote_state_update| {
                let new_state = vote_state_update
                    .0 // vote_state_update.0 is the set of slots and confirmation_counts that is the VoteStateUpdate
                    .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,
                        vote_state_update.2, // vote_state_update.2 is root after processing the VoteStateUpdate
                        None,
                        0,
                        vote_state_update.1, // vote_state_update.1 is the slot in which the VoteStateUpdate occurred
                        Some(&feature_set)
                    ),
                    Ok(())
                );

                // Ensure that the credits earned is correct
                assert_eq!(vote_state.credits(), vote_state_update.3 as u64);
            });
    }

    #[test]
    fn test_process_new_vote_too_many_votes() {
        let mut vote_state1 = VoteState::default();
        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,
                None
            ),
            Err(VoteError::TooManyVotes)
        );
    }

    #[test]
    fn test_process_new_vote_state_root_rollback() {
        let mut vote_state1 = VoteState::default();
        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);

        // Update vote_state2 with a higher slot so that `process_new_vote_state`
        // doesn't panic.
        let mut vote_state2 = vote_state1.clone();
        process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);

        // Trying to set a lesser root should error
        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,
                None,
            ),
            Err(VoteError::RootRollBack)
        );

        // Trying to set root to None should error
        let none_root = None;
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                none_root,
                None,
                current_epoch,
                0,
                None,
            ),
            Err(VoteError::RootRollBack)
        );
    }

    fn process_new_vote_state_replaced_root_vote_credits(
        feature_set: &FeatureSet,
        expected_credits: u64,
    ) {
        let mut vote_state1 = VoteState::default();

        // Initial vote state: as if 31 votes had occurred on slots 0 - 30 (inclusive)
        assert_eq!(
            process_new_vote_state_from_lockouts(
                &mut vote_state1,
                (0..MAX_LOCKOUT_HISTORY)
                    .enumerate()
                    .map(|(index, slot)| Lockout::new_with_confirmation_count(
                        slot as Slot,
                        (MAX_LOCKOUT_HISTORY.checked_sub(index).unwrap()) as u32
                    ))
                    .collect(),
                None,
                None,
                0,
                Some(feature_set),
            ),
            Ok(())
        );

        // Now vote as if new votes on slots 31 and 32 had occurred, yielding a new Root of 1
        assert_eq!(
            process_new_vote_state_from_lockouts(
                &mut vote_state1,
                (2..(MAX_LOCKOUT_HISTORY.checked_add(2).unwrap()))
                    .enumerate()
                    .map(|(index, slot)| Lockout::new_with_confirmation_count(
                        slot as Slot,
                        (MAX_LOCKOUT_HISTORY.checked_sub(index).unwrap()) as u32
                    ))
                    .collect(),
                Some(1),
                None,
                0,
                Some(feature_set),
            ),
            Ok(())
        );

        // Vote credits should be 2, since two voted-on slots were "popped off the back" of the tower
        assert_eq!(vote_state1.credits(), 2);

        // Create a new vote state that represents the validator having not voted for a long time, then voting on
        // slots 10001 through 10032 (inclusive) with an entirely new root of 10000 that was never previously voted
        // on.  This is valid because a vote state can include a root that it never voted on (if it votes after a very
        // long delinquency, the new votes will have a root much newer than its most recently voted slot).
        assert_eq!(
            process_new_vote_state_from_lockouts(
                &mut vote_state1,
                (10001..(MAX_LOCKOUT_HISTORY.checked_add(10001).unwrap()))
                    .enumerate()
                    .map(|(index, slot)| Lockout::new_with_confirmation_count(
                        slot as Slot,
                        (MAX_LOCKOUT_HISTORY.checked_sub(index).unwrap()) as u32
                    ))
                    .collect(),
                Some(10000),
                None,
                0,
                Some(feature_set),
            ),
            Ok(())
        );

        // The vote is valid, but no vote credits should be awarded because although there is a new root, it does not
        // represent a slot previously voted on.
        assert_eq!(vote_state1.credits(), expected_credits)
    }

    #[test]
    fn test_process_new_vote_state_replaced_root_vote_credits() {
        let mut feature_set = FeatureSet::default();

        // Always use allow_votes_to_directly_update_vote_state feature because VoteStateUpdate is being tested
        feature_set.activate(
            &feature_set::allow_votes_to_directly_update_vote_state::id(),
            1,
        );

        // Test without the timely_vote_credits feature.  The expected credits here of 34 is *incorrect* but is what
        // is expected using vote_state_update_credit_per_dequeue.  With this feature, the credits earned will be
        // calculated as:
        // 2 (from initial vote state)
        // + 31 (for votes which were "popped off of the back of the tower" by the new vote
        // + 1 (just because there is a new root, even though it was never voted on -- this is the flaw)
        feature_set.activate(&feature_set::vote_state_update_credit_per_dequeue::id(), 1);
        process_new_vote_state_replaced_root_vote_credits(&feature_set, 34);

        // Now test using the timely_vote_credits feature.  The expected credits here of 33 is *correct*.  With
        // this feature, the credits earned will be calculated as:
        // 2 (from initial vote state)
        // + 31 (for votes which were "popped off of the back of the tower" by the new vote)
        feature_set.activate(&feature_set::timely_vote_credits::id(), 1);
        process_new_vote_state_replaced_root_vote_credits(&feature_set, 33);
    }

    #[test]
    fn test_process_new_vote_state_zero_confirmations() {
        let mut vote_state1 = VoteState::default();
        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,
                None
            ),
            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,
                None
            ),
            Err(VoteError::ZeroConfirmations)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmations_too_large() {
        let mut vote_state1 = VoteState::default();
        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,
            None,
        )
        .unwrap();

        let mut vote_state1 = VoteState::default();
        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,
                None
            ),
            Err(VoteError::ConfirmationTooLarge)
        );
    }

    #[test]
    fn test_process_new_vote_state_slot_smaller_than_root() {
        let mut vote_state1 = VoteState::default();
        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,
                None,
            ),
            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,
                None,
            ),
            Err(VoteError::SlotSmallerThanRoot)
        );
    }

    #[test]
    fn test_process_new_vote_state_slots_not_ordered() {
        let mut vote_state1 = VoteState::default();
        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,
                None
            ),
            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,
                None
            ),
            Err(VoteError::SlotsNotOrdered)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmations_not_ordered() {
        let mut vote_state1 = VoteState::default();
        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,
                None
            ),
            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,
                None
            ),
            Err(VoteError::ConfirmationsNotOrdered)
        );
    }

    #[test]
    fn test_process_new_vote_state_new_vote_state_lockout_mismatch() {
        let mut vote_state1 = VoteState::default();
        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();

        // Slot 7 should have expired slot 0
        assert_eq!(
            process_new_vote_state_from_lockouts(
                &mut vote_state1,
                bad_votes,
                None,
                None,
                current_epoch,
                None
            ),
            Err(VoteError::NewVoteStateLockoutMismatch)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmation_rollback() {
        let mut vote_state1 = VoteState::default();
        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,
            None,
        )
        .unwrap();

        let votes: VecDeque<Lockout> = vec![
            Lockout::new_with_confirmation_count(0, 4),
            // Confirmation count lowered illegally
            Lockout::new_with_confirmation_count(1, 2),
            Lockout::new_with_confirmation_count(2, 1),
        ]
        .into_iter()
        .collect();
        // Should error because newer vote state should not have lower confirmation the same slot
        // 1
        assert_eq!(
            process_new_vote_state_from_lockouts(
                &mut vote_state1,
                votes,
                None,
                None,
                current_epoch,
                None
            ),
            Err(VoteError::ConfirmationRollBack)
        );
    }

    #[test]
    fn test_process_new_vote_state_root_progress() {
        let mut vote_state1 = VoteState::default();
        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();

        // 1) Try to update `vote_state1` with no root,
        // to `vote_state2`, which has a new root, should succeed.
        //
        // 2) Then try to update`vote_state1` with an existing root,
        // to `vote_state2`, which has a newer root, which
        // should succeed.
        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,
                None,
            )
            .unwrap();

            assert_eq!(vote_state1, vote_state2);
        }
    }

    #[test]
    fn test_process_new_vote_state_same_slot_but_not_common_ancestor() {
        // It might be possible that during the switch from old vote instructions
        // to new vote instructions, new_state contains votes for slots LESS
        // than the current state, for instance:
        //
        // Current on-chain state: 1, 5
        // New state: 1, 2 (lockout: 4), 3, 5, 7
        //
        // Imagine the validator made two of these votes:
        // 1) The first vote {1, 2, 3} didn't land in the old state, but didn't
        // land on chain
        // 2) A second vote {1, 2, 5} was then submitted, which landed
        //
        //
        // 2 is not popped off in the local tower because 3 doubled the lockout.
        // However, 3 did not land in the on-chain state, so the vote {1, 2, 6}
        // will immediately pop off 2.

        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        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]
        );

        // Construct local tower state
        let mut vote_state2 = VoteState::default();
        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]
        );

        // See that on-chain vote state can update properly
        process_new_vote_state(
            &mut vote_state1,
            vote_state2.votes.clone(),
            vote_state2.root_slot,
            None,
            vote_state2.current_epoch(),
            0,
            None,
        )
        .unwrap();

        assert_eq!(vote_state1, vote_state2);
    }

    #[test]
    fn test_process_new_vote_state_lockout_violation() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        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]
        );

        // Construct conflicting tower state. Vote 4 is missing,
        // but 5 should not have popped off vote 4.
        let mut vote_state2 = VoteState::default();
        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]
        );

        // See that on-chain vote state can update properly
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                vote_state2.root_slot,
                None,
                vote_state2.current_epoch(),
                0,
                None
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[test]
    fn test_process_new_vote_state_lockout_violation2() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        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]
        );

        // Construct a new vote state. Violates on-chain state because 8
        // should not have popped off 7
        let mut vote_state2 = VoteState::default();
        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]
        );

        // Both vote states contain `5`, but `5` is not part of the common prefix
        // of both vote states. However, the violation should still be detected.
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                vote_state2.root_slot,
                None,
                vote_state2.current_epoch(),
                0,
                None
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[test]
    fn test_process_new_vote_state_expired_ancestor_not_removed() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        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]
        );

        // Example: {1: lockout 8, 9: lockout 2}, vote on 10 will not pop off 1
        // because 9 is not popped off yet
        let mut vote_state2 = vote_state1.clone();
        process_slot_vote_unchecked(&mut vote_state2, 10);

        // Slot 1 has been expired by 10, but is kept alive by its descendant
        // 9 which has not been expired yet.
        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]
        );

        // Should be able to update vote_state1
        process_new_vote_state(
            &mut vote_state1,
            vote_state2.votes.clone(),
            vote_state2.root_slot,
            None,
            vote_state2.current_epoch(),
            0,
            None,
        )
        .unwrap();
        assert_eq!(vote_state1, vote_state2,);
    }

    #[test]
    fn test_process_new_vote_current_state_contains_bigger_slots() {
        let mut vote_state1 = VoteState::default();
        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]
        );

        // Try to process something with lockout violations
        let bad_votes: VecDeque<Lockout> = vec![
            Lockout::new_with_confirmation_count(2, 5),
            // Slot 14 could not have popped off slot 6 yet
            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,
                None
            ),
            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,
            None,
        )
        .unwrap();
        assert_eq!(vote_state1.votes, good_votes);
    }

    #[test]
    fn test_filter_old_votes() {
        let mut vote_state = VoteState::default();
        let old_vote_slot = 1;
        let vote = Vote::new(vec![old_vote_slot], Hash::default());

        // Vote with all slots that are all older than the SlotHashes history should
        // error with `VotesTooOldAllFiltered`
        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)
        );

        // Vote with only some slots older than the SlotHashes history should
        // filter out those older slots
        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
                .into_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(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;
            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]
    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!(
            check_update_vote_state_slots_are_valid(
                &empty_vote_state,
                &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!(
            check_update_vote_state_slots_are_valid(
                &empty_vote_state,
                &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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
            ),
            Err(VoteError::VoteTooOld),
        );
    }

    fn run_test_check_update_vote_state_older_than_history_root(
        earliest_slot_in_history: Slot,
        current_vote_state_slots: Vec<Slot>,
        current_vote_state_root: Option<Slot>,
        vote_state_update_slots_and_lockouts: Vec<(Slot, u32)>,
        vote_state_update_root: Slot,
        expected_root: Option<Slot>,
        expected_vote_state: Vec<Lockout>,
    ) {
        assert!(vote_state_update_root < earliest_slot_in_history);
        assert_eq!(
            expected_root,
            current_vote_state_slots
                .iter()
                .rev()
                .find(|slot| **slot <= vote_state_update_root)
                .cloned()
        );
        let latest_slot_in_history = vote_state_update_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(current_vote_state_slots, &slot_hashes);
        vote_state.root_slot = current_vote_state_root;

        slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
        assert!(!vote_state_update_slots_and_lockouts.is_empty());
        let vote_state_update_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_state_update_slots_and_lockouts.last().unwrap().0)
            .unwrap()
            .1;

        // 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 mut vote_state_update = VoteStateUpdate::from(vote_state_update_slots_and_lockouts);
        vote_state_update.hash = vote_state_update_hash;
        vote_state_update.root = Some(vote_state_update_root);
        check_update_vote_state_slots_are_valid(&vote_state, &mut vote_state_update, &slot_hashes)
            .unwrap();
        assert_eq!(vote_state_update.root, expected_root);

        // The proposed root slot should become the biggest slot in the current vote state less than
        // `earliest_slot_in_history`.
        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            0,
            vote_state_update.clone(),
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
        assert_eq!(vote_state.root_slot, expected_root);
        assert_eq!(
            vote_state
                .votes
                .into_iter()
                .map(|vote| vote.lockout)
                .collect::<Vec<Lockout>>(),
            expected_vote_state,
        );
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_root() {
        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot
        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 vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 4;
        let expected_root = Some(4);
        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot and the `current_vote_state_root.is_some()`.
        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 vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 4;
        let expected_root = Some(4);
        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot
        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 vote_state_update_slots_and_lockouts = vec![(4, 2), (5, 1)];
        let vote_state_update_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_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is not in `current_vote_state_slots`
        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 vote_state_update_slots_and_lockouts = vec![(4, 2), (5, 1)];
        let vote_state_update_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_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when the `vote_state_update_root` is smaller than all the slots in
        // `current_vote_state_slots`, no roots should be set.
        let earliest_slot_in_history = 4;
        let current_vote_state_slots: Vec<Slot> = vec![3, 4];
        let current_vote_state_root = None;
        let vote_state_update_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
        let vote_state_update_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_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `current_vote_state_slots` is empty, no roots should be set
        let earliest_slot_in_history = 4;
        let current_vote_state_slots: Vec<Slot> = vec![];
        let current_vote_state_root = None;
        let vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 2;
        let expected_root = None;
        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );
    }

    #[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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &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 mut 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![
            (1, 4),
            (missing_older_than_history_slot, 2),
            (vote_slot, 3),
        ]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(&vote_state, &mut vote_state_update, &slot_hashes)
            .unwrap();

        // Check the earlier slot was filtered out
        assert_eq!(
            vote_state_update
                .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_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_slots_not_filtered() {
        let slot_hashes = build_slot_hashes(vec![4]);
        let mut vote_state = build_vote_state(vec![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, 3), (vote_slot, 2)]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(&vote_state, &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
                .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_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_slots_filtered_and_not_filtered() {
        let slot_hashes = build_slot_hashes(vec![6]);
        let mut vote_state = build_vote_state(vec![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;
        check_update_vote_state_slots_are_valid(&vote_state, &mut vote_state_update, &slot_hashes)
            .unwrap();
        assert_eq!(vote_state_update.lockouts.len(), 3);
        assert_eq!(
            vote_state_update
                .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_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
            ),
            Err(VoteError::SlotsMismatch),
        );
    }

    #[test]
    fn test_check_update_vote_state_root_on_different_fork() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
        let vote_state = build_vote_state(vec![6], &slot_hashes);

        // Test with a `vote_state_update` where:
        // 1) The root is not present in 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 `RootOnDifferentFork`
        let new_root = 3;

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors, but also this slot must be present in SlotHashes
        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 vote_state_update = VoteStateUpdate::from(vec![(vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        vote_state_update.root = Some(new_root);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
            ),
            Err(VoteError::RootOnDifferentFork),
        );
    }

    #[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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &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 mut 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;
        check_update_vote_state_slots_are_valid(&vote_state, &mut vote_state_update, &slot_hashes)
            .unwrap();

        // Nothing in the update should have been filtered out
        assert_eq!(
            vote_state_update
                .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_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[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 mut vote_state = build_vote_state(vec![6], &slot_hashes);

        // Test with a `vote_state_update` where 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;
        check_update_vote_state_slots_are_valid(&vote_state, &mut vote_state_update, &slot_hashes)
            .unwrap();

        // Nothing in the update should have been filtered out
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout::new_with_confirmation_count(4, 2),
                Lockout::new_with_confirmation_count(vote_slot, 1)
            ]
        );

        // Because 6 from the original VoteState
        // should not have been popped off in the proposed state,
        // we should get a lockout conflict
        assert_eq!(
            do_process_vote_state_update(
                &mut vote_state,
                &slot_hashes,
                0,
                0,
                vote_state_update,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[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!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &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;
        // first slot works
        assert!(is_commission_update_allowed(0, &epoch_schedule));
        // right before first normal slot works, since all warmup slots allow
        // commission updates
        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
        );
    }
}
