solana_vote_program/vote_state/
mod.rs

1//! Vote state, vote program
2//! Receive and processes votes from validators
3
4#[cfg(feature = "dev-context-only-utils")]
5pub mod handler;
6#[cfg(not(feature = "dev-context-only-utils"))]
7pub(crate) mod handler;
8
9pub use solana_vote_interface::state::{vote_state_versions::*, *};
10use {
11    handler::{VoteStateHandle, VoteStateHandler, VoteStateTargetVersion},
12    log::*,
13    solana_account::{AccountSharedData, WritableAccount},
14    solana_clock::{Clock, Epoch, Slot},
15    solana_epoch_schedule::EpochSchedule,
16    solana_hash::Hash,
17    solana_instruction::error::InstructionError,
18    solana_pubkey::Pubkey,
19    solana_rent::Rent,
20    solana_slot_hashes::SlotHash,
21    solana_transaction_context::{BorrowedInstructionAccount, IndexOfAccount, InstructionContext},
22    solana_vote_interface::{error::VoteError, program::id},
23    std::{
24        cmp::Ordering,
25        collections::{HashSet, VecDeque},
26    },
27};
28
29// Switch that preserves old behavior before vote state v4 feature gate.
30// This should be cleaned up when vote state v4 is activated.
31enum PreserveBehaviorInHandlerHelper {
32    V3 { check_initialized: bool },
33    V4,
34}
35
36impl PreserveBehaviorInHandlerHelper {
37    fn new(target_version: VoteStateTargetVersion, check_initialized: bool) -> Self {
38        match target_version {
39            VoteStateTargetVersion::V3 => Self::V3 { check_initialized },
40            VoteStateTargetVersion::V4 => Self::V4,
41        }
42    }
43}
44
45fn get_vote_state_handler_checked(
46    vote_account: &BorrowedInstructionAccount,
47    preserve_behavior: PreserveBehaviorInHandlerHelper,
48) -> Result<VoteStateHandler, InstructionError> {
49    match preserve_behavior {
50        PreserveBehaviorInHandlerHelper::V3 { check_initialized } => {
51            // Existing flow before v4 feature gate activation:
52            // 1. Deserialize as `VoteState3`, converting during deserialization
53            // 2. Check for uninitialized
54            //
55            // Some callsites would deserialize without checking initialization
56            // status, hence the nested `check_initialized` switch.
57            let vote_state = VoteStateV3::deserialize(vote_account.get_data())?;
58            if check_initialized && vote_state.is_uninitialized() {
59                return Err(InstructionError::UninitializedAccount);
60            }
61            Ok(VoteStateHandler::new_v3(vote_state))
62        }
63        PreserveBehaviorInHandlerHelper::V4 => {
64            // New flow after v4 feature gate activation:
65            // 1. Deserialize as `VoteStateVersions`
66            // 2. Check for uninitialized
67            // 3. Convert
68            let versioned = VoteStateVersions::deserialize(vote_account.get_data())?;
69            if versioned.is_uninitialized() {
70                return Err(InstructionError::UninitializedAccount);
71            }
72            let vote_state =
73                handler::try_convert_to_vote_state_v4(versioned, vote_account.get_key())?;
74            Ok(VoteStateHandler::new_v4(vote_state))
75        }
76    }
77}
78
79/// Checks the proposed vote state with the current and
80/// slot hashes, making adjustments to the root / filtering
81/// votes as needed.
82fn check_and_filter_proposed_vote_state(
83    vote_state: &VoteStateHandler,
84    proposed_lockouts: &mut VecDeque<Lockout>,
85    proposed_root: &mut Option<Slot>,
86    proposed_hash: Hash,
87    slot_hashes: &[(Slot, Hash)],
88) -> Result<(), VoteError> {
89    if proposed_lockouts.is_empty() {
90        return Err(VoteError::EmptySlots);
91    }
92
93    let last_proposed_slot = proposed_lockouts
94        .back()
95        .expect("must be nonempty, checked above")
96        .slot();
97
98    // If the proposed state is not new enough, return
99    if let Some(last_vote_slot) = vote_state.votes().back().map(|lockout| lockout.slot()) {
100        if last_proposed_slot <= last_vote_slot {
101            return Err(VoteError::VoteTooOld);
102        }
103    }
104
105    if slot_hashes.is_empty() {
106        return Err(VoteError::SlotsMismatch);
107    }
108    let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
109
110    // Check if the proposed vote state is too old to be in the SlotHash history
111    if last_proposed_slot < earliest_slot_hash_in_history {
112        // If this is the last slot in the vote update, it must be in SlotHashes,
113        // otherwise we have no way of confirming if the hash matches
114        return Err(VoteError::VoteTooOld);
115    }
116
117    // Overwrite the proposed root if it is too old to be in the SlotHash history
118    if let Some(root) = *proposed_root {
119        // If the new proposed root `R` is less than the earliest slot hash in the history
120        // such that we cannot verify whether the slot was actually was on this fork, set
121        // the root to the latest vote in the vote state that's less than R. If no
122        // votes from the vote state are less than R, use its root instead.
123        if root < earliest_slot_hash_in_history {
124            // First overwrite the proposed root with the vote state's root
125            *proposed_root = vote_state.root_slot();
126
127            // Then try to find the latest vote in vote state that's less than R
128            for vote in vote_state.votes().iter().rev() {
129                if vote.slot() <= root {
130                    *proposed_root = Some(vote.slot());
131                    break;
132                }
133            }
134        }
135    }
136
137    // Index into the new proposed vote state's slots, starting with the root if it exists then
138    // we use this mutable root to fold checking the root slot into the below loop
139    // for performance
140    let mut root_to_check = *proposed_root;
141    let mut proposed_lockouts_index = 0;
142
143    // index into the slot_hashes, starting at the oldest known
144    // slot hash
145    let mut slot_hashes_index = slot_hashes.len();
146
147    let mut proposed_lockouts_indices_to_filter = vec![];
148
149    // Note:
150    //
151    // 1) `proposed_lockouts` is sorted from oldest/smallest vote to newest/largest
152    // vote, due to the way votes are applied to the vote state (newest votes
153    // pushed to the back).
154    //
155    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
156    // the oldest/smallest vote
157    //
158    // We check every proposed lockout because have to ensure that every slot is actually part of
159    // the history, not just the most recent ones
160    while proposed_lockouts_index < proposed_lockouts.len() && slot_hashes_index > 0 {
161        let proposed_vote_slot = if let Some(root) = root_to_check {
162            root
163        } else {
164            proposed_lockouts[proposed_lockouts_index].slot()
165        };
166        if root_to_check.is_none()
167            && proposed_lockouts_index > 0
168            && proposed_vote_slot
169                <= proposed_lockouts[proposed_lockouts_index.checked_sub(1).expect(
170                    "`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`",
171                )]
172                .slot()
173        {
174            return Err(VoteError::SlotsNotOrdered);
175        }
176        let ancestor_slot = slot_hashes[slot_hashes_index
177            .checked_sub(1)
178            .expect("`slot_hashes_index` is positive when computing `ancestor_slot`")]
179        .0;
180
181        // Find if this slot in the proposed vote state exists in the SlotHashes history
182        // to confirm if it was a valid ancestor on this fork
183        match proposed_vote_slot.cmp(&ancestor_slot) {
184            Ordering::Less => {
185                if slot_hashes_index == slot_hashes.len() {
186                    // The vote slot does not exist in the SlotHashes history because it's too old,
187                    // i.e. older than the oldest slot in the history.
188                    if proposed_vote_slot >= earliest_slot_hash_in_history {
189                        return Err(VoteError::AssertionFailed);
190                    }
191                    if !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
192                        // If the vote slot is both:
193                        // 1) Too old
194                        // 2) Doesn't already exist in vote state
195                        //
196                        // Then filter it out
197                        proposed_lockouts_indices_to_filter.push(proposed_lockouts_index);
198                    }
199                    if let Some(new_proposed_root) = root_to_check {
200                        // 1. Because `root_to_check.is_some()`, then we know that
201                        // we haven't checked the root yet in this loop, so
202                        // `proposed_vote_slot` == `new_proposed_root` == `proposed_root`.
203                        assert_eq!(new_proposed_root, proposed_vote_slot);
204                        // 2. We know from the assert earlier in the function that
205                        // `proposed_vote_slot < earliest_slot_hash_in_history`,
206                        // so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history`.
207                        if new_proposed_root >= earliest_slot_hash_in_history {
208                            return Err(VoteError::AssertionFailed);
209                        }
210                        root_to_check = None;
211                    } else {
212                        proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
213                            "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
214                             `proposed_vote_slot` is too old to be in SlotHashes history",
215                        );
216                    }
217                    continue;
218                } else {
219                    // If the vote slot is new enough to be in the slot history,
220                    // but is not part of the slot history, then it must belong to another fork,
221                    // which means this proposed vote state is invalid.
222                    if root_to_check.is_some() {
223                        return Err(VoteError::RootOnDifferentFork);
224                    } else {
225                        return Err(VoteError::SlotsMismatch);
226                    }
227                }
228            }
229            Ordering::Greater => {
230                // Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
231                slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
232                    "`slot_hashes_index` is positive when finding newer slots in SlotHashes \
233                     history",
234                );
235                continue;
236            }
237            Ordering::Equal => {
238                // Once the slot in `proposed_lockouts` is found, bump to the next slot
239                // in `proposed_lockouts` and continue. If we were checking the root,
240                // start checking the vote state instead.
241                if root_to_check.is_some() {
242                    root_to_check = None;
243                } else {
244                    proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
245                        "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when match \
246                         is found in SlotHashes history",
247                    );
248                    slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
249                        "`slot_hashes_index` is positive when match is found in SlotHashes history",
250                    );
251                }
252            }
253        }
254    }
255
256    if proposed_lockouts_index != proposed_lockouts.len() {
257        // The last vote slot in the proposed vote state did not exist in SlotHashes
258        return Err(VoteError::SlotsMismatch);
259    }
260
261    // This assertion must be true at this point because we can assume by now:
262    // 1) proposed_lockouts_index == proposed_lockouts.len()
263    // 2) last_proposed_slot >= earliest_slot_hash_in_history
264    // 3) !proposed_lockouts.is_empty()
265    //
266    // 1) implies that during the last iteration of the loop above,
267    // `proposed_lockouts_index` was equal to `proposed_lockouts.len() - 1`,
268    // and was then incremented to `proposed_lockouts.len()`.
269    // This means in that last loop iteration,
270    // `proposed_vote_slot ==
271    //  proposed_lockouts[proposed_lockouts.len() - 1] ==
272    //  last_proposed_slot`.
273    //
274    // Then we know the last comparison `match proposed_vote_slot.cmp(&ancestor_slot)`
275    // is equivalent to `match last_proposed_slot.cmp(&ancestor_slot)`. The result
276    // of this match to increment `proposed_lockouts_index` must have been either:
277    //
278    // 1) The Equal case ran, in which case then we know this assertion must be true
279    // 2) The Less case ran, and more specifically the case
280    // `proposed_vote_slot < earliest_slot_hash_in_history` ran, which is equivalent to
281    // `last_proposed_slot < earliest_slot_hash_in_history`, but this is impossible
282    // due to assumption 3) above.
283    assert_eq!(last_proposed_slot, slot_hashes[slot_hashes_index].0);
284
285    if slot_hashes[slot_hashes_index].1 != proposed_hash {
286        // This means the newest vote in the slot has a match that
287        // doesn't match the expected hash for that slot on this
288        // fork
289        warn!(
290            "{} dropped vote {:?} root {:?} failed to match hash {} {}",
291            vote_state.node_pubkey(),
292            proposed_lockouts,
293            proposed_root,
294            proposed_hash,
295            slot_hashes[slot_hashes_index].1
296        );
297        #[cfg(feature = "metrics")]
298        inc_new_counter_info!("dropped-vote-hash", 1);
299        return Err(VoteError::SlotHashMismatch);
300    }
301
302    // Filter out the irrelevant votes
303    let mut proposed_lockouts_index = 0;
304    let mut filter_votes_index = 0;
305    proposed_lockouts.retain(|_lockout| {
306        let should_retain = if filter_votes_index == proposed_lockouts_indices_to_filter.len() {
307            true
308        } else if proposed_lockouts_index == proposed_lockouts_indices_to_filter[filter_votes_index]
309        {
310            filter_votes_index = filter_votes_index.checked_add(1).unwrap();
311            false
312        } else {
313            true
314        };
315
316        proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
317            "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when filtering out \
318             irrelevant votes",
319        );
320        should_retain
321    });
322
323    Ok(())
324}
325
326fn check_slots_are_valid<T: VoteStateHandle>(
327    vote_state: &T,
328    vote_slots: &[Slot],
329    vote_hash: &Hash,
330    slot_hashes: &[(Slot, Hash)],
331) -> Result<(), VoteError> {
332    // index into the vote's slots, starting at the oldest
333    // slot
334    let mut i = 0;
335
336    // index into the slot_hashes, starting at the oldest known
337    // slot hash
338    let mut j = slot_hashes.len();
339
340    // Note:
341    //
342    // 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
343    // vote, due to the way votes are applied to the vote state (newest votes
344    // pushed to the back).
345    //
346    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
347    // the oldest/smallest vote
348    while i < vote_slots.len() && j > 0 {
349        // 1) increment `i` to find the smallest slot `s` in `vote_slots`
350        // where `s` >= `last_voted_slot`
351        if vote_state
352            .last_voted_slot()
353            .is_some_and(|last_voted_slot| vote_slots[i] <= last_voted_slot)
354        {
355            i = i
356                .checked_add(1)
357                .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots");
358            continue;
359        }
360
361        // 2) Find the hash for this slot `s`.
362        if vote_slots[i] != slot_hashes[j.checked_sub(1).expect("`j` is positive")].0 {
363            // Decrement `j` to find newer slots
364            j = j
365                .checked_sub(1)
366                .expect("`j` is positive when finding newer slots");
367            continue;
368        }
369
370        // 3) Once the hash for `s` is found, bump `s` to the next slot
371        // in `vote_slots` and continue.
372        i = i
373            .checked_add(1)
374            .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found");
375        j = j
376            .checked_sub(1)
377            .expect("`j` is positive when hash is found");
378    }
379
380    if j == slot_hashes.len() {
381        // This means we never made it to steps 2) or 3) above, otherwise
382        // `j` would have been decremented at least once. This means
383        // there are not slots in `vote_slots` greater than `last_voted_slot`
384        debug!(
385            "{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
386            vote_state.node_pubkey(),
387            vote_slots,
388            vote_hash,
389            slot_hashes
390        );
391        return Err(VoteError::VoteTooOld);
392    }
393    if i != vote_slots.len() {
394        // This means there existed some slot for which we couldn't find
395        // a matching slot hash in step 2)
396        info!(
397            "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
398            vote_state.node_pubkey(),
399            vote_slots,
400            slot_hashes,
401        );
402        return Err(VoteError::SlotsMismatch);
403    }
404    if &slot_hashes[j].1 != vote_hash {
405        // This means the newest slot in the `vote_slots` has a match that
406        // doesn't match the expected hash for that slot on this
407        // fork
408        warn!(
409            "{} dropped vote slots {:?} failed to match hash {} {}",
410            vote_state.node_pubkey(),
411            vote_slots,
412            vote_hash,
413            slot_hashes[j].1
414        );
415        return Err(VoteError::SlotHashMismatch);
416    }
417    Ok(())
418}
419
420// Ensure `check_and_filter_proposed_vote_state(&)` runs on the slots in `new_state`
421// before `process_new_vote_state()` is called
422
423// This function should guarantee the following about `new_state`:
424//
425// 1) It's well ordered, i.e. the slots are sorted from smallest to largest,
426// and the confirmations sorted from largest to smallest.
427// 2) Confirmations `c` on any vote slot satisfy `0 < c <= MAX_LOCKOUT_HISTORY`
428// 3) Lockouts are not expired by consecutive votes, i.e. for every consecutive
429// `v_i`, `v_{i + 1}` satisfy `v_i.last_locked_out_slot() >= v_{i + 1}`.
430
431// We also guarantee that compared to the current vote state, `new_state`
432// introduces no rollback. This means:
433//
434// 1) The last slot in `new_state` is always greater than any slot in the
435// current vote state.
436//
437// 2) From 1), this means that for every vote `s` in the current state:
438//    a) If there exists an `s'` in `new_state` where `s.slot == s'.slot`, then
439//    we must guarantee `s.confirmations <= s'.confirmations`
440//
441//    b) If there does not exist any such `s'` in `new_state`, then there exists
442//    some `t` that is the smallest vote in `new_state` where `t.slot > s.slot`.
443//    `t` must have expired/popped off s', so it must be guaranteed that
444//    `s.last_locked_out_slot() < t`.
445
446// Note these two above checks do not guarantee that the vote state being submitted
447// is a vote state that could have been created by iteratively building a tower
448// by processing one vote at a time. For instance, the tower:
449//
450// { slot 0, confirmations: 31 }
451// { slot 1, confirmations: 30 }
452//
453// is a legal tower that could be submitted on top of a previously empty tower. However,
454// there is no way to create this tower from the iterative process, because slot 1 would
455// have to have at least one other slot on top of it, even if the first 30 votes were all
456// popped off.
457pub fn process_new_vote_state(
458    vote_state: &mut VoteStateHandler,
459    mut new_state: VecDeque<LandedVote>,
460    new_root: Option<Slot>,
461    timestamp: Option<i64>,
462    epoch: Epoch,
463    current_slot: Slot,
464) -> Result<(), VoteError> {
465    assert!(!new_state.is_empty());
466    if new_state.len() > MAX_LOCKOUT_HISTORY {
467        return Err(VoteError::TooManyVotes);
468    }
469
470    match (new_root, vote_state.root_slot()) {
471        (Some(new_root), Some(current_root)) => {
472            if new_root < current_root {
473                return Err(VoteError::RootRollBack);
474            }
475        }
476        (None, Some(_)) => {
477            return Err(VoteError::RootRollBack);
478        }
479        _ => (),
480    }
481
482    let mut previous_vote: Option<&LandedVote> = None;
483
484    // Check that all the votes in the new proposed state are:
485    // 1) Strictly sorted from oldest to newest vote
486    // 2) The confirmations are strictly decreasing
487    // 3) Not zero confirmation votes
488    for vote in &new_state {
489        if vote.confirmation_count() == 0 {
490            return Err(VoteError::ZeroConfirmations);
491        } else if vote.confirmation_count() > MAX_LOCKOUT_HISTORY as u32 {
492            return Err(VoteError::ConfirmationTooLarge);
493        } else if let Some(new_root) = new_root {
494            if vote.slot() <= new_root
495                &&
496                // This check is necessary because
497                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L120
498                // always sets a root for even empty towers, which is then hard unwrapped here
499                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L776
500                new_root != Slot::default()
501            {
502                return Err(VoteError::SlotSmallerThanRoot);
503            }
504        }
505
506        if let Some(previous_vote) = previous_vote {
507            if previous_vote.slot() >= vote.slot() {
508                return Err(VoteError::SlotsNotOrdered);
509            } else if previous_vote.confirmation_count() <= vote.confirmation_count() {
510                return Err(VoteError::ConfirmationsNotOrdered);
511            } else if vote.slot() > previous_vote.lockout.last_locked_out_slot() {
512                return Err(VoteError::NewVoteStateLockoutMismatch);
513            }
514        }
515        previous_vote = Some(vote);
516    }
517
518    // Find the first vote in the current vote state for a slot greater
519    // than the new proposed root
520    let mut current_vote_state_index: usize = 0;
521    let mut new_vote_state_index = 0;
522
523    // Accumulate credits earned by newly rooted slots
524    let mut earned_credits = 0_u64;
525
526    if let Some(new_root) = new_root {
527        for current_vote in vote_state.votes() {
528            // Find the first vote in the current vote state for a slot greater
529            // than the new proposed root
530            if current_vote.slot() <= new_root {
531                earned_credits = earned_credits
532                    .checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index))
533                    .expect("`earned_credits` does not overflow");
534                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
535                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
536                     processing new root",
537                );
538                continue;
539            }
540
541            break;
542        }
543    }
544
545    // For any slots newly added to the new vote state, the vote latency of that slot is not provided by the
546    // vote instruction contents, but instead is computed from the actual latency of the vote
547    // instruction. This prevents other validators from manipulating their own vote latencies within their vote states
548    // and forcing the rest of the cluster to accept these possibly fraudulent latency values.  If the
549    // timly_vote_credits feature is not enabled then vote latency is set to 0 for new votes.
550    //
551    // For any slot that is in both the new state and the current state, the vote latency of the new state is taken
552    // from the current state.
553    //
554    // Thus vote latencies are set here for any newly vote-on slots when a vote instruction is received.
555    // They are copied into the new vote state after every vote for already voted-on slots.
556    // And when voted-on slots are rooted, the vote latencies stored in the vote state of all the rooted slots is used
557    // to compute credits earned.
558    // All validators compute the same vote latencies because all process the same vote instruction at the
559    // same slot, and the only time vote latencies are ever computed is at the time that their slot is first voted on;
560    // after that, the latencies are retained unaltered until the slot is rooted.
561
562    // All the votes in our current vote state that are missing from the new vote state
563    // must have been expired by later votes. Check that the lockouts match this assumption.
564    while current_vote_state_index < vote_state.votes().len()
565        && new_vote_state_index < new_state.len()
566    {
567        let current_vote = &vote_state.votes()[current_vote_state_index];
568        let new_vote = &mut new_state[new_vote_state_index];
569
570        // If the current slot is less than the new proposed slot, then the
571        // new slot must have popped off the old slot, so check that the
572        // lockouts are corrects.
573        match current_vote.slot().cmp(&new_vote.slot()) {
574            Ordering::Less => {
575                if current_vote.lockout.last_locked_out_slot() >= new_vote.slot() {
576                    return Err(VoteError::LockoutConflict);
577                }
578                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
579                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
580                     less than proposed",
581                );
582            }
583            Ordering::Equal => {
584                // The new vote state should never have less lockout than
585                // the previous vote state for the same slot
586                if new_vote.confirmation_count() < current_vote.confirmation_count() {
587                    return Err(VoteError::ConfirmationRollBack);
588                }
589
590                // Copy the vote slot latency in from the current state to the new state
591                new_vote.latency = vote_state.votes()[current_vote_state_index].latency;
592
593                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
594                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
595                     equal to proposed",
596                );
597                new_vote_state_index = new_vote_state_index.checked_add(1).expect(
598                    "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
599                     equal to proposed",
600                );
601            }
602            Ordering::Greater => {
603                new_vote_state_index = new_vote_state_index.checked_add(1).expect(
604                    "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
605                     greater than proposed",
606                );
607            }
608        }
609    }
610
611    // `new_vote_state` passed all the checks, finalize the change by rewriting
612    // our state.
613
614    // Now set the vote latencies on new slots not in the current state.  New slots not in the current vote state will
615    // have had their latency initialized to 0 by the above loop.  Those will now be updated to their actual latency.
616    for new_vote in new_state.iter_mut() {
617        if new_vote.latency == 0 {
618            new_vote.latency = handler::compute_vote_latency(new_vote.slot(), current_slot);
619        }
620    }
621
622    if vote_state.root_slot() != new_root {
623        // Award vote credits based on the number of slots that were voted on and have reached finality
624        // For each finalized slot, there was one voted-on slot in the new vote state that was responsible for
625        // finalizing it. Each of those votes is awarded 1 credit.
626        vote_state.increment_credits(epoch, earned_credits);
627    }
628    if let Some(timestamp) = timestamp {
629        let last_slot = new_state.back().unwrap().slot();
630        vote_state.process_timestamp(last_slot, timestamp)?;
631    }
632    vote_state.set_root_slot(new_root);
633    vote_state.set_votes(new_state);
634
635    Ok(())
636}
637
638pub fn process_vote_unfiltered<T: VoteStateHandle>(
639    vote_state: &mut T,
640    vote_slots: &[Slot],
641    vote: &Vote,
642    slot_hashes: &[SlotHash],
643    epoch: Epoch,
644    current_slot: Slot,
645) -> Result<(), VoteError> {
646    check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?;
647    vote_slots
648        .iter()
649        .for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot));
650    Ok(())
651}
652
653pub fn process_vote(
654    vote_state: &mut VoteStateHandler,
655    vote: &Vote,
656    slot_hashes: &[SlotHash],
657    epoch: Epoch,
658    current_slot: Slot,
659) -> Result<(), VoteError> {
660    if vote.slots.is_empty() {
661        return Err(VoteError::EmptySlots);
662    }
663    let earliest_slot_in_history = slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
664    let vote_slots = vote
665        .slots
666        .iter()
667        .filter(|slot| **slot >= earliest_slot_in_history)
668        .cloned()
669        .collect::<Vec<Slot>>();
670    if vote_slots.is_empty() {
671        return Err(VoteError::VotesTooOldAllFiltered);
672    }
673    process_vote_unfiltered(
674        vote_state,
675        &vote_slots,
676        vote,
677        slot_hashes,
678        epoch,
679        current_slot,
680    )
681}
682
683/// "unchecked" functions used by tests and Tower
684pub fn process_vote_unchecked<T: VoteStateHandle>(
685    vote_state: &mut T,
686    vote: Vote,
687) -> Result<(), VoteError> {
688    if vote.slots.is_empty() {
689        return Err(VoteError::EmptySlots);
690    }
691    let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
692    process_vote_unfiltered(
693        vote_state,
694        &vote.slots,
695        &vote,
696        &slot_hashes,
697        vote_state.current_epoch(),
698        0,
699    )
700}
701
702#[cfg(test)]
703pub fn process_slot_votes_unchecked<T: VoteStateHandle>(vote_state: &mut T, slots: &[Slot]) {
704    for slot in slots {
705        process_slot_vote_unchecked(vote_state, *slot);
706    }
707}
708
709pub fn process_slot_vote_unchecked<T: VoteStateHandle>(vote_state: &mut T, slot: Slot) {
710    let _ = process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
711}
712
713/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
714/// but will implicitly withdraw authorization from the previously authorized
715/// key
716pub fn authorize<S: std::hash::BuildHasher>(
717    vote_account: &mut BorrowedInstructionAccount,
718    target_version: VoteStateTargetVersion,
719    authorized: &Pubkey,
720    vote_authorize: VoteAuthorize,
721    signers: &HashSet<Pubkey, S>,
722    clock: &Clock,
723) -> Result<(), InstructionError> {
724    let mut vote_state = get_vote_state_handler_checked(
725        vote_account,
726        PreserveBehaviorInHandlerHelper::new(target_version, false),
727    )?;
728
729    match vote_authorize {
730        VoteAuthorize::Voter => {
731            let authorized_withdrawer_signer =
732                verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
733
734            vote_state.set_new_authorized_voter(
735                authorized,
736                clock.epoch,
737                clock
738                    .leader_schedule_epoch
739                    .checked_add(1)
740                    .ok_or(InstructionError::InvalidAccountData)?,
741                |epoch_authorized_voter| {
742                    // current authorized withdrawer or authorized voter must say "yay"
743                    if authorized_withdrawer_signer {
744                        Ok(())
745                    } else {
746                        verify_authorized_signer(&epoch_authorized_voter, signers)
747                    }
748                },
749            )?;
750        }
751        VoteAuthorize::Withdrawer => {
752            // current authorized withdrawer must say "yay"
753            verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
754            vote_state.set_authorized_withdrawer(*authorized);
755        }
756    }
757
758    vote_state.set_vote_account_state(vote_account)
759}
760
761/// Update the node_pubkey, requires signature of the authorized voter
762pub fn update_validator_identity<S: std::hash::BuildHasher>(
763    vote_account: &mut BorrowedInstructionAccount,
764    target_version: VoteStateTargetVersion,
765    node_pubkey: &Pubkey,
766    signers: &HashSet<Pubkey, S>,
767) -> Result<(), InstructionError> {
768    let mut vote_state = get_vote_state_handler_checked(
769        vote_account,
770        PreserveBehaviorInHandlerHelper::new(target_version, false),
771    )?;
772
773    // current authorized withdrawer must say "yay"
774    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
775
776    // new node must say "yay"
777    verify_authorized_signer(node_pubkey, signers)?;
778
779    vote_state.set_node_pubkey(*node_pubkey);
780    // Keep block_revenue_collector in sync with node_pubkey until SIMD-0232
781    // is implemented.
782    vote_state.set_block_revenue_collector(*node_pubkey);
783
784    vote_state.set_vote_account_state(vote_account)
785}
786
787/// Update the vote account's commission
788pub fn update_commission<S: std::hash::BuildHasher>(
789    vote_account: &mut BorrowedInstructionAccount,
790    target_version: VoteStateTargetVersion,
791    commission: u8,
792    signers: &HashSet<Pubkey, S>,
793    epoch_schedule: &EpochSchedule,
794    clock: &Clock,
795) -> Result<(), InstructionError> {
796    let vote_state_result = get_vote_state_handler_checked(
797        vote_account,
798        PreserveBehaviorInHandlerHelper::new(target_version, false),
799    );
800    let enforce_commission_update_rule = if let Ok(decoded_vote_state) = &vote_state_result {
801        commission > decoded_vote_state.commission()
802    } else {
803        true
804    };
805
806    if enforce_commission_update_rule && !is_commission_update_allowed(clock.slot, epoch_schedule) {
807        return Err(VoteError::CommissionUpdateTooLate.into());
808    }
809
810    let mut vote_state = vote_state_result?;
811
812    // current authorized withdrawer must say "yay"
813    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
814
815    vote_state.set_commission(commission);
816
817    vote_state.set_vote_account_state(vote_account)
818}
819
820/// Given the current slot and epoch schedule, determine if a commission change
821/// is allowed
822pub fn is_commission_update_allowed(slot: Slot, epoch_schedule: &EpochSchedule) -> bool {
823    // always allowed during warmup epochs
824    if let Some(relative_slot) = slot
825        .saturating_sub(epoch_schedule.first_normal_slot)
826        .checked_rem(epoch_schedule.slots_per_epoch)
827    {
828        // allowed up to the midpoint of the epoch
829        relative_slot.saturating_mul(2) <= epoch_schedule.slots_per_epoch
830    } else {
831        // no slots per epoch, just allow it, even though this should never happen
832        true
833    }
834}
835
836fn verify_authorized_signer<S: std::hash::BuildHasher>(
837    authorized: &Pubkey,
838    signers: &HashSet<Pubkey, S>,
839) -> Result<(), InstructionError> {
840    if signers.contains(authorized) {
841        Ok(())
842    } else {
843        Err(InstructionError::MissingRequiredSignature)
844    }
845}
846
847/// Withdraw funds from the vote account
848pub fn withdraw<S: std::hash::BuildHasher>(
849    instruction_context: &InstructionContext,
850    vote_account_index: IndexOfAccount,
851    target_version: VoteStateTargetVersion,
852    lamports: u64,
853    to_account_index: IndexOfAccount,
854    signers: &HashSet<Pubkey, S>,
855    rent_sysvar: &Rent,
856    clock: &Clock,
857) -> Result<(), InstructionError> {
858    let mut vote_account =
859        instruction_context.try_borrow_instruction_account(vote_account_index)?;
860    let vote_state = get_vote_state_handler_checked(
861        &vote_account,
862        PreserveBehaviorInHandlerHelper::new(target_version, false),
863    )?;
864
865    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
866
867    let remaining_balance = vote_account
868        .get_lamports()
869        .checked_sub(lamports)
870        .ok_or(InstructionError::InsufficientFunds)?;
871
872    if remaining_balance == 0 {
873        let reject_active_vote_account_close = vote_state
874            .epoch_credits()
875            .last()
876            .map(|(last_epoch_with_credits, _, _)| {
877                let current_epoch = clock.epoch;
878                // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits
879                // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least
880                // one full epoch since the validator has received credits.
881                current_epoch.saturating_sub(*last_epoch_with_credits) < 2
882            })
883            .unwrap_or(false);
884
885        if reject_active_vote_account_close {
886            return Err(VoteError::ActiveVoteAccountClose.into());
887        } else {
888            // Deinitialize upon zero-balance
889            VoteStateHandler::deinitialize_vote_account_state(&mut vote_account, target_version)?;
890        }
891    } else {
892        let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
893        if remaining_balance < min_rent_exempt_balance {
894            return Err(InstructionError::InsufficientFunds);
895        }
896    }
897
898    vote_account.checked_sub_lamports(lamports)?;
899    drop(vote_account);
900    let mut to_account = instruction_context.try_borrow_instruction_account(to_account_index)?;
901    to_account.checked_add_lamports(lamports)?;
902    Ok(())
903}
904
905/// Initialize the vote_state for a vote account
906/// Assumes that the account is being init as part of a account creation or balance transfer and
907/// that the transaction must be signed by the staker's keys
908pub fn initialize_account<S: std::hash::BuildHasher>(
909    vote_account: &mut BorrowedInstructionAccount,
910    target_version: VoteStateTargetVersion,
911    vote_init: &VoteInit,
912    signers: &HashSet<Pubkey, S>,
913    clock: &Clock,
914) -> Result<(), InstructionError> {
915    VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
916    let versioned = vote_account.get_state::<VoteStateVersions>()?;
917
918    if !versioned.is_uninitialized() {
919        return Err(InstructionError::AccountAlreadyInitialized);
920    }
921
922    // node must agree to accept this vote account
923    verify_authorized_signer(&vote_init.node_pubkey, signers)?;
924
925    VoteStateHandler::init_vote_account_state(vote_account, vote_init, clock, target_version)
926}
927
928pub fn process_vote_with_account<S: std::hash::BuildHasher>(
929    vote_account: &mut BorrowedInstructionAccount,
930    target_version: VoteStateTargetVersion,
931    slot_hashes: &[SlotHash],
932    clock: &Clock,
933    vote: &Vote,
934    signers: &HashSet<Pubkey, S>,
935) -> Result<(), InstructionError> {
936    let mut vote_state = get_vote_state_handler_checked(
937        vote_account,
938        PreserveBehaviorInHandlerHelper::new(target_version, true),
939    )?;
940
941    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
942    verify_authorized_signer(&authorized_voter, signers)?;
943
944    process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?;
945    if let Some(timestamp) = vote.timestamp {
946        vote.slots
947            .iter()
948            .max()
949            .ok_or(VoteError::EmptySlots)
950            .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
951    }
952    vote_state.set_vote_account_state(vote_account)
953}
954
955pub fn process_vote_state_update<S: std::hash::BuildHasher>(
956    vote_account: &mut BorrowedInstructionAccount,
957    target_version: VoteStateTargetVersion,
958    slot_hashes: &[SlotHash],
959    clock: &Clock,
960    vote_state_update: VoteStateUpdate,
961    signers: &HashSet<Pubkey, S>,
962) -> Result<(), InstructionError> {
963    let mut vote_state = get_vote_state_handler_checked(
964        vote_account,
965        PreserveBehaviorInHandlerHelper::new(target_version, true),
966    )?;
967
968    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
969    verify_authorized_signer(&authorized_voter, signers)?;
970
971    do_process_vote_state_update(
972        &mut vote_state,
973        slot_hashes,
974        clock.epoch,
975        clock.slot,
976        vote_state_update,
977    )?;
978    vote_state.set_vote_account_state(vote_account)
979}
980
981pub fn do_process_vote_state_update(
982    vote_state: &mut VoteStateHandler,
983    slot_hashes: &[SlotHash],
984    epoch: u64,
985    slot: u64,
986    mut vote_state_update: VoteStateUpdate,
987) -> Result<(), VoteError> {
988    check_and_filter_proposed_vote_state(
989        vote_state,
990        &mut vote_state_update.lockouts,
991        &mut vote_state_update.root,
992        vote_state_update.hash,
993        slot_hashes,
994    )?;
995    process_new_vote_state(
996        vote_state,
997        vote_state_update
998            .lockouts
999            .iter()
1000            .map(|lockout| LandedVote::from(*lockout))
1001            .collect(),
1002        vote_state_update.root,
1003        vote_state_update.timestamp,
1004        epoch,
1005        slot,
1006    )
1007}
1008
1009pub fn process_tower_sync<S: std::hash::BuildHasher>(
1010    vote_account: &mut BorrowedInstructionAccount,
1011    target_version: VoteStateTargetVersion,
1012    slot_hashes: &[SlotHash],
1013    clock: &Clock,
1014    tower_sync: TowerSync,
1015    signers: &HashSet<Pubkey, S>,
1016) -> Result<(), InstructionError> {
1017    let mut vote_state = get_vote_state_handler_checked(
1018        vote_account,
1019        PreserveBehaviorInHandlerHelper::new(target_version, true),
1020    )?;
1021
1022    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1023    verify_authorized_signer(&authorized_voter, signers)?;
1024
1025    do_process_tower_sync(
1026        &mut vote_state,
1027        slot_hashes,
1028        clock.epoch,
1029        clock.slot,
1030        tower_sync,
1031    )?;
1032    vote_state.set_vote_account_state(vote_account)
1033}
1034
1035fn do_process_tower_sync(
1036    vote_state: &mut VoteStateHandler,
1037    slot_hashes: &[SlotHash],
1038    epoch: u64,
1039    slot: u64,
1040    mut tower_sync: TowerSync,
1041) -> Result<(), VoteError> {
1042    check_and_filter_proposed_vote_state(
1043        vote_state,
1044        &mut tower_sync.lockouts,
1045        &mut tower_sync.root,
1046        tower_sync.hash,
1047        slot_hashes,
1048    )?;
1049    process_new_vote_state(
1050        vote_state,
1051        tower_sync
1052            .lockouts
1053            .iter()
1054            .map(|lockout| LandedVote::from(*lockout))
1055            .collect(),
1056        tower_sync.root,
1057        tower_sync.timestamp,
1058        epoch,
1059        slot,
1060    )
1061}
1062
1063#[cfg(test)]
1064pub fn create_account_with_authorized(
1065    node_pubkey: &Pubkey,
1066    authorized_voter: &Pubkey,
1067    authorized_withdrawer: &Pubkey,
1068    commission: u8,
1069    lamports: u64,
1070) -> AccountSharedData {
1071    let mut vote_account = AccountSharedData::new(lamports, VoteStateV3::size_of(), &id());
1072
1073    let vote_state = VoteStateV3::new(
1074        &VoteInit {
1075            node_pubkey: *node_pubkey,
1076            authorized_voter: *authorized_voter,
1077            authorized_withdrawer: *authorized_withdrawer,
1078            commission,
1079        },
1080        &Clock::default(),
1081    );
1082
1083    VoteStateV3::serialize(
1084        &VoteStateVersions::V3(Box::new(vote_state)),
1085        vote_account.data_as_mut_slice(),
1086    )
1087    .unwrap();
1088
1089    vote_account
1090}
1091
1092pub fn create_v4_account_with_authorized(
1093    node_pubkey: &Pubkey,
1094    authorized_voter: &Pubkey,
1095    authorized_withdrawer: &Pubkey,
1096    bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
1097    inflation_rewards_commission_bps: u16,
1098    lamports: u64,
1099) -> AccountSharedData {
1100    let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id());
1101
1102    let vote_state = handler::create_new_vote_state_v4_for_tests(
1103        node_pubkey,
1104        authorized_voter,
1105        authorized_withdrawer,
1106        bls_pubkey_compressed,
1107        inflation_rewards_commission_bps,
1108    );
1109
1110    VoteStateV4::serialize(
1111        &VoteStateVersions::V4(Box::new(vote_state)),
1112        vote_account.data_as_mut_slice(),
1113    )
1114    .unwrap();
1115
1116    vote_account
1117}
1118
1119#[allow(clippy::arithmetic_side_effects)]
1120#[cfg(test)]
1121mod tests {
1122    use {
1123        super::*,
1124        assert_matches::assert_matches,
1125        solana_account::{AccountSharedData, ReadableAccount},
1126        solana_clock::DEFAULT_SLOTS_PER_EPOCH,
1127        solana_sha256_hasher::hash,
1128        solana_transaction_context::{InstructionAccount, TransactionContext},
1129        solana_vote_interface::authorized_voters::AuthorizedVoters,
1130        test_case::test_case,
1131    };
1132
1133    const MAX_RECENT_VOTES: usize = 16;
1134
1135    fn vote_state_new_for_test(
1136        vote_pubkey: &Pubkey,
1137        target_version: VoteStateTargetVersion,
1138    ) -> VoteStateHandler {
1139        let auth_pubkey = solana_pubkey::new_rand();
1140        let vote_init = VoteInit {
1141            node_pubkey: solana_pubkey::new_rand(),
1142            authorized_voter: auth_pubkey,
1143            authorized_withdrawer: auth_pubkey,
1144            commission: 0,
1145        };
1146        let clock = Clock::default();
1147
1148        match target_version {
1149            VoteStateTargetVersion::V3 => {
1150                VoteStateHandler::new_v3(VoteStateV3::new(&vote_init, &clock))
1151            }
1152            VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(
1153                handler::create_new_vote_state_v4(vote_pubkey, &vote_init, &clock),
1154            ),
1155        }
1156    }
1157
1158    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1159    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1160    fn test_vote_state_upgrade_from_1_14_11(target_version: VoteStateTargetVersion) {
1161        let vote_pubkey = solana_pubkey::new_rand();
1162        let mut vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
1163
1164        // Simulate prior epochs completed with credits and each setting a new authorized voter
1165        vote_state.increment_credits(0, 100);
1166        assert_eq!(
1167            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 0, 1, |_pubkey| Ok(())),
1168            Ok(())
1169        );
1170        vote_state.increment_credits(1, 200);
1171        assert_eq!(
1172            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 1, 2, |_pubkey| Ok(())),
1173            Ok(())
1174        );
1175        vote_state.increment_credits(2, 300);
1176        assert_eq!(
1177            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 2, 3, |_pubkey| Ok(())),
1178            Ok(())
1179        );
1180
1181        // Simulate votes having occurred
1182        vec![
1183            100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
1184            117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
1185            134, 135,
1186        ]
1187        .into_iter()
1188        .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0));
1189
1190        // Create an initial vote account that is sized for the 1_14_11 version of vote state, and has only the
1191        // required lamports for rent exempt minimum at that size
1192        let vote_state_v1_14_11 = match target_version {
1193            VoteStateTargetVersion::V3 => {
1194                // v3 can be converted directly to V1_14_11.
1195                VoteState1_14_11::from(vote_state.as_ref_v3().clone())
1196            }
1197            VoteStateTargetVersion::V4 => {
1198                // v4 cannot be converted directly to V1_14_11.
1199                VoteState1_14_11 {
1200                    node_pubkey: *vote_state.node_pubkey(),
1201                    authorized_withdrawer: *vote_state.authorized_withdrawer(),
1202                    commission: vote_state.commission(),
1203                    votes: vote_state
1204                        .votes()
1205                        .iter()
1206                        .map(|landed_vote| (*landed_vote).into())
1207                        .collect(),
1208                    root_slot: vote_state.root_slot(),
1209                    authorized_voters: vote_state.authorized_voters().clone(),
1210                    epoch_credits: vote_state.epoch_credits().clone(),
1211                    last_timestamp: vote_state.last_timestamp().clone(),
1212                    prior_voters: CircBuf::default(), // v4 does not store prior_voters
1213                }
1214            }
1215        };
1216        let version1_14_11_serialized =
1217            bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(vote_state_v1_14_11)))
1218                .unwrap();
1219        let version1_14_11_serialized_len = version1_14_11_serialized.len();
1220        let rent = Rent::default();
1221        let lamports = rent.minimum_balance(version1_14_11_serialized_len);
1222        let mut vote_account =
1223            AccountSharedData::new(lamports, version1_14_11_serialized_len, &id());
1224        vote_account.set_data_from_slice(&version1_14_11_serialized);
1225
1226        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1227        // vote account that was just created
1228        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1229        let mut transaction_context = TransactionContext::new(
1230            vec![(id(), processor_account), (vote_pubkey, vote_account)],
1231            rent.clone(),
1232            0,
1233            0,
1234        );
1235        transaction_context
1236            .configure_next_instruction_for_tests(
1237                0,
1238                vec![InstructionAccount::new(1, false, true)],
1239                vec![],
1240            )
1241            .unwrap();
1242        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1243
1244        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1245        // state
1246        let mut borrowed_account = instruction_context
1247            .try_borrow_instruction_account(0)
1248            .unwrap();
1249
1250        // Ensure that the vote state started out at 1_14_11
1251        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1252        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1253
1254        // Convert the vote state to current as would occur during vote instructions
1255        let converted_vote_state = get_vote_state_handler_checked(
1256            &borrowed_account,
1257            PreserveBehaviorInHandlerHelper::new(target_version, true),
1258        )
1259        .unwrap();
1260
1261        // Check to make sure that the vote_state is unchanged
1262        assert!(vote_state == converted_vote_state);
1263
1264        let vote_state = converted_vote_state;
1265
1266        // Now re-set the vote account state, knowing the account only has
1267        // enough lamports for V1_14_11.
1268        match target_version {
1269            VoteStateTargetVersion::V3 => {
1270                // V3 will write out as V1_14_11.
1271                assert_eq!(
1272                    vote_state
1273                        .clone()
1274                        .set_vote_account_state(&mut borrowed_account),
1275                    Ok(())
1276                );
1277                let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1278                assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1279            }
1280            VoteStateTargetVersion::V4 => {
1281                // V4 will throw an error.
1282                assert_eq!(
1283                    vote_state
1284                        .clone()
1285                        .set_vote_account_state(&mut borrowed_account),
1286                    Err(InstructionError::AccountNotRentExempt)
1287                );
1288            }
1289        }
1290
1291        // Convert the vote state to current as would occur during vote instructions
1292        let converted_vote_state = get_vote_state_handler_checked(
1293            &borrowed_account,
1294            PreserveBehaviorInHandlerHelper::new(target_version, true),
1295        )
1296        .unwrap();
1297
1298        // Check to make sure that the vote_state is unchanged
1299        assert!(vote_state == converted_vote_state);
1300
1301        let vote_state = converted_vote_state;
1302
1303        // Now top-up the vote account's lamports to be rent exempt for the target version.
1304        let space = match target_version {
1305            VoteStateTargetVersion::V3 => VoteStateV3::size_of(),
1306            VoteStateTargetVersion::V4 => VoteStateV4::size_of(), // They're the same, but for posterity
1307        };
1308        assert_eq!(
1309            borrowed_account.set_lamports(rent.minimum_balance(space)),
1310            Ok(())
1311        );
1312        assert_eq!(
1313            vote_state
1314                .clone()
1315                .set_vote_account_state(&mut borrowed_account),
1316            Ok(())
1317        );
1318
1319        // The vote state version should match the target version.
1320        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1321        match target_version {
1322            VoteStateTargetVersion::V3 => {
1323                assert_matches!(vote_state_version, VoteStateVersions::V3(_));
1324            }
1325            VoteStateTargetVersion::V4 => {
1326                assert_matches!(vote_state_version, VoteStateVersions::V4(_));
1327            }
1328        }
1329
1330        // Convert the vote state to current as would occur during vote instructions
1331        let converted_vote_state = get_vote_state_handler_checked(
1332            &borrowed_account,
1333            PreserveBehaviorInHandlerHelper::new(target_version, true),
1334        )
1335        .unwrap();
1336
1337        // Check to make sure that the vote_state is unchanged
1338        assert_eq!(vote_state, converted_vote_state);
1339    }
1340
1341    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1342    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1343    fn test_vote_lockout(target_version: VoteStateTargetVersion) {
1344        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1345
1346        for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
1347            process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
1348        }
1349
1350        // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
1351        assert_eq!(vote_state.votes().len(), MAX_LOCKOUT_HISTORY);
1352        assert_eq!(vote_state.root_slot(), Some(0));
1353        check_lockouts(&vote_state);
1354
1355        // One more vote that confirms the entire stack,
1356        // the root_slot should change to the
1357        // second vote
1358        let top_vote = vote_state.votes().front().unwrap().slot();
1359        let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
1360        process_slot_vote_unchecked(&mut vote_state, slot);
1361        assert_eq!(Some(top_vote), vote_state.root_slot());
1362
1363        // Expire everything except the first vote
1364        let slot = vote_state
1365            .votes()
1366            .front()
1367            .unwrap()
1368            .lockout
1369            .last_locked_out_slot();
1370        process_slot_vote_unchecked(&mut vote_state, slot);
1371        // First vote and new vote are both stored for a total of 2 votes
1372        assert_eq!(vote_state.votes().len(), 2);
1373    }
1374
1375    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1376    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1377    fn test_update_commission(target_version: VoteStateTargetVersion) {
1378        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1379        let node_pubkey = *vote_state.node_pubkey();
1380        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1381
1382        // Set commission to start.
1383        vote_state.set_commission(10);
1384
1385        let serialized = vote_state.serialize();
1386        let serialized_len = serialized.len();
1387        let rent = Rent::default();
1388        let lamports = rent.minimum_balance(serialized_len);
1389        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1390        vote_account.set_data_from_slice(&serialized);
1391
1392        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1393        // vote account that was just created
1394        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1395        let mut transaction_context = TransactionContext::new(
1396            vec![(id(), processor_account), (node_pubkey, vote_account)],
1397            rent,
1398            0,
1399            0,
1400        );
1401        transaction_context
1402            .configure_next_instruction_for_tests(
1403                0,
1404                vec![InstructionAccount::new(1, false, true)],
1405                vec![],
1406            )
1407            .unwrap();
1408        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1409
1410        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1411        // state
1412        let mut borrowed_account = instruction_context
1413            .try_borrow_instruction_account(0)
1414            .unwrap();
1415
1416        let epoch_schedule = std::sync::Arc::new(EpochSchedule::without_warmup());
1417
1418        let first_half_clock = std::sync::Arc::new(Clock {
1419            slot: epoch_schedule.slots_per_epoch / 4,
1420            ..Clock::default()
1421        });
1422
1423        let second_half_clock = std::sync::Arc::new(Clock {
1424            slot: (epoch_schedule.slots_per_epoch * 3) / 4,
1425            ..Clock::default()
1426        });
1427
1428        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1429
1430        // Increase commission in first half of epoch -- allowed
1431        assert_eq!(
1432            get_vote_state_handler_checked(
1433                &borrowed_account,
1434                PreserveBehaviorInHandlerHelper::new(target_version, true),
1435            )
1436            .unwrap()
1437            .commission(),
1438            10
1439        );
1440        assert_matches!(
1441            update_commission(
1442                &mut borrowed_account,
1443                target_version,
1444                11,
1445                &signers,
1446                &epoch_schedule,
1447                &first_half_clock,
1448            ),
1449            Ok(())
1450        );
1451        assert_eq!(
1452            get_vote_state_handler_checked(
1453                &borrowed_account,
1454                PreserveBehaviorInHandlerHelper::new(target_version, true),
1455            )
1456            .unwrap()
1457            .commission(),
1458            11
1459        );
1460
1461        // Increase commission in second half of epoch -- disallowed
1462        assert_matches!(
1463            update_commission(
1464                &mut borrowed_account,
1465                target_version,
1466                12,
1467                &signers,
1468                &epoch_schedule,
1469                &second_half_clock,
1470            ),
1471            Err(_)
1472        );
1473        assert_eq!(
1474            get_vote_state_handler_checked(
1475                &borrowed_account,
1476                PreserveBehaviorInHandlerHelper::new(target_version, true),
1477            )
1478            .unwrap()
1479            .commission(),
1480            11
1481        );
1482
1483        // Decrease commission in first half of epoch -- allowed
1484        assert_matches!(
1485            update_commission(
1486                &mut borrowed_account,
1487                target_version,
1488                10,
1489                &signers,
1490                &epoch_schedule,
1491                &first_half_clock,
1492            ),
1493            Ok(())
1494        );
1495        assert_eq!(
1496            get_vote_state_handler_checked(
1497                &borrowed_account,
1498                PreserveBehaviorInHandlerHelper::new(target_version, true),
1499            )
1500            .unwrap()
1501            .commission(),
1502            10
1503        );
1504
1505        assert_eq!(
1506            get_vote_state_handler_checked(
1507                &borrowed_account,
1508                PreserveBehaviorInHandlerHelper::new(target_version, true),
1509            )
1510            .unwrap()
1511            .commission(),
1512            10
1513        );
1514
1515        assert_matches!(
1516            update_commission(
1517                &mut borrowed_account,
1518                target_version,
1519                9,
1520                &signers,
1521                &epoch_schedule,
1522                &second_half_clock,
1523            ),
1524            Ok(())
1525        );
1526        assert_eq!(
1527            get_vote_state_handler_checked(
1528                &borrowed_account,
1529                PreserveBehaviorInHandlerHelper::new(target_version, true),
1530            )
1531            .unwrap()
1532            .commission(),
1533            9
1534        );
1535    }
1536
1537    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1538    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1539    fn test_vote_double_lockout_after_expiration(target_version: VoteStateTargetVersion) {
1540        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1541
1542        for i in 0..3 {
1543            process_slot_vote_unchecked(&mut vote_state, i as u64);
1544        }
1545
1546        check_lockouts(&vote_state);
1547
1548        // Expire the third vote (which was a vote for slot 2). The height of the
1549        // vote stack is unchanged, so none of the previous votes should have
1550        // doubled in lockout
1551        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
1552        check_lockouts(&vote_state);
1553
1554        // Vote again, this time the vote stack depth increases, so the votes should
1555        // double for everybody
1556        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
1557        check_lockouts(&vote_state);
1558
1559        // Vote again, this time the vote stack depth increases, so the votes should
1560        // double for everybody
1561        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
1562        check_lockouts(&vote_state);
1563    }
1564
1565    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1566    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1567    fn test_expire_multiple_votes(target_version: VoteStateTargetVersion) {
1568        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1569
1570        for i in 0..3 {
1571            process_slot_vote_unchecked(&mut vote_state, i as u64);
1572        }
1573
1574        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1575
1576        // Expire the second and third votes
1577        let expire_slot =
1578            vote_state.votes()[1].slot() + vote_state.votes()[1].lockout.lockout() + 1;
1579        process_slot_vote_unchecked(&mut vote_state, expire_slot);
1580        assert_eq!(vote_state.votes().len(), 2);
1581
1582        // Check that the old votes expired
1583        assert_eq!(vote_state.votes()[0].slot(), 0);
1584        assert_eq!(vote_state.votes()[1].slot(), expire_slot);
1585
1586        // Process one more vote
1587        process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);
1588
1589        // Confirmation count for the older first vote should remain unchanged
1590        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1591
1592        // The later votes should still have increasing confirmation counts
1593        assert_eq!(vote_state.votes()[1].confirmation_count(), 2);
1594        assert_eq!(vote_state.votes()[2].confirmation_count(), 1);
1595    }
1596
1597    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1598    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1599    fn test_vote_credits(target_version: VoteStateTargetVersion) {
1600        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1601
1602        for i in 0..MAX_LOCKOUT_HISTORY {
1603            process_slot_vote_unchecked(&mut vote_state, i as u64);
1604        }
1605
1606        assert_eq!(vote_state.credits(), 0);
1607
1608        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
1609        assert_eq!(vote_state.credits(), 1);
1610        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
1611        assert_eq!(vote_state.credits(), 2);
1612        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
1613        assert_eq!(vote_state.credits(), 3);
1614    }
1615
1616    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1617    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1618    fn test_duplicate_vote(target_version: VoteStateTargetVersion) {
1619        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1620        process_slot_vote_unchecked(&mut vote_state, 0);
1621        process_slot_vote_unchecked(&mut vote_state, 1);
1622        process_slot_vote_unchecked(&mut vote_state, 0);
1623        assert_eq!(vote_state.nth_recent_lockout(0).unwrap().slot(), 1);
1624        assert_eq!(vote_state.nth_recent_lockout(1).unwrap().slot(), 0);
1625        assert!(vote_state.nth_recent_lockout(2).is_none());
1626    }
1627
1628    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1629    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1630    fn test_nth_recent_lockout(target_version: VoteStateTargetVersion) {
1631        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1632        for i in 0..MAX_LOCKOUT_HISTORY {
1633            process_slot_vote_unchecked(&mut vote_state, i as u64);
1634        }
1635        for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
1636            assert_eq!(
1637                vote_state.nth_recent_lockout(i).unwrap().slot() as usize,
1638                MAX_LOCKOUT_HISTORY - i - 1,
1639            );
1640        }
1641        assert!(vote_state.nth_recent_lockout(MAX_LOCKOUT_HISTORY).is_none());
1642    }
1643
1644    fn check_lockouts(vote_state: &VoteStateHandler) {
1645        let votes = vote_state.votes();
1646        for (i, vote) in votes.iter().enumerate() {
1647            let num_votes = votes
1648                .len()
1649                .checked_sub(i)
1650                .expect("`i` is less than `vote_state.votes().len()`");
1651            assert_eq!(
1652                vote.lockout.lockout(),
1653                INITIAL_LOCKOUT.pow(num_votes as u32) as u64
1654            );
1655        }
1656    }
1657
1658    fn recent_votes(vote_state: &VoteStateHandler) -> Vec<Vote> {
1659        let votes = vote_state.votes();
1660        let start = votes.len().saturating_sub(MAX_RECENT_VOTES);
1661        (start..votes.len())
1662            .map(|i| Vote::new(vec![votes.get(i).unwrap().slot()], Hash::default()))
1663            .collect()
1664    }
1665
1666    /// check that two accounts with different data can be brought to the same state with one vote submission
1667    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1668    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1669    fn test_process_missed_votes(target_version: VoteStateTargetVersion) {
1670        let mut vote_state_a = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1671        let mut vote_state_b = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1672
1673        // process some votes on account a
1674        (0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
1675        assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
1676
1677        // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
1678        let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
1679        let vote = Vote::new(slots, Hash::default());
1680        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
1681
1682        assert_eq!(
1683            process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0),
1684            Ok(())
1685        );
1686        assert_eq!(
1687            process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0),
1688            Ok(())
1689        );
1690        assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
1691    }
1692
1693    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1694    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1695    fn test_process_vote_skips_old_vote(mut vote_state: VoteStateHandler) {
1696        let vote = Vote::new(vec![0], Hash::default());
1697        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
1698        assert_eq!(
1699            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1700            Ok(())
1701        );
1702        let recent = recent_votes(&vote_state);
1703        assert_eq!(
1704            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1705            Err(VoteError::VoteTooOld)
1706        );
1707        assert_eq!(recent, recent_votes(&vote_state));
1708    }
1709
1710    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1711    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1712    fn test_check_slots_are_valid_vote_empty_slot_hashes(vote_state: VoteStateHandler) {
1713        let vote = Vote::new(vec![0], Hash::default());
1714        assert_eq!(
1715            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
1716            Err(VoteError::VoteTooOld)
1717        );
1718    }
1719
1720    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1721    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1722    fn test_check_slots_are_valid_new_vote(vote_state: VoteStateHandler) {
1723        let vote = Vote::new(vec![0], Hash::default());
1724        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1725        assert_eq!(
1726            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1727            Ok(())
1728        );
1729    }
1730
1731    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1732    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1733    fn test_check_slots_are_valid_bad_hash(vote_state: VoteStateHandler) {
1734        let vote = Vote::new(vec![0], Hash::default());
1735        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
1736        assert_eq!(
1737            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1738            Err(VoteError::SlotHashMismatch)
1739        );
1740    }
1741
1742    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1743    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1744    fn test_check_slots_are_valid_bad_slot(vote_state: VoteStateHandler) {
1745        let vote = Vote::new(vec![1], Hash::default());
1746        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
1747        assert_eq!(
1748            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1749            Err(VoteError::SlotsMismatch)
1750        );
1751    }
1752
1753    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1754    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1755    fn test_check_slots_are_valid_duplicate_vote(mut vote_state: VoteStateHandler) {
1756        let vote = Vote::new(vec![0], Hash::default());
1757        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1758        assert_eq!(
1759            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1760            Ok(())
1761        );
1762        assert_eq!(
1763            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1764            Err(VoteError::VoteTooOld)
1765        );
1766    }
1767
1768    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1769    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1770    fn test_check_slots_are_valid_next_vote(mut vote_state: VoteStateHandler) {
1771        let vote = Vote::new(vec![0], Hash::default());
1772        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1773        assert_eq!(
1774            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1775            Ok(())
1776        );
1777
1778        let vote = Vote::new(vec![0, 1], Hash::default());
1779        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
1780        assert_eq!(
1781            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1782            Ok(())
1783        );
1784    }
1785
1786    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1787    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1788    fn test_check_slots_are_valid_next_vote_only(mut vote_state: VoteStateHandler) {
1789        let vote = Vote::new(vec![0], Hash::default());
1790        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1791        assert_eq!(
1792            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1793            Ok(())
1794        );
1795
1796        let vote = Vote::new(vec![1], Hash::default());
1797        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
1798        assert_eq!(
1799            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1800            Ok(())
1801        );
1802    }
1803
1804    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1805    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1806    fn test_process_vote_empty_slots(mut vote_state: VoteStateHandler) {
1807        let vote = Vote::new(vec![], Hash::default());
1808        assert_eq!(
1809            process_vote(&mut vote_state, &vote, &[], 0, 0),
1810            Err(VoteError::EmptySlots)
1811        );
1812    }
1813
1814    pub fn process_new_vote_state_from_lockouts(
1815        vote_state: &mut VoteStateHandler,
1816        new_state: VecDeque<Lockout>,
1817        new_root: Option<Slot>,
1818        timestamp: Option<i64>,
1819        epoch: Epoch,
1820    ) -> Result<(), VoteError> {
1821        process_new_vote_state(
1822            vote_state,
1823            new_state.into_iter().map(LandedVote::from).collect(),
1824            new_root,
1825            timestamp,
1826            epoch,
1827            0,
1828        )
1829    }
1830
1831    // Test vote credit updates after "one credit per slot" feature is enabled
1832    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1833    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1834    fn test_vote_state_update_increment_credits(mut vote_state: VoteStateHandler) {
1835        // Test data: a sequence of groups of votes to simulate having been cast, after each group a vote
1836        // state update is compared to "normal" vote processing to ensure that credits are earned equally
1837        let test_vote_groups: Vec<Vec<Slot>> = vec![
1838            // Initial set of votes that don't dequeue any slots, so no credits earned
1839            vec![1, 2, 3, 4, 5, 6, 7, 8],
1840            vec![
1841                9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
1842                30, 31,
1843            ],
1844            // Now a single vote which should result in the first root and first credit earned
1845            vec![32],
1846            // Now another vote, should earn one credit
1847            vec![33],
1848            // Two votes in sequence
1849            vec![34, 35],
1850            // 3 votes in sequence
1851            vec![36, 37, 38],
1852            // 30 votes in sequence
1853            vec![
1854                39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
1855                60, 61, 62, 63, 64, 65, 66, 67, 68,
1856            ],
1857            // 31 votes in sequence
1858            vec![
1859                69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
1860                90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1861            ],
1862            // Votes with expiry
1863            vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
1864            // More votes with expiry of a large number of votes
1865            vec![200, 201],
1866            vec![
1867                202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
1868                218, 219, 220, 221, 222, 223, 224, 225, 226,
1869            ],
1870            vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
1871        ];
1872
1873        for vote_group in test_vote_groups {
1874            // Duplicate vote_state so that the new vote can be applied
1875            let mut vote_state_after_vote = vote_state.clone();
1876
1877            process_vote_unchecked(
1878                &mut vote_state_after_vote,
1879                Vote {
1880                    slots: vote_group.clone(),
1881                    hash: Hash::new_unique(),
1882                    timestamp: None,
1883                },
1884            )
1885            .unwrap();
1886
1887            // Now use the resulting new vote state to perform a vote state update on vote_state
1888            assert_eq!(
1889                process_new_vote_state(
1890                    &mut vote_state,
1891                    vote_state_after_vote.votes().clone(),
1892                    vote_state_after_vote.root_slot(),
1893                    None,
1894                    0,
1895                    0,
1896                ),
1897                Ok(())
1898            );
1899
1900            // And ensure that the credits earned were the same
1901            assert_eq!(
1902                vote_state.epoch_credits(),
1903                vote_state_after_vote.epoch_credits()
1904            );
1905        }
1906    }
1907
1908    // Test vote credit updates after "timely vote credits" feature is enabled
1909    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1910    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1911    fn test_timely_credits(target_version: VoteStateTargetVersion) {
1912        // Each of the following (Vec<Slot>, Slot, u32) tuples gives a set of slots to cast votes on, a slot in which
1913        // the vote was cast, and the number of credits that should have been earned by the vote account after this
1914        // and all prior votes were cast.
1915        let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
1916            // Initial set of votes that don't dequeue any slots, so no credits earned
1917            (
1918                vec![1, 2, 3, 4, 5, 6, 7, 8],
1919                9,
1920                // root: none, no credits earned
1921                0,
1922            ),
1923            (
1924                vec![
1925                    9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
1926                    29, 30, 31,
1927                ],
1928                34,
1929                // lockouts full
1930                // root: none, no credits earned
1931                0,
1932            ),
1933            // Now a single vote which should result in the first root and first credit earned
1934            (
1935                vec![32],
1936                35,
1937                // root: 1
1938                // when slot 1 was voted on in slot 9, it earned 10 credits
1939                10,
1940            ),
1941            // Now another vote, should earn one credit
1942            (
1943                vec![33],
1944                36,
1945                // root: 2
1946                // when slot 2 was voted on in slot 9, it earned 11 credits
1947                10 + 11, // 21
1948            ),
1949            // Two votes in sequence
1950            (
1951                vec![34, 35],
1952                37,
1953                // root: 4
1954                // when slots 3 and 4 were voted on in slot 9, they earned 12 and 13 credits
1955                21 + 12 + 13, // 46
1956            ),
1957            // 3 votes in sequence
1958            (
1959                vec![36, 37, 38],
1960                39,
1961                // root: 7
1962                // slots 5, 6, and 7 earned 14, 15, and 16 credits when voted in slot 9
1963                46 + 14 + 15 + 16, // 91
1964            ),
1965            (
1966                // 30 votes in sequence
1967                vec![
1968                    39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
1969                    58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
1970                ],
1971                69,
1972                // root: 37
1973                // slot 8 was voted in slot 9, earning 16 credits
1974                // slots 9 - 25 earned 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, and 9 credits when voted in
1975                //   slot 34
1976                // slot 26, 27, 28, 29, 30, 31 earned 10, 11, 12, 13, 14, 15 credits when voted in slot 34
1977                // slot 32 earned 15 credits when voted in slot 35
1978                // slot 33 earned 15 credits when voted in slot 36
1979                // slot 34 and 35 earned 15 and 16 credits when voted in slot 37
1980                // slot 36 and 37 earned 15 and 16 credits when voted in slot 39
1981                91 + 16
1982                    + 9 // * 1
1983                    + 2
1984                    + 3
1985                    + 4
1986                    + 5
1987                    + 6
1988                    + 7
1989                    + 8
1990                    + 9
1991                    + 10
1992                    + 11
1993                    + 12
1994                    + 13
1995                    + 14
1996                    + 15
1997                    + 15
1998                    + 15
1999                    + 15
2000                    + 16
2001                    + 15
2002                    + 16, // 327
2003            ),
2004            // 31 votes in sequence
2005            (
2006                vec![
2007                    69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
2008                    89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2009                ],
2010                100,
2011                // root: 68
2012                // slot 38 earned 16 credits when voted in slot 39
2013                // slot 39 - 60 earned 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, and 9 credits
2014                //   when voted in slot 69
2015                // slot 61, 62, 63, 64, 65, 66, 67, 68 earned 10, 11, 12, 13, 14, 15, 16, and 16 credits when
2016                //   voted in slot 69
2017                327 + 16
2018                    + 14 // * 1
2019                    + 2
2020                    + 3
2021                    + 4
2022                    + 5
2023                    + 6
2024                    + 7
2025                    + 8
2026                    + 9
2027                    + 10
2028                    + 11
2029                    + 12
2030                    + 13
2031                    + 14
2032                    + 15
2033                    + 16
2034                    + 16, // 508
2035            ),
2036            // Votes with expiry
2037            (
2038                vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
2039                130,
2040                // root: 74
2041                // slots 96 - 114 expire
2042                // slots 69 - 74 earned 1 credit when voted in slot 100
2043                508 + ((74 - 69) + 1), // 514
2044            ),
2045            // More votes with expiry of a large number of votes
2046            (
2047                vec![200, 201],
2048                202,
2049                // root: 74
2050                // slots 119 - 124 expire
2051                514,
2052            ),
2053            (
2054                vec![
2055                    202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2056                    218, 219, 220, 221, 222, 223, 224, 225, 226,
2057                ],
2058                227,
2059                // root: 95
2060                // slot 75 - 91 earned 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, and 9 credits when voted in
2061                //   slot 100
2062                // slot 92, 93, 94, 95 earned 10, 11, 12, 13, credits when voted in slot 100
2063                514 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13, // 613
2064            ),
2065            (
2066                vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2067                237,
2068                // root: 205
2069                // slot 115 - 118 earned 3, 4, 5, and 6 credits when voted in slot 130
2070                // slot 200 and 201 earned 16 credits when voted in slot 202
2071                // slots 202 - 205 earned 1 credit when voted in slot 227
2072                613 + 3 + 4 + 5 + 6 + 16 + 16 + 1 + 1 + 1 + 1, // 667
2073            ),
2074        ];
2075
2076        let new_vote_state = || match target_version {
2077            VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
2078            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2079        };
2080
2081        // For each vote group, process all vote groups leading up to it and it itself, and ensure that the number of
2082        // credits earned is correct for both regular votes and vote state updates
2083        for i in 0..test_vote_groups.len() {
2084            // Create a new VoteStateV3 for vote transaction
2085            let mut vote_state_1 = new_vote_state();
2086            // Create a new VoteStateV3 for vote state update transaction
2087            let mut vote_state_2 = new_vote_state();
2088            test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
2089                let vote = Vote {
2090                    slots: vote_group.0.clone(), //vote_group.0 is the set of slots to cast votes on
2091                    hash: Hash::new_unique(),
2092                    timestamp: None,
2093                };
2094                let slot_hashes: Vec<_> =
2095                    vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2096                assert_eq!(
2097                    process_vote(
2098                        &mut vote_state_1,
2099                        &vote,
2100                        &slot_hashes,
2101                        0,
2102                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2103                    ),
2104                    Ok(())
2105                );
2106
2107                assert_eq!(
2108                    process_new_vote_state(
2109                        &mut vote_state_2,
2110                        vote_state_1.votes().clone(),
2111                        vote_state_1.root_slot(),
2112                        None,
2113                        0,
2114                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2115                    ),
2116                    Ok(())
2117                );
2118            });
2119
2120            // Ensure that the credits earned is correct for both vote states
2121            let vote_group = &test_vote_groups[i];
2122            assert_eq!(vote_state_1.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2123            assert_eq!(vote_state_2.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2124        }
2125    }
2126
2127    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2128    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2129    fn test_retroactive_voting_timely_credits(mut vote_state: VoteStateHandler) {
2130        // Each of the following (Vec<(Slot, int)>, Slot, Option<Slot>, u32) tuples gives the following data:
2131        // Vec<(Slot, int)> -- the set of slots and confirmation_counts that is the proposed vote state
2132        // Slot -- the slot in which the proposed vote state landed
2133        // Option<Slot> -- the root after processing the proposed vote state
2134        // u32 -- the credits after processing the proposed vote state
2135        #[allow(clippy::type_complexity)]
2136        let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
2137            // proposed vote state to set initial vote state
2138            (
2139                vec![(7, 4), (8, 3), (9, 2), (10, 1)],
2140                11,
2141                // root: none
2142                None,
2143                // no credits earned
2144                0,
2145            ),
2146            // proposed vote state to include the missing slots *prior to previously included slots*
2147            (
2148                vec![
2149                    (1, 10),
2150                    (2, 9),
2151                    (3, 8),
2152                    (4, 7),
2153                    (5, 6),
2154                    (6, 5),
2155                    (7, 4),
2156                    (8, 3),
2157                    (9, 2),
2158                    (10, 1),
2159                ],
2160                12,
2161                // root: none
2162                None,
2163                // no credits earned
2164                0,
2165            ),
2166            // Now a single proposed vote state which roots all of the slots from 1 - 10
2167            (
2168                vec![
2169                    (11, 31),
2170                    (12, 30),
2171                    (13, 29),
2172                    (14, 28),
2173                    (15, 27),
2174                    (16, 26),
2175                    (17, 25),
2176                    (18, 24),
2177                    (19, 23),
2178                    (20, 22),
2179                    (21, 21),
2180                    (22, 20),
2181                    (23, 19),
2182                    (24, 18),
2183                    (25, 17),
2184                    (26, 16),
2185                    (27, 15),
2186                    (28, 14),
2187                    (29, 13),
2188                    (30, 12),
2189                    (31, 11),
2190                    (32, 10),
2191                    (33, 9),
2192                    (34, 8),
2193                    (35, 7),
2194                    (36, 6),
2195                    (37, 5),
2196                    (38, 4),
2197                    (39, 3),
2198                    (40, 2),
2199                    (41, 1),
2200                ],
2201                42,
2202                // root: 10
2203                Some(10),
2204                // when slots 1 - 6 were voted on in slot 12, they earned 7, 8, 9, 10, 11, and 12 credits
2205                // when slots 7 - 10 were voted on in slot 11, they earned 14, 15, 16, and 16 credits
2206                7 + 8 + 9 + 10 + 11 + 12 + 14 + 15 + 16 + 16,
2207            ),
2208        ];
2209
2210        // Process the vote state updates in sequence and ensure that the credits earned after each is processed is
2211        // correct
2212        test_vote_state_updates
2213            .iter()
2214            .for_each(|proposed_vote_state| {
2215                let new_state = proposed_vote_state
2216                    .0 // proposed_vote_state.0 is the set of slots and confirmation_counts that is the proposed vote state
2217                    .iter()
2218                    .map(|(slot, confirmation_count)| LandedVote {
2219                        latency: 0,
2220                        lockout: Lockout::new_with_confirmation_count(*slot, *confirmation_count),
2221                    })
2222                    .collect::<VecDeque<LandedVote>>();
2223                assert_eq!(
2224                    process_new_vote_state(
2225                        &mut vote_state,
2226                        new_state,
2227                        proposed_vote_state.2, // proposed_vote_state.2 is root after processing the proposed vote state
2228                        None,
2229                        0,
2230                        proposed_vote_state.1, // proposed_vote_state.1 is the slot in which the proposed vote state was applied
2231                    ),
2232                    Ok(())
2233                );
2234
2235                // Ensure that the credits earned is correct
2236                assert_eq!(vote_state.credits(), proposed_vote_state.3 as u64);
2237            });
2238    }
2239
2240    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2241    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2242    fn test_process_new_vote_too_many_votes(mut vote_state1: VoteStateHandler) {
2243        let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
2244            .map(|slot| {
2245                Lockout::new_with_confirmation_count(
2246                    slot as Slot,
2247                    (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
2248                )
2249            })
2250            .collect();
2251
2252        let current_epoch = vote_state1.current_epoch();
2253        assert_eq!(
2254            process_new_vote_state_from_lockouts(
2255                &mut vote_state1,
2256                bad_votes,
2257                None,
2258                None,
2259                current_epoch,
2260            ),
2261            Err(VoteError::TooManyVotes)
2262        );
2263    }
2264
2265    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2266    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2267    fn test_process_new_vote_state_root_rollback(mut vote_state1: VoteStateHandler) {
2268        for i in 0..MAX_LOCKOUT_HISTORY + 2 {
2269            process_slot_vote_unchecked(&mut vote_state1, i as Slot);
2270        }
2271        assert_eq!(vote_state1.root_slot().unwrap(), 1);
2272
2273        // Update vote_state2 with a higher slot so that `process_new_vote_state`
2274        // doesn't panic.
2275        let mut vote_state2 = vote_state1.clone();
2276        process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);
2277
2278        // Trying to set a lesser root should error
2279        let lesser_root = Some(0);
2280
2281        let current_epoch = vote_state2.current_epoch();
2282        assert_eq!(
2283            process_new_vote_state(
2284                &mut vote_state1,
2285                vote_state2.votes().clone(),
2286                lesser_root,
2287                None,
2288                current_epoch,
2289                0,
2290            ),
2291            Err(VoteError::RootRollBack)
2292        );
2293
2294        // Trying to set root to None should error
2295        let none_root = None;
2296        assert_eq!(
2297            process_new_vote_state(
2298                &mut vote_state1,
2299                vote_state2.votes().clone(),
2300                none_root,
2301                None,
2302                current_epoch,
2303                0,
2304            ),
2305            Err(VoteError::RootRollBack)
2306        );
2307    }
2308
2309    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2310    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2311    fn test_process_new_vote_state_zero_confirmations(mut vote_state1: VoteStateHandler) {
2312        let current_epoch = vote_state1.current_epoch();
2313
2314        let bad_votes: VecDeque<Lockout> = vec![
2315            Lockout::new_with_confirmation_count(0, 0),
2316            Lockout::new_with_confirmation_count(1, 1),
2317        ]
2318        .into_iter()
2319        .collect();
2320        assert_eq!(
2321            process_new_vote_state_from_lockouts(
2322                &mut vote_state1,
2323                bad_votes,
2324                None,
2325                None,
2326                current_epoch,
2327            ),
2328            Err(VoteError::ZeroConfirmations)
2329        );
2330
2331        let bad_votes: VecDeque<Lockout> = vec![
2332            Lockout::new_with_confirmation_count(0, 2),
2333            Lockout::new_with_confirmation_count(1, 0),
2334        ]
2335        .into_iter()
2336        .collect();
2337        assert_eq!(
2338            process_new_vote_state_from_lockouts(
2339                &mut vote_state1,
2340                bad_votes,
2341                None,
2342                None,
2343                current_epoch,
2344            ),
2345            Err(VoteError::ZeroConfirmations)
2346        );
2347    }
2348
2349    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2350    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2351    fn test_process_new_vote_state_confirmations_too_large(initial_vote_state: VoteStateHandler) {
2352        let mut vote_state1 = initial_vote_state.clone();
2353        let current_epoch = vote_state1.current_epoch();
2354
2355        let good_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2356            0,
2357            MAX_LOCKOUT_HISTORY as u32,
2358        )]
2359        .into_iter()
2360        .collect();
2361
2362        process_new_vote_state_from_lockouts(
2363            &mut vote_state1,
2364            good_votes,
2365            None,
2366            None,
2367            current_epoch,
2368        )
2369        .unwrap();
2370
2371        let mut vote_state1 = initial_vote_state;
2372        let bad_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2373            0,
2374            MAX_LOCKOUT_HISTORY as u32 + 1,
2375        )]
2376        .into_iter()
2377        .collect();
2378        assert_eq!(
2379            process_new_vote_state_from_lockouts(
2380                &mut vote_state1,
2381                bad_votes,
2382                None,
2383                None,
2384                current_epoch,
2385            ),
2386            Err(VoteError::ConfirmationTooLarge)
2387        );
2388    }
2389
2390    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2391    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2392    fn test_process_new_vote_state_slot_smaller_than_root(mut vote_state1: VoteStateHandler) {
2393        let current_epoch = vote_state1.current_epoch();
2394        let root_slot = 5;
2395
2396        let bad_votes: VecDeque<Lockout> = vec![
2397            Lockout::new_with_confirmation_count(root_slot, 2),
2398            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2399        ]
2400        .into_iter()
2401        .collect();
2402        assert_eq!(
2403            process_new_vote_state_from_lockouts(
2404                &mut vote_state1,
2405                bad_votes,
2406                Some(root_slot),
2407                None,
2408                current_epoch,
2409            ),
2410            Err(VoteError::SlotSmallerThanRoot)
2411        );
2412
2413        let bad_votes: VecDeque<Lockout> = vec![
2414            Lockout::new_with_confirmation_count(root_slot - 1, 2),
2415            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2416        ]
2417        .into_iter()
2418        .collect();
2419        assert_eq!(
2420            process_new_vote_state_from_lockouts(
2421                &mut vote_state1,
2422                bad_votes,
2423                Some(root_slot),
2424                None,
2425                current_epoch,
2426            ),
2427            Err(VoteError::SlotSmallerThanRoot)
2428        );
2429    }
2430
2431    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2432    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2433    fn test_process_new_vote_state_slots_not_ordered(mut vote_state1: VoteStateHandler) {
2434        let current_epoch = vote_state1.current_epoch();
2435
2436        let bad_votes: VecDeque<Lockout> = vec![
2437            Lockout::new_with_confirmation_count(1, 2),
2438            Lockout::new_with_confirmation_count(0, 1),
2439        ]
2440        .into_iter()
2441        .collect();
2442        assert_eq!(
2443            process_new_vote_state_from_lockouts(
2444                &mut vote_state1,
2445                bad_votes,
2446                None,
2447                None,
2448                current_epoch,
2449            ),
2450            Err(VoteError::SlotsNotOrdered)
2451        );
2452
2453        let bad_votes: VecDeque<Lockout> = vec![
2454            Lockout::new_with_confirmation_count(1, 2),
2455            Lockout::new_with_confirmation_count(1, 1),
2456        ]
2457        .into_iter()
2458        .collect();
2459        assert_eq!(
2460            process_new_vote_state_from_lockouts(
2461                &mut vote_state1,
2462                bad_votes,
2463                None,
2464                None,
2465                current_epoch,
2466            ),
2467            Err(VoteError::SlotsNotOrdered)
2468        );
2469    }
2470
2471    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2472    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2473    fn test_process_new_vote_state_confirmations_not_ordered(mut vote_state1: VoteStateHandler) {
2474        let current_epoch = vote_state1.current_epoch();
2475
2476        let bad_votes: VecDeque<Lockout> = vec![
2477            Lockout::new_with_confirmation_count(0, 1),
2478            Lockout::new_with_confirmation_count(1, 2),
2479        ]
2480        .into_iter()
2481        .collect();
2482        assert_eq!(
2483            process_new_vote_state_from_lockouts(
2484                &mut vote_state1,
2485                bad_votes,
2486                None,
2487                None,
2488                current_epoch,
2489            ),
2490            Err(VoteError::ConfirmationsNotOrdered)
2491        );
2492
2493        let bad_votes: VecDeque<Lockout> = vec![
2494            Lockout::new_with_confirmation_count(0, 1),
2495            Lockout::new_with_confirmation_count(1, 1),
2496        ]
2497        .into_iter()
2498        .collect();
2499        assert_eq!(
2500            process_new_vote_state_from_lockouts(
2501                &mut vote_state1,
2502                bad_votes,
2503                None,
2504                None,
2505                current_epoch,
2506            ),
2507            Err(VoteError::ConfirmationsNotOrdered)
2508        );
2509    }
2510
2511    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2512    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2513    fn test_process_new_vote_state_new_vote_state_lockout_mismatch(
2514        mut vote_state1: VoteStateHandler,
2515    ) {
2516        let current_epoch = vote_state1.current_epoch();
2517
2518        let bad_votes: VecDeque<Lockout> = vec![
2519            Lockout::new_with_confirmation_count(0, 2),
2520            Lockout::new_with_confirmation_count(7, 1),
2521        ]
2522        .into_iter()
2523        .collect();
2524
2525        // Slot 7 should have expired slot 0
2526        assert_eq!(
2527            process_new_vote_state_from_lockouts(
2528                &mut vote_state1,
2529                bad_votes,
2530                None,
2531                None,
2532                current_epoch,
2533            ),
2534            Err(VoteError::NewVoteStateLockoutMismatch)
2535        );
2536    }
2537
2538    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2539    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2540    fn test_process_new_vote_state_confirmation_rollback(mut vote_state1: VoteStateHandler) {
2541        let current_epoch = vote_state1.current_epoch();
2542        let votes: VecDeque<Lockout> = vec![
2543            Lockout::new_with_confirmation_count(0, 4),
2544            Lockout::new_with_confirmation_count(1, 3),
2545        ]
2546        .into_iter()
2547        .collect();
2548        process_new_vote_state_from_lockouts(&mut vote_state1, votes, None, None, current_epoch)
2549            .unwrap();
2550
2551        let votes: VecDeque<Lockout> = vec![
2552            Lockout::new_with_confirmation_count(0, 4),
2553            // Confirmation count lowered illegally
2554            Lockout::new_with_confirmation_count(1, 2),
2555            Lockout::new_with_confirmation_count(2, 1),
2556        ]
2557        .into_iter()
2558        .collect();
2559        // Should error because newer vote state should not have lower confirmation the same slot
2560        // 1
2561        assert_eq!(
2562            process_new_vote_state_from_lockouts(
2563                &mut vote_state1,
2564                votes,
2565                None,
2566                None,
2567                current_epoch,
2568            ),
2569            Err(VoteError::ConfirmationRollBack)
2570        );
2571    }
2572
2573    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2574    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2575    fn test_process_new_vote_state_root_progress(mut vote_state1: VoteStateHandler) {
2576        for i in 0..MAX_LOCKOUT_HISTORY {
2577            process_slot_vote_unchecked(&mut vote_state1, i as u64);
2578        }
2579
2580        assert!(vote_state1.root_slot().is_none());
2581        let mut vote_state2 = vote_state1.clone();
2582
2583        // 1) Try to update `vote_state1` with no root,
2584        // to `vote_state2`, which has a new root, should succeed.
2585        //
2586        // 2) Then try to update`vote_state1` with an existing root,
2587        // to `vote_state2`, which has a newer root, which
2588        // should succeed.
2589        for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
2590            process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
2591            assert_ne!(vote_state1.root_slot(), vote_state2.root_slot());
2592
2593            process_new_vote_state(
2594                &mut vote_state1,
2595                vote_state2.votes().clone(),
2596                vote_state2.root_slot(),
2597                None,
2598                vote_state2.current_epoch(),
2599                0,
2600            )
2601            .unwrap();
2602
2603            assert_eq!(vote_state1, vote_state2);
2604        }
2605    }
2606
2607    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2608    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2609    fn test_process_new_vote_state_same_slot_but_not_common_ancestor(
2610        initial_vote_state: VoteStateHandler,
2611    ) {
2612        // It might be possible that during the switch from old vote instructions
2613        // to new vote instructions, new_state contains votes for slots LESS
2614        // than the current state, for instance:
2615        //
2616        // Current on-chain state: 1, 5
2617        // New state: 1, 2 (lockout: 4), 3, 5, 7
2618        //
2619        // Imagine the validator made two of these votes:
2620        // 1) The first vote {1, 2, 3} didn't land in the old state, but didn't
2621        // land on chain
2622        // 2) A second vote {1, 2, 5} was then submitted, which landed
2623        //
2624        //
2625        // 2 is not popped off in the local tower because 3 doubled the lockout.
2626        // However, 3 did not land in the on-chain state, so the vote {1, 2, 6}
2627        // will immediately pop off 2.
2628
2629        // Construct on-chain vote state
2630        let mut vote_state1 = initial_vote_state.clone();
2631        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
2632        assert_eq!(
2633            vote_state1
2634                .votes()
2635                .iter()
2636                .map(|vote| vote.slot())
2637                .collect::<Vec<Slot>>(),
2638            vec![1, 5]
2639        );
2640
2641        // Construct local tower state
2642        let mut vote_state2 = initial_vote_state;
2643        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
2644        assert_eq!(
2645            vote_state2
2646                .votes()
2647                .iter()
2648                .map(|vote| vote.slot())
2649                .collect::<Vec<Slot>>(),
2650            vec![1, 2, 3, 5, 7]
2651        );
2652
2653        // See that on-chain vote state can update properly
2654        process_new_vote_state(
2655            &mut vote_state1,
2656            vote_state2.votes().clone(),
2657            vote_state2.root_slot(),
2658            None,
2659            vote_state2.current_epoch(),
2660            0,
2661        )
2662        .unwrap();
2663
2664        assert_eq!(vote_state1, vote_state2);
2665    }
2666
2667    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2668    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2669    fn test_process_new_vote_state_lockout_violation(initial_vote_state: VoteStateHandler) {
2670        // Construct on-chain vote state
2671        let mut vote_state1 = initial_vote_state.clone();
2672        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
2673        assert_eq!(
2674            vote_state1
2675                .votes()
2676                .iter()
2677                .map(|vote| vote.slot())
2678                .collect::<Vec<Slot>>(),
2679            vec![1, 2, 4, 5]
2680        );
2681
2682        // Construct conflicting tower state. Vote 4 is missing,
2683        // but 5 should not have popped off vote 4.
2684        let mut vote_state2 = initial_vote_state;
2685        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
2686        assert_eq!(
2687            vote_state2
2688                .votes()
2689                .iter()
2690                .map(|vote| vote.slot())
2691                .collect::<Vec<Slot>>(),
2692            vec![1, 2, 3, 5, 7]
2693        );
2694
2695        // See that on-chain vote state can update properly
2696        assert_eq!(
2697            process_new_vote_state(
2698                &mut vote_state1,
2699                vote_state2.votes().clone(),
2700                vote_state2.root_slot(),
2701                None,
2702                vote_state2.current_epoch(),
2703                0,
2704            ),
2705            Err(VoteError::LockoutConflict)
2706        );
2707    }
2708
2709    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2710    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2711    fn test_process_new_vote_state_lockout_violation2(initial_vote_state: VoteStateHandler) {
2712        // Construct on-chain vote state
2713        let mut vote_state1 = initial_vote_state.clone();
2714        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
2715        assert_eq!(
2716            vote_state1
2717                .votes()
2718                .iter()
2719                .map(|vote| vote.slot())
2720                .collect::<Vec<Slot>>(),
2721            vec![1, 5, 6, 7]
2722        );
2723
2724        // Construct a new vote state. Violates on-chain state because 8
2725        // should not have popped off 7
2726        let mut vote_state2 = initial_vote_state;
2727        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
2728        assert_eq!(
2729            vote_state2
2730                .votes()
2731                .iter()
2732                .map(|vote| vote.slot())
2733                .collect::<Vec<Slot>>(),
2734            vec![1, 2, 3, 5, 6, 8]
2735        );
2736
2737        // Both vote states contain `5`, but `5` is not part of the common prefix
2738        // of both vote states. However, the violation should still be detected.
2739        assert_eq!(
2740            process_new_vote_state(
2741                &mut vote_state1,
2742                vote_state2.votes().clone(),
2743                vote_state2.root_slot(),
2744                None,
2745                vote_state2.current_epoch(),
2746                0,
2747            ),
2748            Err(VoteError::LockoutConflict)
2749        );
2750    }
2751
2752    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2753    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2754    fn test_process_new_vote_state_expired_ancestor_not_removed(mut vote_state1: VoteStateHandler) {
2755        // Construct on-chain vote state
2756        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
2757        assert_eq!(
2758            vote_state1
2759                .votes()
2760                .iter()
2761                .map(|vote| vote.slot())
2762                .collect::<Vec<Slot>>(),
2763            vec![1, 9]
2764        );
2765
2766        // Example: {1: lockout 8, 9: lockout 2}, vote on 10 will not pop off 1
2767        // because 9 is not popped off yet
2768        let mut vote_state2 = vote_state1.clone();
2769        process_slot_vote_unchecked(&mut vote_state2, 10);
2770
2771        // Slot 1 has been expired by 10, but is kept alive by its descendant
2772        // 9 which has not been expired yet.
2773        assert_eq!(vote_state2.votes()[0].slot(), 1);
2774        assert_eq!(vote_state2.votes()[0].lockout.last_locked_out_slot(), 9);
2775        assert_eq!(
2776            vote_state2
2777                .votes()
2778                .iter()
2779                .map(|vote| vote.slot())
2780                .collect::<Vec<Slot>>(),
2781            vec![1, 9, 10]
2782        );
2783
2784        // Should be able to update vote_state1
2785        process_new_vote_state(
2786            &mut vote_state1,
2787            vote_state2.votes().clone(),
2788            vote_state2.root_slot(),
2789            None,
2790            vote_state2.current_epoch(),
2791            0,
2792        )
2793        .unwrap();
2794        assert_eq!(vote_state1, vote_state2,);
2795    }
2796
2797    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2798    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2799    fn test_process_new_vote_current_state_contains_bigger_slots(
2800        mut vote_state1: VoteStateHandler,
2801    ) {
2802        process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
2803        assert_eq!(
2804            vote_state1
2805                .votes()
2806                .iter()
2807                .map(|vote| vote.slot())
2808                .collect::<Vec<Slot>>(),
2809            vec![6, 7, 8]
2810        );
2811
2812        // Try to process something with lockout violations
2813        let bad_votes: VecDeque<Lockout> = vec![
2814            Lockout::new_with_confirmation_count(2, 5),
2815            // Slot 14 could not have popped off slot 6 yet
2816            Lockout::new_with_confirmation_count(14, 1),
2817        ]
2818        .into_iter()
2819        .collect();
2820        let root = Some(1);
2821
2822        let current_epoch = vote_state1.current_epoch();
2823        assert_eq!(
2824            process_new_vote_state_from_lockouts(
2825                &mut vote_state1,
2826                bad_votes,
2827                root,
2828                None,
2829                current_epoch,
2830            ),
2831            Err(VoteError::LockoutConflict)
2832        );
2833
2834        let good_votes: VecDeque<LandedVote> = vec![
2835            Lockout::new_with_confirmation_count(2, 5).into(),
2836            Lockout::new_with_confirmation_count(15, 1).into(),
2837        ]
2838        .into_iter()
2839        .collect();
2840
2841        let current_epoch = vote_state1.current_epoch();
2842        process_new_vote_state(
2843            &mut vote_state1,
2844            good_votes.clone(),
2845            root,
2846            None,
2847            current_epoch,
2848            0,
2849        )
2850        .unwrap();
2851        assert_eq!(*vote_state1.votes(), good_votes);
2852    }
2853
2854    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2855    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2856    fn test_filter_old_votes(mut vote_state: VoteStateHandler) {
2857        let old_vote_slot = 1;
2858        let vote = Vote::new(vec![old_vote_slot], Hash::default());
2859
2860        // Vote with all slots that are all older than the SlotHashes history should
2861        // error with `VotesTooOldAllFiltered`
2862        let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
2863        assert_eq!(
2864            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2865            Err(VoteError::VotesTooOldAllFiltered)
2866        );
2867
2868        // Vote with only some slots older than the SlotHashes history should
2869        // filter out those older slots
2870        let vote_slot = 2;
2871        let vote_slot_hash = slot_hashes
2872            .iter()
2873            .find(|(slot, _hash)| *slot == vote_slot)
2874            .unwrap()
2875            .1;
2876
2877        let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
2878        process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap();
2879        assert_eq!(
2880            vote_state
2881                .votes()
2882                .iter()
2883                .map(|vote| vote.lockout)
2884                .collect::<Vec<Lockout>>(),
2885            vec![Lockout::new_with_confirmation_count(vote_slot, 1)]
2886        );
2887    }
2888
2889    fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
2890        slots
2891            .iter()
2892            .rev()
2893            .map(|x| (*x, Hash::new_unique()))
2894            .collect()
2895    }
2896
2897    fn build_vote_state(
2898        target_version: VoteStateTargetVersion,
2899        vote_slots: Vec<Slot>,
2900        slot_hashes: &[(Slot, Hash)],
2901    ) -> VoteStateHandler {
2902        let mut vote_state = match target_version {
2903            VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
2904            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2905        };
2906
2907        if !vote_slots.is_empty() {
2908            let vote_hash = slot_hashes
2909                .iter()
2910                .find(|(slot, _hash)| slot == vote_slots.last().unwrap())
2911                .unwrap()
2912                .1;
2913            let vote = Vote::new(vote_slots, vote_hash);
2914            process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0)
2915                .unwrap();
2916        }
2917
2918        vote_state
2919    }
2920
2921    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
2922    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2923    fn test_check_and_filter_proposed_vote_state_empty(target_version: VoteStateTargetVersion) {
2924        let empty_slot_hashes = build_slot_hashes(vec![]);
2925        let empty_vote_state = build_vote_state(target_version, vec![], &empty_slot_hashes);
2926
2927        // Test with empty TowerSync, should return EmptySlots error
2928        let mut tower_sync = TowerSync::from(vec![]);
2929        assert_eq!(
2930            check_and_filter_proposed_vote_state(
2931                &empty_vote_state,
2932                &mut tower_sync.lockouts,
2933                &mut tower_sync.root,
2934                tower_sync.hash,
2935                &empty_slot_hashes
2936            ),
2937            Err(VoteError::EmptySlots),
2938        );
2939
2940        // Test with non-empty TowerSync, should return SlotsMismatch since nothing exists in SlotHashes
2941        let mut tower_sync = TowerSync::from(vec![(0, 1)]);
2942        assert_eq!(
2943            check_and_filter_proposed_vote_state(
2944                &empty_vote_state,
2945                &mut tower_sync.lockouts,
2946                &mut tower_sync.root,
2947                tower_sync.hash,
2948                &empty_slot_hashes
2949            ),
2950            Err(VoteError::SlotsMismatch),
2951        );
2952    }
2953
2954    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
2955    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2956    fn test_check_and_filter_proposed_vote_state_too_old(target_version: VoteStateTargetVersion) {
2957        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
2958        let latest_vote = 4;
2959        let vote_state = build_vote_state(target_version, vec![1, 2, 3, latest_vote], &slot_hashes);
2960
2961        // Test with a vote for a slot less than the latest vote in the vote_state,
2962        // should return error `VoteTooOld`
2963        let mut tower_sync = TowerSync::from(vec![(latest_vote, 1)]);
2964        assert_eq!(
2965            check_and_filter_proposed_vote_state(
2966                &vote_state,
2967                &mut tower_sync.lockouts,
2968                &mut tower_sync.root,
2969                tower_sync.hash,
2970                &slot_hashes
2971            ),
2972            Err(VoteError::VoteTooOld),
2973        );
2974
2975        // Test with a vote state update where the latest slot `X` in the update is
2976        // 1) Less than the earliest slot in slot_hashes history, AND
2977        // 2) `X` > latest_vote
2978        let earliest_slot_in_history = latest_vote + 2;
2979        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
2980        let mut tower_sync = TowerSync::from(vec![(earliest_slot_in_history - 1, 1)]);
2981        assert_eq!(
2982            check_and_filter_proposed_vote_state(
2983                &vote_state,
2984                &mut tower_sync.lockouts,
2985                &mut tower_sync.root,
2986                tower_sync.hash,
2987                &slot_hashes
2988            ),
2989            Err(VoteError::VoteTooOld),
2990        );
2991    }
2992
2993    fn run_test_check_and_filter_proposed_vote_state_older_than_history_root(
2994        target_version: VoteStateTargetVersion,
2995        earliest_slot_in_history: Slot,
2996        current_vote_state_slots: Vec<Slot>,
2997        current_vote_state_root: Option<Slot>,
2998        proposed_slots_and_lockouts: Vec<(Slot, u32)>,
2999        proposed_root: Slot,
3000        expected_root: Option<Slot>,
3001        expected_vote_state: Vec<Lockout>,
3002    ) {
3003        assert!(proposed_root < earliest_slot_in_history);
3004        assert_eq!(
3005            expected_root,
3006            current_vote_state_slots
3007                .iter()
3008                .rev()
3009                .find(|slot| **slot <= proposed_root)
3010                .cloned()
3011        );
3012        let latest_slot_in_history = proposed_slots_and_lockouts
3013            .last()
3014            .unwrap()
3015            .0
3016            .max(earliest_slot_in_history);
3017        let mut slot_hashes = build_slot_hashes(
3018            (current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
3019                .collect::<Vec<Slot>>(),
3020        );
3021
3022        let mut vote_state =
3023            build_vote_state(target_version, current_vote_state_slots, &slot_hashes);
3024        vote_state.set_root_slot(current_vote_state_root);
3025
3026        slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
3027        assert!(!proposed_slots_and_lockouts.is_empty());
3028        let proposed_hash = slot_hashes
3029            .iter()
3030            .find(|(slot, _hash)| *slot == proposed_slots_and_lockouts.last().unwrap().0)
3031            .unwrap()
3032            .1;
3033
3034        // Test with a `TowerSync` where the root is less than `earliest_slot_in_history`.
3035        // Root slot in the `TowerSync` should be updated to match the root slot in the
3036        // current vote state
3037        let mut tower_sync = TowerSync::from(proposed_slots_and_lockouts);
3038        tower_sync.hash = proposed_hash;
3039        tower_sync.root = Some(proposed_root);
3040        check_and_filter_proposed_vote_state(
3041            &vote_state,
3042            &mut tower_sync.lockouts,
3043            &mut tower_sync.root,
3044            tower_sync.hash,
3045            &slot_hashes,
3046        )
3047        .unwrap();
3048        assert_eq!(tower_sync.root, expected_root);
3049
3050        // The proposed root slot should become the biggest slot in the current vote state less than
3051        // `earliest_slot_in_history`.
3052        assert!(
3053            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync.clone(),).is_ok()
3054        );
3055        assert_eq!(vote_state.root_slot(), expected_root);
3056        assert_eq!(
3057            vote_state
3058                .votes()
3059                .iter()
3060                .map(|vote| vote.lockout)
3061                .collect::<Vec<Lockout>>(),
3062            expected_vote_state,
3063        );
3064    }
3065
3066    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3067    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3068    fn test_check_and_filter_proposed_vote_state_older_than_history_root(
3069        target_version: VoteStateTargetVersion,
3070    ) {
3071        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3072        // slot
3073        let earliest_slot_in_history = 5;
3074        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3075        let current_vote_state_root = None;
3076        let proposed_slots_and_lockouts = vec![(5, 1)];
3077        let proposed_root = 4;
3078        let expected_root = Some(4);
3079        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3080        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3081            target_version,
3082            earliest_slot_in_history,
3083            current_vote_state_slots,
3084            current_vote_state_root,
3085            proposed_slots_and_lockouts,
3086            proposed_root,
3087            expected_root,
3088            expected_vote_state,
3089        );
3090
3091        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3092        // slot and the `current_vote_state_root.is_some()`.
3093        let earliest_slot_in_history = 5;
3094        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3095        let current_vote_state_root = Some(0);
3096        let proposed_slots_and_lockouts = vec![(5, 1)];
3097        let proposed_root = 4;
3098        let expected_root = Some(4);
3099        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3100        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3101            target_version,
3102            earliest_slot_in_history,
3103            current_vote_state_slots,
3104            current_vote_state_root,
3105            proposed_slots_and_lockouts,
3106            proposed_root,
3107            expected_root,
3108            expected_vote_state,
3109        );
3110
3111        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3112        // slot
3113        let earliest_slot_in_history = 5;
3114        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3115        let current_vote_state_root = Some(0);
3116        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3117        let proposed_root = 3;
3118        let expected_root = Some(3);
3119        let expected_vote_state = vec![
3120            Lockout::new_with_confirmation_count(4, 2),
3121            Lockout::new_with_confirmation_count(5, 1),
3122        ];
3123        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3124            target_version,
3125            earliest_slot_in_history,
3126            current_vote_state_slots,
3127            current_vote_state_root,
3128            proposed_slots_and_lockouts,
3129            proposed_root,
3130            expected_root,
3131            expected_vote_state,
3132        );
3133
3134        // Test when `proposed_root` is not in `current_vote_state_slots`
3135        let earliest_slot_in_history = 5;
3136        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
3137        let current_vote_state_root = Some(0);
3138        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3139        let proposed_root = 3;
3140        let expected_root = Some(2);
3141        let expected_vote_state = vec![
3142            Lockout::new_with_confirmation_count(4, 2),
3143            Lockout::new_with_confirmation_count(5, 1),
3144        ];
3145        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3146            target_version,
3147            earliest_slot_in_history,
3148            current_vote_state_slots,
3149            current_vote_state_root,
3150            proposed_slots_and_lockouts,
3151            proposed_root,
3152            expected_root,
3153            expected_vote_state,
3154        );
3155
3156        // Test when the `proposed_root` is smaller than all the slots in
3157        // `current_vote_state_slots`, no roots should be set.
3158        let earliest_slot_in_history = 4;
3159        let current_vote_state_slots: Vec<Slot> = vec![3, 4];
3160        let current_vote_state_root = None;
3161        let proposed_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
3162        let proposed_root = 2;
3163        let expected_root = None;
3164        let expected_vote_state = vec![
3165            Lockout::new_with_confirmation_count(3, 3),
3166            Lockout::new_with_confirmation_count(4, 2),
3167            Lockout::new_with_confirmation_count(5, 1),
3168        ];
3169        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3170            target_version,
3171            earliest_slot_in_history,
3172            current_vote_state_slots,
3173            current_vote_state_root,
3174            proposed_slots_and_lockouts,
3175            proposed_root,
3176            expected_root,
3177            expected_vote_state,
3178        );
3179
3180        // Test when `current_vote_state_slots` is empty, no roots should be set
3181        let earliest_slot_in_history = 4;
3182        let current_vote_state_slots: Vec<Slot> = vec![];
3183        let current_vote_state_root = None;
3184        let proposed_slots_and_lockouts = vec![(5, 1)];
3185        let proposed_root = 2;
3186        let expected_root = None;
3187        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3188        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3189            target_version,
3190            earliest_slot_in_history,
3191            current_vote_state_slots,
3192            current_vote_state_root,
3193            proposed_slots_and_lockouts,
3194            proposed_root,
3195            expected_root,
3196            expected_vote_state,
3197        );
3198    }
3199
3200    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3201    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3202    fn test_check_and_filter_proposed_vote_state_slots_not_ordered(
3203        target_version: VoteStateTargetVersion,
3204    ) {
3205        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3206        let vote_state = build_vote_state(target_version, vec![1], &slot_hashes);
3207
3208        // Test with a `TowerSync` where the slots are out of order
3209        let vote_slot = 3;
3210        let vote_slot_hash = slot_hashes
3211            .iter()
3212            .find(|(slot, _hash)| *slot == vote_slot)
3213            .unwrap()
3214            .1;
3215        let mut tower_sync = TowerSync::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
3216        tower_sync.hash = vote_slot_hash;
3217        assert_eq!(
3218            check_and_filter_proposed_vote_state(
3219                &vote_state,
3220                &mut tower_sync.lockouts,
3221                &mut tower_sync.root,
3222                tower_sync.hash,
3223                &slot_hashes
3224            ),
3225            Err(VoteError::SlotsNotOrdered),
3226        );
3227
3228        // Test with a `TowerSync` where there are multiples of the same slot
3229        let mut tower_sync = TowerSync::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
3230        tower_sync.hash = vote_slot_hash;
3231        assert_eq!(
3232            check_and_filter_proposed_vote_state(
3233                &vote_state,
3234                &mut tower_sync.lockouts,
3235                &mut tower_sync.root,
3236                tower_sync.hash,
3237                &slot_hashes
3238            ),
3239            Err(VoteError::SlotsNotOrdered),
3240        );
3241    }
3242
3243    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3244    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3245    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered(
3246        target_version: VoteStateTargetVersion,
3247    ) {
3248        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3249        let mut vote_state = build_vote_state(target_version, vec![1, 2, 3, 4], &slot_hashes);
3250
3251        // Test with a `TowerSync` where there:
3252        // 1) Exists a slot less than `earliest_slot_in_history`
3253        // 2) This slot does not exist in the vote state already
3254        // This slot should be filtered out
3255        let earliest_slot_in_history = 11;
3256        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3257        let vote_slot = 12;
3258        let vote_slot_hash = slot_hashes
3259            .iter()
3260            .find(|(slot, _hash)| *slot == vote_slot)
3261            .unwrap()
3262            .1;
3263        let missing_older_than_history_slot = earliest_slot_in_history - 1;
3264        let mut tower_sync = TowerSync::from(vec![
3265            (1, 4),
3266            (missing_older_than_history_slot, 2),
3267            (vote_slot, 3),
3268        ]);
3269        tower_sync.hash = vote_slot_hash;
3270        check_and_filter_proposed_vote_state(
3271            &vote_state,
3272            &mut tower_sync.lockouts,
3273            &mut tower_sync.root,
3274            tower_sync.hash,
3275            &slot_hashes,
3276        )
3277        .unwrap();
3278
3279        // Check the earlier slot was filtered out
3280        assert_eq!(
3281            tower_sync
3282                .clone()
3283                .lockouts
3284                .into_iter()
3285                .collect::<Vec<Lockout>>(),
3286            vec![
3287                Lockout::new_with_confirmation_count(1, 4),
3288                Lockout::new_with_confirmation_count(vote_slot, 3)
3289            ]
3290        );
3291        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3292    }
3293
3294    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3295    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3296    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_not_filtered(
3297        target_version: VoteStateTargetVersion,
3298    ) {
3299        let slot_hashes = build_slot_hashes(vec![4]);
3300        let mut vote_state = build_vote_state(target_version, vec![4], &slot_hashes);
3301
3302        // Test with a `TowerSync` where there:
3303        // 1) Exists a slot less than `earliest_slot_in_history`
3304        // 2) This slot exists in the vote state already
3305        // This slot should *NOT* be filtered out
3306        let earliest_slot_in_history = 11;
3307        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3308        let vote_slot = 12;
3309        let vote_slot_hash = slot_hashes
3310            .iter()
3311            .find(|(slot, _hash)| *slot == vote_slot)
3312            .unwrap()
3313            .1;
3314        let existing_older_than_history_slot = 4;
3315        let mut tower_sync =
3316            TowerSync::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
3317        tower_sync.hash = vote_slot_hash;
3318        check_and_filter_proposed_vote_state(
3319            &vote_state,
3320            &mut tower_sync.lockouts,
3321            &mut tower_sync.root,
3322            tower_sync.hash,
3323            &slot_hashes,
3324        )
3325        .unwrap();
3326        // Check the earlier slot was *NOT* filtered out
3327        assert_eq!(tower_sync.lockouts.len(), 2);
3328        assert_eq!(
3329            tower_sync
3330                .clone()
3331                .lockouts
3332                .into_iter()
3333                .collect::<Vec<Lockout>>(),
3334            vec![
3335                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3336                Lockout::new_with_confirmation_count(vote_slot, 2)
3337            ]
3338        );
3339        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3340    }
3341
3342    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3343    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3344    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered_and_not_filtered(
3345        target_version: VoteStateTargetVersion,
3346    ) {
3347        let slot_hashes = build_slot_hashes(vec![6]);
3348        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3349
3350        // Test with a `TowerSync` where there exists both a slot:
3351        // 1) Less than `earliest_slot_in_history`
3352        // 2) This slot exists in the vote state already
3353        // which should not be filtered
3354        //
3355        // AND a slot that
3356        //
3357        // 1) Less than `earliest_slot_in_history`
3358        // 2) This slot does not exist in the vote state already
3359        // which should be filtered
3360        let earliest_slot_in_history = 11;
3361        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3362        let vote_slot = 14;
3363        let vote_slot_hash = slot_hashes
3364            .iter()
3365            .find(|(slot, _hash)| *slot == vote_slot)
3366            .unwrap()
3367            .1;
3368
3369        let missing_older_than_history_slot = 4;
3370        let existing_older_than_history_slot = 6;
3371
3372        let mut tower_sync = TowerSync::from(vec![
3373            (missing_older_than_history_slot, 4),
3374            (existing_older_than_history_slot, 3),
3375            (12, 2),
3376            (vote_slot, 1),
3377        ]);
3378        tower_sync.hash = vote_slot_hash;
3379        check_and_filter_proposed_vote_state(
3380            &vote_state,
3381            &mut tower_sync.lockouts,
3382            &mut tower_sync.root,
3383            tower_sync.hash,
3384            &slot_hashes,
3385        )
3386        .unwrap();
3387        assert_eq!(tower_sync.lockouts.len(), 3);
3388        assert_eq!(
3389            tower_sync
3390                .clone()
3391                .lockouts
3392                .into_iter()
3393                .collect::<Vec<Lockout>>(),
3394            vec![
3395                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3396                Lockout::new_with_confirmation_count(12, 2),
3397                Lockout::new_with_confirmation_count(vote_slot, 1)
3398            ]
3399        );
3400        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3401    }
3402
3403    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3404    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3405    fn test_check_and_filter_proposed_vote_state_slot_not_on_fork(
3406        target_version: VoteStateTargetVersion,
3407    ) {
3408        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3409        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3410
3411        // Test with a `TowerSync` where there:
3412        // 1) Exists a slot not in the slot hashes history
3413        // 2) The slot is greater than the earliest slot in the history
3414        // Thus this slot is not part of the fork and the update should be rejected
3415        // with error `SlotsMismatch`
3416        let missing_vote_slot = 3;
3417
3418        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3419        // errors
3420        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3421        let vote_slot_hash = slot_hashes
3422            .iter()
3423            .find(|(slot, _hash)| *slot == vote_slot)
3424            .unwrap()
3425            .1;
3426        let mut tower_sync = TowerSync::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
3427        tower_sync.hash = vote_slot_hash;
3428        assert_eq!(
3429            check_and_filter_proposed_vote_state(
3430                &vote_state,
3431                &mut tower_sync.lockouts,
3432                &mut tower_sync.root,
3433                tower_sync.hash,
3434                &slot_hashes
3435            ),
3436            Err(VoteError::SlotsMismatch),
3437        );
3438
3439        // Test where some earlier vote slots exist in the history, but others don't
3440        let missing_vote_slot = 7;
3441        let mut tower_sync = TowerSync::from(vec![
3442            (2, 5),
3443            (4, 4),
3444            (6, 3),
3445            (missing_vote_slot, 2),
3446            (vote_slot, 1),
3447        ]);
3448        tower_sync.hash = vote_slot_hash;
3449        assert_eq!(
3450            check_and_filter_proposed_vote_state(
3451                &vote_state,
3452                &mut tower_sync.lockouts,
3453                &mut tower_sync.root,
3454                tower_sync.hash,
3455                &slot_hashes
3456            ),
3457            Err(VoteError::SlotsMismatch),
3458        );
3459    }
3460
3461    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3462    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3463    fn test_check_and_filter_proposed_vote_state_root_on_different_fork(
3464        target_version: VoteStateTargetVersion,
3465    ) {
3466        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3467        let vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3468
3469        // Test with a `TowerSync` where:
3470        // 1) The root is not present in slot hashes history
3471        // 2) The slot is greater than the earliest slot in the history
3472        // Thus this slot is not part of the fork and the update should be rejected
3473        // with error `RootOnDifferentFork`
3474        let new_root = 3;
3475
3476        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3477        // errors, but also this slot must be present in SlotHashes
3478        let vote_slot = 8;
3479        assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
3480        let vote_slot_hash = slot_hashes
3481            .iter()
3482            .find(|(slot, _hash)| *slot == vote_slot)
3483            .unwrap()
3484            .1;
3485        let mut tower_sync = TowerSync::from(vec![(vote_slot, 1)]);
3486        tower_sync.hash = vote_slot_hash;
3487        tower_sync.root = Some(new_root);
3488        assert_eq!(
3489            check_and_filter_proposed_vote_state(
3490                &vote_state,
3491                &mut tower_sync.lockouts,
3492                &mut tower_sync.root,
3493                tower_sync.hash,
3494                &slot_hashes
3495            ),
3496            Err(VoteError::RootOnDifferentFork),
3497        );
3498    }
3499
3500    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3501    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3502    fn test_check_and_filter_proposed_vote_state_slot_newer_than_slot_history(
3503        target_version: VoteStateTargetVersion,
3504    ) {
3505        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3506        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3507
3508        // Test with a `TowerSync` where there:
3509        // 1) The last slot in the update is a slot not in the slot hashes history
3510        // 2) The slot is greater than the newest slot in the slot history
3511        // Thus this slot is not part of the fork and the update should be rejected
3512        // with error `SlotsMismatch`
3513        let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
3514        let vote_slot_hash = Hash::new_unique();
3515        let mut tower_sync = TowerSync::from(vec![(8, 2), (missing_vote_slot, 3)]);
3516        tower_sync.hash = vote_slot_hash;
3517        assert_eq!(
3518            check_and_filter_proposed_vote_state(
3519                &vote_state,
3520                &mut tower_sync.lockouts,
3521                &mut tower_sync.root,
3522                tower_sync.hash,
3523                &slot_hashes
3524            ),
3525            Err(VoteError::SlotsMismatch),
3526        );
3527    }
3528
3529    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3530    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3531    fn test_check_and_filter_proposed_vote_state_slot_all_slot_hashes_in_update_ok(
3532        target_version: VoteStateTargetVersion,
3533    ) {
3534        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3535        let mut vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3536
3537        // Test with a `TowerSync` where every slot in the history is
3538        // in the update
3539
3540        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3541        // errors
3542        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3543        let vote_slot_hash = slot_hashes
3544            .iter()
3545            .find(|(slot, _hash)| *slot == vote_slot)
3546            .unwrap()
3547            .1;
3548        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3549        tower_sync.hash = vote_slot_hash;
3550        check_and_filter_proposed_vote_state(
3551            &vote_state,
3552            &mut tower_sync.lockouts,
3553            &mut tower_sync.root,
3554            tower_sync.hash,
3555            &slot_hashes,
3556        )
3557        .unwrap();
3558
3559        // Nothing in the update should have been filtered out
3560        assert_eq!(
3561            tower_sync
3562                .clone()
3563                .lockouts
3564                .into_iter()
3565                .collect::<Vec<Lockout>>(),
3566            vec![
3567                Lockout::new_with_confirmation_count(2, 4),
3568                Lockout::new_with_confirmation_count(4, 3),
3569                Lockout::new_with_confirmation_count(6, 2),
3570                Lockout::new_with_confirmation_count(vote_slot, 1)
3571            ]
3572        );
3573
3574        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3575    }
3576
3577    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3578    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3579    fn test_check_and_filter_proposed_vote_state_slot_some_slot_hashes_in_update_ok(
3580        target_version: VoteStateTargetVersion,
3581    ) {
3582        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3583        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3584
3585        // Test with a `TowerSync` where only some slots in the history are
3586        // in the update, and others slots in the history are missing.
3587
3588        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3589        // errors
3590        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3591        let vote_slot_hash = slot_hashes
3592            .iter()
3593            .find(|(slot, _hash)| *slot == vote_slot)
3594            .unwrap()
3595            .1;
3596        let mut tower_sync = TowerSync::from(vec![(4, 2), (vote_slot, 1)]);
3597        tower_sync.hash = vote_slot_hash;
3598        check_and_filter_proposed_vote_state(
3599            &vote_state,
3600            &mut tower_sync.lockouts,
3601            &mut tower_sync.root,
3602            tower_sync.hash,
3603            &slot_hashes,
3604        )
3605        .unwrap();
3606
3607        // Nothing in the update should have been filtered out
3608        assert_eq!(
3609            tower_sync
3610                .clone()
3611                .lockouts
3612                .into_iter()
3613                .collect::<Vec<Lockout>>(),
3614            vec![
3615                Lockout::new_with_confirmation_count(4, 2),
3616                Lockout::new_with_confirmation_count(vote_slot, 1)
3617            ]
3618        );
3619
3620        // Because 6 from the original VoteStateV3
3621        // should not have been popped off in the proposed state,
3622        // we should get a lockout conflict
3623        assert_eq!(
3624            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,),
3625            Err(VoteError::LockoutConflict)
3626        );
3627    }
3628
3629    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3630    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3631    fn test_check_and_filter_proposed_vote_state_slot_hash_mismatch(
3632        target_version: VoteStateTargetVersion,
3633    ) {
3634        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3635        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3636
3637        // Test with a `TowerSync` where the hash is mismatched
3638
3639        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3640        // errors
3641        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3642        let vote_slot_hash = Hash::new_unique();
3643        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3644        tower_sync.hash = vote_slot_hash;
3645        assert_eq!(
3646            check_and_filter_proposed_vote_state(
3647                &vote_state,
3648                &mut tower_sync.lockouts,
3649                &mut tower_sync.root,
3650                tower_sync.hash,
3651                &slot_hashes,
3652            ),
3653            Err(VoteError::SlotHashMismatch),
3654        );
3655    }
3656
3657    #[test_case(0, true; "first slot")]
3658    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
3659    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
3660    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
3661    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
3662    fn test_epoch_half_check(slot: Slot, expected_allowed: bool) {
3663        let epoch_schedule = EpochSchedule::without_warmup();
3664        assert_eq!(
3665            is_commission_update_allowed(slot, &epoch_schedule),
3666            expected_allowed
3667        );
3668    }
3669
3670    #[test]
3671    fn test_warmup_epoch_half_check_with_warmup() {
3672        let epoch_schedule = EpochSchedule::default();
3673        let first_normal_slot = epoch_schedule.first_normal_slot;
3674        // first slot works
3675        assert!(is_commission_update_allowed(0, &epoch_schedule));
3676        // right before first normal slot works, since all warmup slots allow
3677        // commission updates
3678        assert!(is_commission_update_allowed(
3679            first_normal_slot - 1,
3680            &epoch_schedule
3681        ));
3682    }
3683
3684    #[test_case(0, true; "first slot")]
3685    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
3686    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
3687    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
3688    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
3689    fn test_epoch_half_check_with_warmup(slot: Slot, expected_allowed: bool) {
3690        let epoch_schedule = EpochSchedule::default();
3691        let first_normal_slot = epoch_schedule.first_normal_slot;
3692        assert_eq!(
3693            is_commission_update_allowed(first_normal_slot.saturating_add(slot), &epoch_schedule),
3694            expected_allowed
3695        );
3696    }
3697
3698    #[test]
3699    fn test_create_v4_account_with_authorized() {
3700        let node_pubkey = Pubkey::new_unique();
3701        let authorized_voter = Pubkey::new_unique();
3702        let authorized_withdrawer = Pubkey::new_unique();
3703        let bls_pubkey_compressed = [42; 48];
3704        let inflation_rewards_commission_bps = 10000;
3705        let lamports = 100;
3706        let vote_account = create_v4_account_with_authorized(
3707            &node_pubkey,
3708            &authorized_voter,
3709            &authorized_withdrawer,
3710            Some(bls_pubkey_compressed),
3711            inflation_rewards_commission_bps,
3712            lamports,
3713        );
3714        assert_eq!(vote_account.lamports(), lamports);
3715        assert_eq!(vote_account.owner(), &id());
3716        assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
3717        let vote_state_v4 = VoteStateV4::deserialize(vote_account.data(), &node_pubkey).unwrap();
3718        assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
3719        assert_eq!(
3720            vote_state_v4.authorized_voters,
3721            AuthorizedVoters::new(0, authorized_voter)
3722        );
3723        assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
3724        assert_eq!(
3725            vote_state_v4.bls_pubkey_compressed,
3726            Some(bls_pubkey_compressed)
3727        );
3728        assert_eq!(
3729            vote_state_v4.inflation_rewards_commission_bps,
3730            inflation_rewards_commission_bps
3731        );
3732    }
3733
3734    #[test]
3735    fn test_update_validator_identity_syncs_block_revenue_collector() {
3736        let vote_state =
3737            vote_state_new_for_test(&solana_pubkey::new_rand(), VoteStateTargetVersion::V4);
3738        let node_pubkey = *vote_state.node_pubkey();
3739        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
3740
3741        let serialized = vote_state.serialize();
3742        let serialized_len = serialized.len();
3743        let rent = Rent::default();
3744        let lamports = rent.minimum_balance(serialized_len);
3745        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
3746        vote_account.set_data_from_slice(&serialized);
3747
3748        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
3749        let mut transaction_context = TransactionContext::new(
3750            vec![(id(), processor_account), (node_pubkey, vote_account)],
3751            rent,
3752            0,
3753            0,
3754        );
3755        transaction_context
3756            .configure_next_instruction_for_tests(
3757                0,
3758                vec![InstructionAccount::new(1, false, true)],
3759                vec![],
3760            )
3761            .unwrap();
3762        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
3763        let mut borrowed_account = instruction_context
3764            .try_borrow_instruction_account(0)
3765            .unwrap();
3766
3767        let new_node_pubkey = solana_pubkey::new_rand();
3768        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
3769            .into_iter()
3770            .collect();
3771
3772        update_validator_identity(
3773            &mut borrowed_account,
3774            VoteStateTargetVersion::V4,
3775            &new_node_pubkey,
3776            &signers,
3777        )
3778        .unwrap();
3779
3780        // Both `node_pubkey` and `block_revenue_collector` should be set to
3781        // the new node pubkey.
3782        let vote_state =
3783            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
3784        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
3785        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
3786
3787        // Run it again.
3788        let new_node_pubkey = solana_pubkey::new_rand();
3789        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
3790            .into_iter()
3791            .collect();
3792
3793        update_validator_identity(
3794            &mut borrowed_account,
3795            VoteStateTargetVersion::V4,
3796            &new_node_pubkey,
3797            &signers,
3798        )
3799        .unwrap();
3800
3801        let vote_state =
3802            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
3803        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
3804        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
3805    }
3806}