Add ability to update entire vote state (#20014)
This commit is contained in:
		@@ -46,6 +46,37 @@ pub enum VoteError {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[error("authorized voter has already been changed this epoch")]
 | 
					    #[error("authorized voter has already been changed this epoch")]
 | 
				
			||||||
    TooSoonToReauthorize,
 | 
					    TooSoonToReauthorize,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: figure out how to migrate these new errors
 | 
				
			||||||
 | 
					    #[error("Old state had vote which should not have been popped off by vote in new state")]
 | 
				
			||||||
 | 
					    LockoutConflict,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Proposed state had earlier slot which should have been popped off by later vote")]
 | 
				
			||||||
 | 
					    NewVoteStateLockoutMismatch,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Vote slots are not ordered")]
 | 
				
			||||||
 | 
					    SlotsNotOrdered,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Confirmations are not ordered")]
 | 
				
			||||||
 | 
					    ConfirmationsNotOrdered,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Zero confirmations")]
 | 
				
			||||||
 | 
					    ZeroConfirmations,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Confirmation exceeds limit")]
 | 
				
			||||||
 | 
					    ConfirmationTooLarge,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Root rolled back")]
 | 
				
			||||||
 | 
					    RootRollBack,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Confirmations for same vote were smaller in new proposed state")]
 | 
				
			||||||
 | 
					    ConfirmationRollBack,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("New state contained a vote slot smaller than the root")]
 | 
				
			||||||
 | 
					    SlotSmallerThanRoot,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("New state contained too many votes")]
 | 
				
			||||||
 | 
					    TooManyVotes,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<E> DecodeError<E> for VoteError {
 | 
					impl<E> DecodeError<E> for VoteError {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,10 +304,25 @@ impl VoteState {
 | 
				
			|||||||
        vote: &Vote,
 | 
					        vote: &Vote,
 | 
				
			||||||
        slot_hashes: &[(Slot, Hash)],
 | 
					        slot_hashes: &[(Slot, Hash)],
 | 
				
			||||||
    ) -> Result<(), VoteError> {
 | 
					    ) -> Result<(), VoteError> {
 | 
				
			||||||
        let mut i = 0; // index into the vote's slots
 | 
					        // index into the vote's slots, sarting at the newest
 | 
				
			||||||
        let mut j = slot_hashes.len(); // index into the slot_hashes
 | 
					        // known 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), but `slot_hashes` is sorted smallest to largest.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
 | 
				
			||||||
 | 
					        // the oldest/smallest vote
 | 
				
			||||||
        while i < vote.slots.len() && j > 0 {
 | 
					        while i < vote.slots.len() && j > 0 {
 | 
				
			||||||
            // find the last slot in the vote
 | 
					            // 1) increment `i` to find the smallest slot `s` in `vote.slots`
 | 
				
			||||||
 | 
					            // where `s` >= `last_voted_slot`
 | 
				
			||||||
            if self
 | 
					            if self
 | 
				
			||||||
                .last_voted_slot()
 | 
					                .last_voted_slot()
 | 
				
			||||||
                .map_or(false, |last_voted_slot| vote.slots[i] <= last_voted_slot)
 | 
					                .map_or(false, |last_voted_slot| vote.slots[i] <= last_voted_slot)
 | 
				
			||||||
@@ -315,14 +330,24 @@ impl VoteState {
 | 
				
			|||||||
                i += 1;
 | 
					                i += 1;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 2) Find the hash for this slot `s`.
 | 
				
			||||||
            if vote.slots[i] != slot_hashes[j - 1].0 {
 | 
					            if vote.slots[i] != slot_hashes[j - 1].0 {
 | 
				
			||||||
 | 
					                // Decrement `j` to find newer slots
 | 
				
			||||||
                j -= 1;
 | 
					                j -= 1;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 3) Once the hash for `s` is found, bump `s` to the next slot
 | 
				
			||||||
 | 
					            // in `vote.slots` and continue.
 | 
				
			||||||
            i += 1;
 | 
					            i += 1;
 | 
				
			||||||
            j -= 1;
 | 
					            j -= 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if j == slot_hashes.len() {
 | 
					        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` greater than `last_voted_slot`
 | 
				
			||||||
            debug!(
 | 
					            debug!(
 | 
				
			||||||
                "{} dropped vote {:?} too old: {:?} ",
 | 
					                "{} dropped vote {:?} too old: {:?} ",
 | 
				
			||||||
                self.node_pubkey, vote, slot_hashes
 | 
					                self.node_pubkey, vote, slot_hashes
 | 
				
			||||||
@@ -330,6 +355,8 @@ impl VoteState {
 | 
				
			|||||||
            return Err(VoteError::VoteTooOld);
 | 
					            return Err(VoteError::VoteTooOld);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if i != vote.slots.len() {
 | 
					        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!(
 | 
					            info!(
 | 
				
			||||||
                "{} dropped vote {:?} failed to match slot:  {:?}",
 | 
					                "{} dropped vote {:?} failed to match slot:  {:?}",
 | 
				
			||||||
                self.node_pubkey, vote, slot_hashes,
 | 
					                self.node_pubkey, vote, slot_hashes,
 | 
				
			||||||
@@ -338,6 +365,9 @@ impl VoteState {
 | 
				
			|||||||
            return Err(VoteError::SlotsMismatch);
 | 
					            return Err(VoteError::SlotsMismatch);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if slot_hashes[j].1 != vote.hash {
 | 
					        if slot_hashes[j].1 != vote.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!(
 | 
					            warn!(
 | 
				
			||||||
                "{} dropped vote {:?} failed to match hash {} {}",
 | 
					                "{} dropped vote {:?} failed to match hash {} {}",
 | 
				
			||||||
                self.node_pubkey, vote, vote.hash, slot_hashes[j].1
 | 
					                self.node_pubkey, vote, vote.hash, slot_hashes[j].1
 | 
				
			||||||
@@ -348,6 +378,176 @@ impl VoteState {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //`Ensure check_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(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        new_state: VecDeque<Lockout>,
 | 
				
			||||||
 | 
					        new_root: Option<Slot>,
 | 
				
			||||||
 | 
					        timestamp: Option<i64>,
 | 
				
			||||||
 | 
					        epoch: Epoch,
 | 
				
			||||||
 | 
					    ) -> Result<(), VoteError> {
 | 
				
			||||||
 | 
					        assert!(!new_state.is_empty());
 | 
				
			||||||
 | 
					        if new_state.len() > MAX_LOCKOUT_HISTORY {
 | 
				
			||||||
 | 
					            return Err(VoteError::TooManyVotes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check_slots_are_valid()` ensures we don't process any states
 | 
				
			||||||
 | 
					        // that are older than the current state
 | 
				
			||||||
 | 
					        if !self.votes.is_empty() {
 | 
				
			||||||
 | 
					            assert!(new_state.back().unwrap().slot > self.votes.back().unwrap().slot);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match (new_root, self.root_slot) {
 | 
				
			||||||
 | 
					            (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<&Lockout> = 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.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 = 0;
 | 
				
			||||||
 | 
					        let mut new_vote_state_index = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for current_vote in &self.votes {
 | 
				
			||||||
 | 
					            // Find the first vote in the current vote state for a slot greater
 | 
				
			||||||
 | 
					            // than the new proposed root
 | 
				
			||||||
 | 
					            if let Some(new_root) = new_root {
 | 
				
			||||||
 | 
					                if current_vote.slot <= new_root {
 | 
				
			||||||
 | 
					                    current_vote_state_index += 1;
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 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 < self.votes.len() && new_vote_state_index < new_state.len()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let current_vote = &self.votes[current_vote_state_index];
 | 
				
			||||||
 | 
					            let new_vote = &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.last_locked_out_slot() >= new_vote.slot {
 | 
				
			||||||
 | 
					                        return Err(VoteError::LockoutConflict);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    current_vote_state_index += 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                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);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    current_vote_state_index += 1;
 | 
				
			||||||
 | 
					                    new_vote_state_index += 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Ordering::Greater => {
 | 
				
			||||||
 | 
					                    new_vote_state_index += 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // `new_vote_state` passed all the checks, finalize the change by rewriting
 | 
				
			||||||
 | 
					        // our state.
 | 
				
			||||||
 | 
					        if self.root_slot != new_root {
 | 
				
			||||||
 | 
					            // TODO to think about: Note, people may be incentivized to set more
 | 
				
			||||||
 | 
					            // roots to get more credits, but I think they can already do this...
 | 
				
			||||||
 | 
					            self.increment_credits(epoch);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let Some(timestamp) = timestamp {
 | 
				
			||||||
 | 
					            let last_slot = new_state.back().unwrap().slot;
 | 
				
			||||||
 | 
					            self.process_timestamp(last_slot, timestamp)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.root_slot = new_root;
 | 
				
			||||||
 | 
					        self.votes = new_state;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn process_vote(
 | 
					    pub fn process_vote(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        vote: &Vote,
 | 
					        vote: &Vote,
 | 
				
			||||||
@@ -422,6 +622,14 @@ impl VoteState {
 | 
				
			|||||||
        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
 | 
					        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
 | 
				
			||||||
        let _ignored = self.process_vote(vote, &slot_hashes, self.current_epoch());
 | 
					        let _ignored = self.process_vote(vote, &slot_hashes, self.current_epoch());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[cfg(test)]
 | 
				
			||||||
 | 
					    pub fn process_slot_votes_unchecked(&mut self, slots: &[Slot]) {
 | 
				
			||||||
 | 
					        for slot in slots {
 | 
				
			||||||
 | 
					            self.process_slot_vote_unchecked(*slot);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
 | 
					    pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
 | 
				
			||||||
        self.process_vote_unchecked(&Vote::new(vec![slot], Hash::default()));
 | 
					        self.process_vote_unchecked(&Vote::new(vec![slot], Hash::default()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -2123,4 +2331,587 @@ mod tests {
 | 
				
			|||||||
            &vote_account_data
 | 
					            &vote_account_data
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[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 {
 | 
				
			||||||
 | 
					                slot: slot as Slot,
 | 
				
			||||||
 | 
					                confirmation_count: (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            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 {
 | 
				
			||||||
 | 
					            vote_state1.process_slot_vote_unchecked(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();
 | 
				
			||||||
 | 
					        vote_state2.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as Slot + 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Trying to set a lesser root should error
 | 
				
			||||||
 | 
					        let lesser_root = Some(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                lesser_root,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(VoteError::RootRollBack)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Trying to set root to None should error
 | 
				
			||||||
 | 
					        let none_root = None;
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                none_root,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(VoteError::RootRollBack)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_zero_confirmations() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::ZeroConfirmations)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::ZeroConfirmations)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_confirmations_too_large() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let good_votes: VecDeque<Lockout> = vec![Lockout {
 | 
				
			||||||
 | 
					            slot: 0,
 | 
				
			||||||
 | 
					            confirmation_count: MAX_LOCKOUT_HISTORY as u32,
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vote_state1
 | 
				
			||||||
 | 
					            .process_new_vote_state(good_votes, None, None, vote_state1.current_epoch())
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![Lockout {
 | 
				
			||||||
 | 
					            slot: 0,
 | 
				
			||||||
 | 
					            confirmation_count: MAX_LOCKOUT_HISTORY as u32 + 1,
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::ConfirmationTooLarge)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_slot_smaller_than_root() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					        let root_slot = 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: root_slot,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: root_slot + 1,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                bad_votes,
 | 
				
			||||||
 | 
					                Some(root_slot),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state1.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(VoteError::SlotSmallerThanRoot)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: root_slot - 1,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: root_slot + 1,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                bad_votes,
 | 
				
			||||||
 | 
					                Some(root_slot),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state1.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(VoteError::SlotSmallerThanRoot)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_slots_not_ordered() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::SlotsNotOrdered)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::SlotsNotOrdered)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_confirmations_not_ordered() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::ConfirmationsNotOrdered)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::ConfirmationsNotOrdered)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_new_vote_state_lockout_mismatch() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bad_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 7,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Slot 7 should have expired slot 0
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::NewVoteStateLockoutMismatch)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_confirmation_rollback() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					        let votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 4,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                confirmation_count: 3,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        vote_state1
 | 
				
			||||||
 | 
					            .process_new_vote_state(votes, None, None, vote_state1.current_epoch())
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 0,
 | 
				
			||||||
 | 
					                confirmation_count: 4,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 1,
 | 
				
			||||||
 | 
					                // Confirmation count lowered illegally
 | 
				
			||||||
 | 
					                confirmation_count: 2,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 2,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        // Should error because newer vote state should not have lower confirmation the same slot
 | 
				
			||||||
 | 
					        // 1
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(votes, None, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            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 {
 | 
				
			||||||
 | 
					            vote_state1.process_slot_vote_unchecked(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 {
 | 
				
			||||||
 | 
					            vote_state2.process_slot_vote_unchecked(new_vote as Slot);
 | 
				
			||||||
 | 
					            assert_ne!(vote_state1.root_slot, vote_state2.root_slot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            vote_state1
 | 
				
			||||||
 | 
					                .process_new_vote_state(
 | 
				
			||||||
 | 
					                    vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                    vote_state2.root_slot,
 | 
				
			||||||
 | 
					                    None,
 | 
				
			||||||
 | 
					                    vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .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();
 | 
				
			||||||
 | 
					        vote_state1.process_slot_votes_unchecked(&[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();
 | 
				
			||||||
 | 
					        vote_state2.process_slot_votes_unchecked(&[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
 | 
				
			||||||
 | 
					        vote_state1
 | 
				
			||||||
 | 
					            .process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                vote_state2.root_slot,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .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();
 | 
				
			||||||
 | 
					        vote_state1.process_slot_votes_unchecked(&[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();
 | 
				
			||||||
 | 
					        vote_state2.process_slot_votes_unchecked(&[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!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                vote_state2.root_slot,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Err(VoteError::LockoutConflict)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_state_lockout_violation2() {
 | 
				
			||||||
 | 
					        // Construct on-chain vote state
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					        vote_state1.process_slot_votes_unchecked(&[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();
 | 
				
			||||||
 | 
					        vote_state2.process_slot_votes_unchecked(&[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!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                vote_state2.root_slot,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            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();
 | 
				
			||||||
 | 
					        vote_state1.process_slot_votes_unchecked(&[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();
 | 
				
			||||||
 | 
					        vote_state2.process_slot_vote_unchecked(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].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
 | 
				
			||||||
 | 
					        vote_state1
 | 
				
			||||||
 | 
					            .process_new_vote_state(
 | 
				
			||||||
 | 
					                vote_state2.votes.clone(),
 | 
				
			||||||
 | 
					                vote_state2.root_slot,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                vote_state2.current_epoch(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(vote_state1, vote_state2,);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_process_new_vote_current_state_contains_bigger_slots() {
 | 
				
			||||||
 | 
					        let mut vote_state1 = VoteState::default();
 | 
				
			||||||
 | 
					        vote_state1.process_slot_votes_unchecked(&[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 {
 | 
				
			||||||
 | 
					                slot: 2,
 | 
				
			||||||
 | 
					                confirmation_count: 5,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                // Slot 14 could not have popped off slot 6 yet
 | 
				
			||||||
 | 
					                slot: 14,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					        let root = Some(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            vote_state1.process_new_vote_state(bad_votes, root, None, vote_state1.current_epoch(),),
 | 
				
			||||||
 | 
					            Err(VoteError::LockoutConflict)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let good_votes: VecDeque<Lockout> = vec![
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 2,
 | 
				
			||||||
 | 
					                confirmation_count: 5,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Lockout {
 | 
				
			||||||
 | 
					                slot: 15,
 | 
				
			||||||
 | 
					                confirmation_count: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vote_state1
 | 
				
			||||||
 | 
					            .process_new_vote_state(good_votes.clone(), root, None, vote_state1.current_epoch())
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(vote_state1.votes, good_votes);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user