Skip to main content

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
1063pub fn create_v3_account_with_authorized(
1064    node_pubkey: &Pubkey,
1065    authorized_voter: &Pubkey,
1066    authorized_withdrawer: &Pubkey,
1067    commission: u8,
1068    lamports: u64,
1069) -> AccountSharedData {
1070    let mut vote_account = AccountSharedData::new(lamports, VoteStateV3::size_of(), &id());
1071
1072    let vote_state = VoteStateV3::new(
1073        &VoteInit {
1074            node_pubkey: *node_pubkey,
1075            authorized_voter: *authorized_voter,
1076            authorized_withdrawer: *authorized_withdrawer,
1077            commission,
1078        },
1079        &Clock::default(),
1080    );
1081
1082    VoteStateV3::serialize(
1083        &VoteStateVersions::V3(Box::new(vote_state)),
1084        vote_account.data_as_mut_slice(),
1085    )
1086    .unwrap();
1087
1088    vote_account
1089}
1090
1091pub fn create_v4_account_with_authorized(
1092    node_pubkey: &Pubkey,
1093    authorized_voter: &Pubkey,
1094    authorized_withdrawer: &Pubkey,
1095    bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
1096    inflation_rewards_commission_bps: u16,
1097    lamports: u64,
1098) -> AccountSharedData {
1099    let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id());
1100
1101    let vote_state = handler::create_new_vote_state_v4_for_tests(
1102        node_pubkey,
1103        authorized_voter,
1104        authorized_withdrawer,
1105        bls_pubkey_compressed,
1106        inflation_rewards_commission_bps,
1107    );
1108
1109    VoteStateV4::serialize(
1110        &VoteStateVersions::V4(Box::new(vote_state)),
1111        vote_account.data_as_mut_slice(),
1112    )
1113    .unwrap();
1114
1115    vote_account
1116}
1117
1118#[allow(clippy::arithmetic_side_effects)]
1119#[cfg(test)]
1120mod tests {
1121    use {
1122        super::*,
1123        assert_matches::assert_matches,
1124        solana_account::{AccountSharedData, ReadableAccount},
1125        solana_clock::DEFAULT_SLOTS_PER_EPOCH,
1126        solana_sha256_hasher::hash,
1127        solana_transaction_context::{InstructionAccount, TransactionContext},
1128        solana_vote_interface::authorized_voters::AuthorizedVoters,
1129        test_case::test_case,
1130    };
1131
1132    const MAX_RECENT_VOTES: usize = 16;
1133
1134    fn vote_state_new_for_test(
1135        vote_pubkey: &Pubkey,
1136        target_version: VoteStateTargetVersion,
1137    ) -> VoteStateHandler {
1138        let auth_pubkey = solana_pubkey::new_rand();
1139        let vote_init = VoteInit {
1140            node_pubkey: solana_pubkey::new_rand(),
1141            authorized_voter: auth_pubkey,
1142            authorized_withdrawer: auth_pubkey,
1143            commission: 0,
1144        };
1145        let clock = Clock::default();
1146
1147        match target_version {
1148            VoteStateTargetVersion::V3 => {
1149                VoteStateHandler::new_v3(VoteStateV3::new(&vote_init, &clock))
1150            }
1151            VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(
1152                handler::create_new_vote_state_v4(vote_pubkey, &vote_init, &clock),
1153            ),
1154        }
1155    }
1156
1157    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1158    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1159    fn test_vote_state_upgrade_from_1_14_11(target_version: VoteStateTargetVersion) {
1160        let vote_pubkey = solana_pubkey::new_rand();
1161        let mut vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
1162
1163        // Simulate prior epochs completed with credits and each setting a new authorized voter
1164        vote_state.increment_credits(0, 100);
1165        assert_eq!(
1166            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 0, 1, |_pubkey| Ok(())),
1167            Ok(())
1168        );
1169        vote_state.increment_credits(1, 200);
1170        assert_eq!(
1171            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 1, 2, |_pubkey| Ok(())),
1172            Ok(())
1173        );
1174        vote_state.increment_credits(2, 300);
1175        assert_eq!(
1176            vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 2, 3, |_pubkey| Ok(())),
1177            Ok(())
1178        );
1179
1180        // Simulate votes having occurred
1181        vec![
1182            100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
1183            117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
1184            134, 135,
1185        ]
1186        .into_iter()
1187        .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0));
1188
1189        // Create an initial vote account that is sized for the 1_14_11 version of vote state, and has only the
1190        // required lamports for rent exempt minimum at that size
1191        let vote_state_v1_14_11 = match target_version {
1192            VoteStateTargetVersion::V3 => {
1193                // v3 can be converted directly to V1_14_11.
1194                VoteState1_14_11::from(vote_state.as_ref_v3().clone())
1195            }
1196            VoteStateTargetVersion::V4 => {
1197                // v4 cannot be converted directly to V1_14_11.
1198                VoteState1_14_11 {
1199                    node_pubkey: *vote_state.node_pubkey(),
1200                    authorized_withdrawer: *vote_state.authorized_withdrawer(),
1201                    commission: vote_state.commission(),
1202                    votes: vote_state
1203                        .votes()
1204                        .iter()
1205                        .map(|landed_vote| (*landed_vote).into())
1206                        .collect(),
1207                    root_slot: vote_state.root_slot(),
1208                    authorized_voters: vote_state.authorized_voters().clone(),
1209                    epoch_credits: vote_state.epoch_credits().clone(),
1210                    last_timestamp: vote_state.last_timestamp().clone(),
1211                    prior_voters: CircBuf::default(), // v4 does not store prior_voters
1212                }
1213            }
1214        };
1215        let version1_14_11_serialized =
1216            bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(vote_state_v1_14_11)))
1217                .unwrap();
1218        let version1_14_11_serialized_len = version1_14_11_serialized.len();
1219        let rent = Rent::default();
1220        let lamports = rent.minimum_balance(version1_14_11_serialized_len);
1221        let mut vote_account =
1222            AccountSharedData::new(lamports, version1_14_11_serialized_len, &id());
1223        vote_account.set_data_from_slice(&version1_14_11_serialized);
1224
1225        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1226        // vote account that was just created
1227        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1228        let mut transaction_context = TransactionContext::new(
1229            vec![(id(), processor_account), (vote_pubkey, vote_account)],
1230            rent.clone(),
1231            0,
1232            0,
1233        );
1234        transaction_context
1235            .configure_next_instruction_for_tests(
1236                0,
1237                vec![InstructionAccount::new(1, false, true)],
1238                vec![],
1239            )
1240            .unwrap();
1241        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1242
1243        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1244        // state
1245        let mut borrowed_account = instruction_context
1246            .try_borrow_instruction_account(0)
1247            .unwrap();
1248
1249        // Ensure that the vote state started out at 1_14_11
1250        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1251        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1252
1253        // Convert the vote state to current as would occur during vote instructions
1254        let converted_vote_state = get_vote_state_handler_checked(
1255            &borrowed_account,
1256            PreserveBehaviorInHandlerHelper::new(target_version, true),
1257        )
1258        .unwrap();
1259
1260        // Check to make sure that the vote_state is unchanged
1261        assert!(vote_state == converted_vote_state);
1262
1263        let vote_state = converted_vote_state;
1264
1265        // Now re-set the vote account state, knowing the account only has
1266        // enough lamports for V1_14_11.
1267        match target_version {
1268            VoteStateTargetVersion::V3 => {
1269                // V3 will write out as V1_14_11.
1270                assert_eq!(
1271                    vote_state
1272                        .clone()
1273                        .set_vote_account_state(&mut borrowed_account),
1274                    Ok(())
1275                );
1276                let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1277                assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1278            }
1279            VoteStateTargetVersion::V4 => {
1280                // V4 will throw an error.
1281                assert_eq!(
1282                    vote_state
1283                        .clone()
1284                        .set_vote_account_state(&mut borrowed_account),
1285                    Err(InstructionError::AccountNotRentExempt)
1286                );
1287            }
1288        }
1289
1290        // Convert the vote state to current as would occur during vote instructions
1291        let converted_vote_state = get_vote_state_handler_checked(
1292            &borrowed_account,
1293            PreserveBehaviorInHandlerHelper::new(target_version, true),
1294        )
1295        .unwrap();
1296
1297        // Check to make sure that the vote_state is unchanged
1298        assert!(vote_state == converted_vote_state);
1299
1300        let vote_state = converted_vote_state;
1301
1302        // Now top-up the vote account's lamports to be rent exempt for the target version.
1303        let space = match target_version {
1304            VoteStateTargetVersion::V3 => VoteStateV3::size_of(),
1305            VoteStateTargetVersion::V4 => VoteStateV4::size_of(), // They're the same, but for posterity
1306        };
1307        assert_eq!(
1308            borrowed_account.set_lamports(rent.minimum_balance(space)),
1309            Ok(())
1310        );
1311        assert_eq!(
1312            vote_state
1313                .clone()
1314                .set_vote_account_state(&mut borrowed_account),
1315            Ok(())
1316        );
1317
1318        // The vote state version should match the target version.
1319        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1320        match target_version {
1321            VoteStateTargetVersion::V3 => {
1322                assert_matches!(vote_state_version, VoteStateVersions::V3(_));
1323            }
1324            VoteStateTargetVersion::V4 => {
1325                assert_matches!(vote_state_version, VoteStateVersions::V4(_));
1326            }
1327        }
1328
1329        // Convert the vote state to current as would occur during vote instructions
1330        let converted_vote_state = get_vote_state_handler_checked(
1331            &borrowed_account,
1332            PreserveBehaviorInHandlerHelper::new(target_version, true),
1333        )
1334        .unwrap();
1335
1336        // Check to make sure that the vote_state is unchanged
1337        assert_eq!(vote_state, converted_vote_state);
1338    }
1339
1340    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1341    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1342    fn test_vote_lockout(target_version: VoteStateTargetVersion) {
1343        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1344
1345        for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
1346            process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
1347        }
1348
1349        // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
1350        assert_eq!(vote_state.votes().len(), MAX_LOCKOUT_HISTORY);
1351        assert_eq!(vote_state.root_slot(), Some(0));
1352        check_lockouts(&vote_state);
1353
1354        // One more vote that confirms the entire stack,
1355        // the root_slot should change to the
1356        // second vote
1357        let top_vote = vote_state.votes().front().unwrap().slot();
1358        let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
1359        process_slot_vote_unchecked(&mut vote_state, slot);
1360        assert_eq!(Some(top_vote), vote_state.root_slot());
1361
1362        // Expire everything except the first vote
1363        let slot = vote_state
1364            .votes()
1365            .front()
1366            .unwrap()
1367            .lockout
1368            .last_locked_out_slot();
1369        process_slot_vote_unchecked(&mut vote_state, slot);
1370        // First vote and new vote are both stored for a total of 2 votes
1371        assert_eq!(vote_state.votes().len(), 2);
1372    }
1373
1374    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1375    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1376    fn test_update_commission(target_version: VoteStateTargetVersion) {
1377        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1378        let node_pubkey = *vote_state.node_pubkey();
1379        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1380
1381        // Set commission to start.
1382        vote_state.set_commission(10);
1383
1384        let serialized = vote_state.serialize();
1385        let serialized_len = serialized.len();
1386        let rent = Rent::default();
1387        let lamports = rent.minimum_balance(serialized_len);
1388        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1389        vote_account.set_data_from_slice(&serialized);
1390
1391        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1392        // vote account that was just created
1393        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1394        let mut transaction_context = TransactionContext::new(
1395            vec![(id(), processor_account), (node_pubkey, vote_account)],
1396            rent,
1397            0,
1398            0,
1399        );
1400        transaction_context
1401            .configure_next_instruction_for_tests(
1402                0,
1403                vec![InstructionAccount::new(1, false, true)],
1404                vec![],
1405            )
1406            .unwrap();
1407        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1408
1409        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1410        // state
1411        let mut borrowed_account = instruction_context
1412            .try_borrow_instruction_account(0)
1413            .unwrap();
1414
1415        let epoch_schedule = std::sync::Arc::new(EpochSchedule::without_warmup());
1416
1417        let first_half_clock = std::sync::Arc::new(Clock {
1418            slot: epoch_schedule.slots_per_epoch / 4,
1419            ..Clock::default()
1420        });
1421
1422        let second_half_clock = std::sync::Arc::new(Clock {
1423            slot: (epoch_schedule.slots_per_epoch * 3) / 4,
1424            ..Clock::default()
1425        });
1426
1427        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1428
1429        // Increase commission in first half of epoch -- allowed
1430        assert_eq!(
1431            get_vote_state_handler_checked(
1432                &borrowed_account,
1433                PreserveBehaviorInHandlerHelper::new(target_version, true),
1434            )
1435            .unwrap()
1436            .commission(),
1437            10
1438        );
1439        assert_matches!(
1440            update_commission(
1441                &mut borrowed_account,
1442                target_version,
1443                11,
1444                &signers,
1445                &epoch_schedule,
1446                &first_half_clock,
1447            ),
1448            Ok(())
1449        );
1450        assert_eq!(
1451            get_vote_state_handler_checked(
1452                &borrowed_account,
1453                PreserveBehaviorInHandlerHelper::new(target_version, true),
1454            )
1455            .unwrap()
1456            .commission(),
1457            11
1458        );
1459
1460        // Increase commission in second half of epoch -- disallowed
1461        assert_matches!(
1462            update_commission(
1463                &mut borrowed_account,
1464                target_version,
1465                12,
1466                &signers,
1467                &epoch_schedule,
1468                &second_half_clock,
1469            ),
1470            Err(_)
1471        );
1472        assert_eq!(
1473            get_vote_state_handler_checked(
1474                &borrowed_account,
1475                PreserveBehaviorInHandlerHelper::new(target_version, true),
1476            )
1477            .unwrap()
1478            .commission(),
1479            11
1480        );
1481
1482        // Decrease commission in first half of epoch -- allowed
1483        assert_matches!(
1484            update_commission(
1485                &mut borrowed_account,
1486                target_version,
1487                10,
1488                &signers,
1489                &epoch_schedule,
1490                &first_half_clock,
1491            ),
1492            Ok(())
1493        );
1494        assert_eq!(
1495            get_vote_state_handler_checked(
1496                &borrowed_account,
1497                PreserveBehaviorInHandlerHelper::new(target_version, true),
1498            )
1499            .unwrap()
1500            .commission(),
1501            10
1502        );
1503
1504        assert_eq!(
1505            get_vote_state_handler_checked(
1506                &borrowed_account,
1507                PreserveBehaviorInHandlerHelper::new(target_version, true),
1508            )
1509            .unwrap()
1510            .commission(),
1511            10
1512        );
1513
1514        assert_matches!(
1515            update_commission(
1516                &mut borrowed_account,
1517                target_version,
1518                9,
1519                &signers,
1520                &epoch_schedule,
1521                &second_half_clock,
1522            ),
1523            Ok(())
1524        );
1525        assert_eq!(
1526            get_vote_state_handler_checked(
1527                &borrowed_account,
1528                PreserveBehaviorInHandlerHelper::new(target_version, true),
1529            )
1530            .unwrap()
1531            .commission(),
1532            9
1533        );
1534    }
1535
1536    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1537    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1538    fn test_vote_double_lockout_after_expiration(target_version: VoteStateTargetVersion) {
1539        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1540
1541        for i in 0..3 {
1542            process_slot_vote_unchecked(&mut vote_state, i as u64);
1543        }
1544
1545        check_lockouts(&vote_state);
1546
1547        // Expire the third vote (which was a vote for slot 2). The height of the
1548        // vote stack is unchanged, so none of the previous votes should have
1549        // doubled in lockout
1550        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
1551        check_lockouts(&vote_state);
1552
1553        // Vote again, this time the vote stack depth increases, so the votes should
1554        // double for everybody
1555        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
1556        check_lockouts(&vote_state);
1557
1558        // Vote again, this time the vote stack depth increases, so the votes should
1559        // double for everybody
1560        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
1561        check_lockouts(&vote_state);
1562    }
1563
1564    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1565    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1566    fn test_expire_multiple_votes(target_version: VoteStateTargetVersion) {
1567        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1568
1569        for i in 0..3 {
1570            process_slot_vote_unchecked(&mut vote_state, i as u64);
1571        }
1572
1573        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1574
1575        // Expire the second and third votes
1576        let expire_slot =
1577            vote_state.votes()[1].slot() + vote_state.votes()[1].lockout.lockout() + 1;
1578        process_slot_vote_unchecked(&mut vote_state, expire_slot);
1579        assert_eq!(vote_state.votes().len(), 2);
1580
1581        // Check that the old votes expired
1582        assert_eq!(vote_state.votes()[0].slot(), 0);
1583        assert_eq!(vote_state.votes()[1].slot(), expire_slot);
1584
1585        // Process one more vote
1586        process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);
1587
1588        // Confirmation count for the older first vote should remain unchanged
1589        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1590
1591        // The later votes should still have increasing confirmation counts
1592        assert_eq!(vote_state.votes()[1].confirmation_count(), 2);
1593        assert_eq!(vote_state.votes()[2].confirmation_count(), 1);
1594    }
1595
1596    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1597    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1598    fn test_vote_credits(target_version: VoteStateTargetVersion) {
1599        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1600
1601        for i in 0..MAX_LOCKOUT_HISTORY {
1602            process_slot_vote_unchecked(&mut vote_state, i as u64);
1603        }
1604
1605        assert_eq!(vote_state.credits(), 0);
1606
1607        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
1608        assert_eq!(vote_state.credits(), 1);
1609        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
1610        assert_eq!(vote_state.credits(), 2);
1611        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
1612        assert_eq!(vote_state.credits(), 3);
1613    }
1614
1615    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1616    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1617    fn test_duplicate_vote(target_version: VoteStateTargetVersion) {
1618        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1619        process_slot_vote_unchecked(&mut vote_state, 0);
1620        process_slot_vote_unchecked(&mut vote_state, 1);
1621        process_slot_vote_unchecked(&mut vote_state, 0);
1622        assert_eq!(vote_state.nth_recent_lockout(0).unwrap().slot(), 1);
1623        assert_eq!(vote_state.nth_recent_lockout(1).unwrap().slot(), 0);
1624        assert!(vote_state.nth_recent_lockout(2).is_none());
1625    }
1626
1627    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1628    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1629    fn test_nth_recent_lockout(target_version: VoteStateTargetVersion) {
1630        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1631        for i in 0..MAX_LOCKOUT_HISTORY {
1632            process_slot_vote_unchecked(&mut vote_state, i as u64);
1633        }
1634        for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
1635            assert_eq!(
1636                vote_state.nth_recent_lockout(i).unwrap().slot() as usize,
1637                MAX_LOCKOUT_HISTORY - i - 1,
1638            );
1639        }
1640        assert!(vote_state.nth_recent_lockout(MAX_LOCKOUT_HISTORY).is_none());
1641    }
1642
1643    fn check_lockouts(vote_state: &VoteStateHandler) {
1644        let votes = vote_state.votes();
1645        for (i, vote) in votes.iter().enumerate() {
1646            let num_votes = votes
1647                .len()
1648                .checked_sub(i)
1649                .expect("`i` is less than `vote_state.votes().len()`");
1650            assert_eq!(
1651                vote.lockout.lockout(),
1652                INITIAL_LOCKOUT.pow(num_votes as u32) as u64
1653            );
1654        }
1655    }
1656
1657    fn recent_votes(vote_state: &VoteStateHandler) -> Vec<Vote> {
1658        let votes = vote_state.votes();
1659        let start = votes.len().saturating_sub(MAX_RECENT_VOTES);
1660        (start..votes.len())
1661            .map(|i| Vote::new(vec![votes.get(i).unwrap().slot()], Hash::default()))
1662            .collect()
1663    }
1664
1665    /// check that two accounts with different data can be brought to the same state with one vote submission
1666    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1667    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1668    fn test_process_missed_votes(target_version: VoteStateTargetVersion) {
1669        let mut vote_state_a = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1670        let mut vote_state_b = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1671
1672        // process some votes on account a
1673        (0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
1674        assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
1675
1676        // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
1677        let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
1678        let vote = Vote::new(slots, Hash::default());
1679        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
1680
1681        assert_eq!(
1682            process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0),
1683            Ok(())
1684        );
1685        assert_eq!(
1686            process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0),
1687            Ok(())
1688        );
1689        assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
1690    }
1691
1692    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1693    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1694    fn test_process_vote_skips_old_vote(mut vote_state: VoteStateHandler) {
1695        let vote = Vote::new(vec![0], Hash::default());
1696        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
1697        assert_eq!(
1698            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1699            Ok(())
1700        );
1701        let recent = recent_votes(&vote_state);
1702        assert_eq!(
1703            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1704            Err(VoteError::VoteTooOld)
1705        );
1706        assert_eq!(recent, recent_votes(&vote_state));
1707    }
1708
1709    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1710    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1711    fn test_check_slots_are_valid_vote_empty_slot_hashes(vote_state: VoteStateHandler) {
1712        let vote = Vote::new(vec![0], Hash::default());
1713        assert_eq!(
1714            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
1715            Err(VoteError::VoteTooOld)
1716        );
1717    }
1718
1719    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1720    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1721    fn test_check_slots_are_valid_new_vote(vote_state: VoteStateHandler) {
1722        let vote = Vote::new(vec![0], Hash::default());
1723        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1724        assert_eq!(
1725            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1726            Ok(())
1727        );
1728    }
1729
1730    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1731    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1732    fn test_check_slots_are_valid_bad_hash(vote_state: VoteStateHandler) {
1733        let vote = Vote::new(vec![0], Hash::default());
1734        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
1735        assert_eq!(
1736            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1737            Err(VoteError::SlotHashMismatch)
1738        );
1739    }
1740
1741    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1742    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1743    fn test_check_slots_are_valid_bad_slot(vote_state: VoteStateHandler) {
1744        let vote = Vote::new(vec![1], Hash::default());
1745        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
1746        assert_eq!(
1747            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1748            Err(VoteError::SlotsMismatch)
1749        );
1750    }
1751
1752    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1753    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1754    fn test_check_slots_are_valid_duplicate_vote(mut vote_state: VoteStateHandler) {
1755        let vote = Vote::new(vec![0], Hash::default());
1756        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1757        assert_eq!(
1758            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1759            Ok(())
1760        );
1761        assert_eq!(
1762            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1763            Err(VoteError::VoteTooOld)
1764        );
1765    }
1766
1767    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1768    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1769    fn test_check_slots_are_valid_next_vote(mut vote_state: VoteStateHandler) {
1770        let vote = Vote::new(vec![0], Hash::default());
1771        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1772        assert_eq!(
1773            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1774            Ok(())
1775        );
1776
1777        let vote = Vote::new(vec![0, 1], Hash::default());
1778        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
1779        assert_eq!(
1780            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1781            Ok(())
1782        );
1783    }
1784
1785    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1786    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1787    fn test_check_slots_are_valid_next_vote_only(mut vote_state: VoteStateHandler) {
1788        let vote = Vote::new(vec![0], Hash::default());
1789        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
1790        assert_eq!(
1791            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
1792            Ok(())
1793        );
1794
1795        let vote = Vote::new(vec![1], Hash::default());
1796        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
1797        assert_eq!(
1798            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
1799            Ok(())
1800        );
1801    }
1802
1803    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1804    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1805    fn test_process_vote_empty_slots(mut vote_state: VoteStateHandler) {
1806        let vote = Vote::new(vec![], Hash::default());
1807        assert_eq!(
1808            process_vote(&mut vote_state, &vote, &[], 0, 0),
1809            Err(VoteError::EmptySlots)
1810        );
1811    }
1812
1813    pub fn process_new_vote_state_from_lockouts(
1814        vote_state: &mut VoteStateHandler,
1815        new_state: VecDeque<Lockout>,
1816        new_root: Option<Slot>,
1817        timestamp: Option<i64>,
1818        epoch: Epoch,
1819    ) -> Result<(), VoteError> {
1820        process_new_vote_state(
1821            vote_state,
1822            new_state.into_iter().map(LandedVote::from).collect(),
1823            new_root,
1824            timestamp,
1825            epoch,
1826            0,
1827        )
1828    }
1829
1830    // Test vote credit updates after "one credit per slot" feature is enabled
1831    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
1832    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
1833    fn test_vote_state_update_increment_credits(mut vote_state: VoteStateHandler) {
1834        // Test data: a sequence of groups of votes to simulate having been cast, after each group a vote
1835        // state update is compared to "normal" vote processing to ensure that credits are earned equally
1836        let test_vote_groups: Vec<Vec<Slot>> = vec![
1837            // Initial set of votes that don't dequeue any slots, so no credits earned
1838            vec![1, 2, 3, 4, 5, 6, 7, 8],
1839            vec![
1840                9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
1841                30, 31,
1842            ],
1843            // Now a single vote which should result in the first root and first credit earned
1844            vec![32],
1845            // Now another vote, should earn one credit
1846            vec![33],
1847            // Two votes in sequence
1848            vec![34, 35],
1849            // 3 votes in sequence
1850            vec![36, 37, 38],
1851            // 30 votes in sequence
1852            vec![
1853                39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
1854                60, 61, 62, 63, 64, 65, 66, 67, 68,
1855            ],
1856            // 31 votes in sequence
1857            vec![
1858                69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
1859                90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1860            ],
1861            // Votes with expiry
1862            vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
1863            // More votes with expiry of a large number of votes
1864            vec![200, 201],
1865            vec![
1866                202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
1867                218, 219, 220, 221, 222, 223, 224, 225, 226,
1868            ],
1869            vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
1870        ];
1871
1872        for vote_group in test_vote_groups {
1873            // Duplicate vote_state so that the new vote can be applied
1874            let mut vote_state_after_vote = vote_state.clone();
1875
1876            process_vote_unchecked(
1877                &mut vote_state_after_vote,
1878                Vote {
1879                    slots: vote_group.clone(),
1880                    hash: Hash::new_unique(),
1881                    timestamp: None,
1882                },
1883            )
1884            .unwrap();
1885
1886            // Now use the resulting new vote state to perform a vote state update on vote_state
1887            assert_eq!(
1888                process_new_vote_state(
1889                    &mut vote_state,
1890                    vote_state_after_vote.votes().clone(),
1891                    vote_state_after_vote.root_slot(),
1892                    None,
1893                    0,
1894                    0,
1895                ),
1896                Ok(())
1897            );
1898
1899            // And ensure that the credits earned were the same
1900            assert_eq!(
1901                vote_state.epoch_credits(),
1902                vote_state_after_vote.epoch_credits()
1903            );
1904        }
1905    }
1906
1907    // Test vote credit updates after "timely vote credits" feature is enabled
1908    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
1909    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1910    fn test_timely_credits(target_version: VoteStateTargetVersion) {
1911        // Each of the following (Vec<Slot>, Slot, u32) tuples gives a set of slots to cast votes on, a slot in which
1912        // the vote was cast, and the number of credits that should have been earned by the vote account after this
1913        // and all prior votes were cast.
1914        let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
1915            // Initial set of votes that don't dequeue any slots, so no credits earned
1916            (
1917                vec![1, 2, 3, 4, 5, 6, 7, 8],
1918                9,
1919                // root: none, no credits earned
1920                0,
1921            ),
1922            (
1923                vec![
1924                    9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
1925                    29, 30, 31,
1926                ],
1927                34,
1928                // lockouts full
1929                // root: none, no credits earned
1930                0,
1931            ),
1932            // Now a single vote which should result in the first root and first credit earned
1933            (
1934                vec![32],
1935                35,
1936                // root: 1
1937                // when slot 1 was voted on in slot 9, it earned 10 credits
1938                10,
1939            ),
1940            // Now another vote, should earn one credit
1941            (
1942                vec![33],
1943                36,
1944                // root: 2
1945                // when slot 2 was voted on in slot 9, it earned 11 credits
1946                10 + 11, // 21
1947            ),
1948            // Two votes in sequence
1949            (
1950                vec![34, 35],
1951                37,
1952                // root: 4
1953                // when slots 3 and 4 were voted on in slot 9, they earned 12 and 13 credits
1954                21 + 12 + 13, // 46
1955            ),
1956            // 3 votes in sequence
1957            (
1958                vec![36, 37, 38],
1959                39,
1960                // root: 7
1961                // slots 5, 6, and 7 earned 14, 15, and 16 credits when voted in slot 9
1962                46 + 14 + 15 + 16, // 91
1963            ),
1964            (
1965                // 30 votes in sequence
1966                vec![
1967                    39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
1968                    58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
1969                ],
1970                69,
1971                // root: 37
1972                // slot 8 was voted in slot 9, earning 16 credits
1973                // 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
1974                //   slot 34
1975                // slot 26, 27, 28, 29, 30, 31 earned 10, 11, 12, 13, 14, 15 credits when voted in slot 34
1976                // slot 32 earned 15 credits when voted in slot 35
1977                // slot 33 earned 15 credits when voted in slot 36
1978                // slot 34 and 35 earned 15 and 16 credits when voted in slot 37
1979                // slot 36 and 37 earned 15 and 16 credits when voted in slot 39
1980                91 + 16
1981                    + 9 // * 1
1982                    + 2
1983                    + 3
1984                    + 4
1985                    + 5
1986                    + 6
1987                    + 7
1988                    + 8
1989                    + 9
1990                    + 10
1991                    + 11
1992                    + 12
1993                    + 13
1994                    + 14
1995                    + 15
1996                    + 15
1997                    + 15
1998                    + 15
1999                    + 16
2000                    + 15
2001                    + 16, // 327
2002            ),
2003            // 31 votes in sequence
2004            (
2005                vec![
2006                    69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
2007                    89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2008                ],
2009                100,
2010                // root: 68
2011                // slot 38 earned 16 credits when voted in slot 39
2012                // 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
2013                //   when voted in slot 69
2014                // slot 61, 62, 63, 64, 65, 66, 67, 68 earned 10, 11, 12, 13, 14, 15, 16, and 16 credits when
2015                //   voted in slot 69
2016                327 + 16
2017                    + 14 // * 1
2018                    + 2
2019                    + 3
2020                    + 4
2021                    + 5
2022                    + 6
2023                    + 7
2024                    + 8
2025                    + 9
2026                    + 10
2027                    + 11
2028                    + 12
2029                    + 13
2030                    + 14
2031                    + 15
2032                    + 16
2033                    + 16, // 508
2034            ),
2035            // Votes with expiry
2036            (
2037                vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
2038                130,
2039                // root: 74
2040                // slots 96 - 114 expire
2041                // slots 69 - 74 earned 1 credit when voted in slot 100
2042                508 + ((74 - 69) + 1), // 514
2043            ),
2044            // More votes with expiry of a large number of votes
2045            (
2046                vec![200, 201],
2047                202,
2048                // root: 74
2049                // slots 119 - 124 expire
2050                514,
2051            ),
2052            (
2053                vec![
2054                    202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2055                    218, 219, 220, 221, 222, 223, 224, 225, 226,
2056                ],
2057                227,
2058                // root: 95
2059                // 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
2060                //   slot 100
2061                // slot 92, 93, 94, 95 earned 10, 11, 12, 13, credits when voted in slot 100
2062                514 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13, // 613
2063            ),
2064            (
2065                vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2066                237,
2067                // root: 205
2068                // slot 115 - 118 earned 3, 4, 5, and 6 credits when voted in slot 130
2069                // slot 200 and 201 earned 16 credits when voted in slot 202
2070                // slots 202 - 205 earned 1 credit when voted in slot 227
2071                613 + 3 + 4 + 5 + 6 + 16 + 16 + 1 + 1 + 1 + 1, // 667
2072            ),
2073        ];
2074
2075        let new_vote_state = || match target_version {
2076            VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
2077            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2078        };
2079
2080        // For each vote group, process all vote groups leading up to it and it itself, and ensure that the number of
2081        // credits earned is correct for both regular votes and vote state updates
2082        for i in 0..test_vote_groups.len() {
2083            // Create a new VoteStateV3 for vote transaction
2084            let mut vote_state_1 = new_vote_state();
2085            // Create a new VoteStateV3 for vote state update transaction
2086            let mut vote_state_2 = new_vote_state();
2087            test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
2088                let vote = Vote {
2089                    slots: vote_group.0.clone(), //vote_group.0 is the set of slots to cast votes on
2090                    hash: Hash::new_unique(),
2091                    timestamp: None,
2092                };
2093                let slot_hashes: Vec<_> =
2094                    vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2095                assert_eq!(
2096                    process_vote(
2097                        &mut vote_state_1,
2098                        &vote,
2099                        &slot_hashes,
2100                        0,
2101                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2102                    ),
2103                    Ok(())
2104                );
2105
2106                assert_eq!(
2107                    process_new_vote_state(
2108                        &mut vote_state_2,
2109                        vote_state_1.votes().clone(),
2110                        vote_state_1.root_slot(),
2111                        None,
2112                        0,
2113                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2114                    ),
2115                    Ok(())
2116                );
2117            });
2118
2119            // Ensure that the credits earned is correct for both vote states
2120            let vote_group = &test_vote_groups[i];
2121            assert_eq!(vote_state_1.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2122            assert_eq!(vote_state_2.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2123        }
2124    }
2125
2126    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2127    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2128    fn test_retroactive_voting_timely_credits(mut vote_state: VoteStateHandler) {
2129        // Each of the following (Vec<(Slot, int)>, Slot, Option<Slot>, u32) tuples gives the following data:
2130        // Vec<(Slot, int)> -- the set of slots and confirmation_counts that is the proposed vote state
2131        // Slot -- the slot in which the proposed vote state landed
2132        // Option<Slot> -- the root after processing the proposed vote state
2133        // u32 -- the credits after processing the proposed vote state
2134        #[allow(clippy::type_complexity)]
2135        let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
2136            // proposed vote state to set initial vote state
2137            (
2138                vec![(7, 4), (8, 3), (9, 2), (10, 1)],
2139                11,
2140                // root: none
2141                None,
2142                // no credits earned
2143                0,
2144            ),
2145            // proposed vote state to include the missing slots *prior to previously included slots*
2146            (
2147                vec![
2148                    (1, 10),
2149                    (2, 9),
2150                    (3, 8),
2151                    (4, 7),
2152                    (5, 6),
2153                    (6, 5),
2154                    (7, 4),
2155                    (8, 3),
2156                    (9, 2),
2157                    (10, 1),
2158                ],
2159                12,
2160                // root: none
2161                None,
2162                // no credits earned
2163                0,
2164            ),
2165            // Now a single proposed vote state which roots all of the slots from 1 - 10
2166            (
2167                vec![
2168                    (11, 31),
2169                    (12, 30),
2170                    (13, 29),
2171                    (14, 28),
2172                    (15, 27),
2173                    (16, 26),
2174                    (17, 25),
2175                    (18, 24),
2176                    (19, 23),
2177                    (20, 22),
2178                    (21, 21),
2179                    (22, 20),
2180                    (23, 19),
2181                    (24, 18),
2182                    (25, 17),
2183                    (26, 16),
2184                    (27, 15),
2185                    (28, 14),
2186                    (29, 13),
2187                    (30, 12),
2188                    (31, 11),
2189                    (32, 10),
2190                    (33, 9),
2191                    (34, 8),
2192                    (35, 7),
2193                    (36, 6),
2194                    (37, 5),
2195                    (38, 4),
2196                    (39, 3),
2197                    (40, 2),
2198                    (41, 1),
2199                ],
2200                42,
2201                // root: 10
2202                Some(10),
2203                // when slots 1 - 6 were voted on in slot 12, they earned 7, 8, 9, 10, 11, and 12 credits
2204                // when slots 7 - 10 were voted on in slot 11, they earned 14, 15, 16, and 16 credits
2205                7 + 8 + 9 + 10 + 11 + 12 + 14 + 15 + 16 + 16,
2206            ),
2207        ];
2208
2209        // Process the vote state updates in sequence and ensure that the credits earned after each is processed is
2210        // correct
2211        test_vote_state_updates
2212            .iter()
2213            .for_each(|proposed_vote_state| {
2214                let new_state = proposed_vote_state
2215                    .0 // proposed_vote_state.0 is the set of slots and confirmation_counts that is the proposed vote state
2216                    .iter()
2217                    .map(|(slot, confirmation_count)| LandedVote {
2218                        latency: 0,
2219                        lockout: Lockout::new_with_confirmation_count(*slot, *confirmation_count),
2220                    })
2221                    .collect::<VecDeque<LandedVote>>();
2222                assert_eq!(
2223                    process_new_vote_state(
2224                        &mut vote_state,
2225                        new_state,
2226                        proposed_vote_state.2, // proposed_vote_state.2 is root after processing the proposed vote state
2227                        None,
2228                        0,
2229                        proposed_vote_state.1, // proposed_vote_state.1 is the slot in which the proposed vote state was applied
2230                    ),
2231                    Ok(())
2232                );
2233
2234                // Ensure that the credits earned is correct
2235                assert_eq!(vote_state.credits(), proposed_vote_state.3 as u64);
2236            });
2237    }
2238
2239    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2240    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2241    fn test_process_new_vote_too_many_votes(mut vote_state1: VoteStateHandler) {
2242        let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
2243            .map(|slot| {
2244                Lockout::new_with_confirmation_count(
2245                    slot as Slot,
2246                    (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
2247                )
2248            })
2249            .collect();
2250
2251        let current_epoch = vote_state1.current_epoch();
2252        assert_eq!(
2253            process_new_vote_state_from_lockouts(
2254                &mut vote_state1,
2255                bad_votes,
2256                None,
2257                None,
2258                current_epoch,
2259            ),
2260            Err(VoteError::TooManyVotes)
2261        );
2262    }
2263
2264    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2265    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2266    fn test_process_new_vote_state_root_rollback(mut vote_state1: VoteStateHandler) {
2267        for i in 0..MAX_LOCKOUT_HISTORY + 2 {
2268            process_slot_vote_unchecked(&mut vote_state1, i as Slot);
2269        }
2270        assert_eq!(vote_state1.root_slot().unwrap(), 1);
2271
2272        // Update vote_state2 with a higher slot so that `process_new_vote_state`
2273        // doesn't panic.
2274        let mut vote_state2 = vote_state1.clone();
2275        process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);
2276
2277        // Trying to set a lesser root should error
2278        let lesser_root = Some(0);
2279
2280        let current_epoch = vote_state2.current_epoch();
2281        assert_eq!(
2282            process_new_vote_state(
2283                &mut vote_state1,
2284                vote_state2.votes().clone(),
2285                lesser_root,
2286                None,
2287                current_epoch,
2288                0,
2289            ),
2290            Err(VoteError::RootRollBack)
2291        );
2292
2293        // Trying to set root to None should error
2294        let none_root = None;
2295        assert_eq!(
2296            process_new_vote_state(
2297                &mut vote_state1,
2298                vote_state2.votes().clone(),
2299                none_root,
2300                None,
2301                current_epoch,
2302                0,
2303            ),
2304            Err(VoteError::RootRollBack)
2305        );
2306    }
2307
2308    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2309    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2310    fn test_process_new_vote_state_zero_confirmations(mut vote_state1: VoteStateHandler) {
2311        let current_epoch = vote_state1.current_epoch();
2312
2313        let bad_votes: VecDeque<Lockout> = vec![
2314            Lockout::new_with_confirmation_count(0, 0),
2315            Lockout::new_with_confirmation_count(1, 1),
2316        ]
2317        .into_iter()
2318        .collect();
2319        assert_eq!(
2320            process_new_vote_state_from_lockouts(
2321                &mut vote_state1,
2322                bad_votes,
2323                None,
2324                None,
2325                current_epoch,
2326            ),
2327            Err(VoteError::ZeroConfirmations)
2328        );
2329
2330        let bad_votes: VecDeque<Lockout> = vec![
2331            Lockout::new_with_confirmation_count(0, 2),
2332            Lockout::new_with_confirmation_count(1, 0),
2333        ]
2334        .into_iter()
2335        .collect();
2336        assert_eq!(
2337            process_new_vote_state_from_lockouts(
2338                &mut vote_state1,
2339                bad_votes,
2340                None,
2341                None,
2342                current_epoch,
2343            ),
2344            Err(VoteError::ZeroConfirmations)
2345        );
2346    }
2347
2348    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2349    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2350    fn test_process_new_vote_state_confirmations_too_large(initial_vote_state: VoteStateHandler) {
2351        let mut vote_state1 = initial_vote_state.clone();
2352        let current_epoch = vote_state1.current_epoch();
2353
2354        let good_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2355            0,
2356            MAX_LOCKOUT_HISTORY as u32,
2357        )]
2358        .into_iter()
2359        .collect();
2360
2361        process_new_vote_state_from_lockouts(
2362            &mut vote_state1,
2363            good_votes,
2364            None,
2365            None,
2366            current_epoch,
2367        )
2368        .unwrap();
2369
2370        let mut vote_state1 = initial_vote_state;
2371        let bad_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2372            0,
2373            MAX_LOCKOUT_HISTORY as u32 + 1,
2374        )]
2375        .into_iter()
2376        .collect();
2377        assert_eq!(
2378            process_new_vote_state_from_lockouts(
2379                &mut vote_state1,
2380                bad_votes,
2381                None,
2382                None,
2383                current_epoch,
2384            ),
2385            Err(VoteError::ConfirmationTooLarge)
2386        );
2387    }
2388
2389    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2390    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2391    fn test_process_new_vote_state_slot_smaller_than_root(mut vote_state1: VoteStateHandler) {
2392        let current_epoch = vote_state1.current_epoch();
2393        let root_slot = 5;
2394
2395        let bad_votes: VecDeque<Lockout> = vec![
2396            Lockout::new_with_confirmation_count(root_slot, 2),
2397            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2398        ]
2399        .into_iter()
2400        .collect();
2401        assert_eq!(
2402            process_new_vote_state_from_lockouts(
2403                &mut vote_state1,
2404                bad_votes,
2405                Some(root_slot),
2406                None,
2407                current_epoch,
2408            ),
2409            Err(VoteError::SlotSmallerThanRoot)
2410        );
2411
2412        let bad_votes: VecDeque<Lockout> = vec![
2413            Lockout::new_with_confirmation_count(root_slot - 1, 2),
2414            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2415        ]
2416        .into_iter()
2417        .collect();
2418        assert_eq!(
2419            process_new_vote_state_from_lockouts(
2420                &mut vote_state1,
2421                bad_votes,
2422                Some(root_slot),
2423                None,
2424                current_epoch,
2425            ),
2426            Err(VoteError::SlotSmallerThanRoot)
2427        );
2428    }
2429
2430    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2431    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2432    fn test_process_new_vote_state_slots_not_ordered(mut vote_state1: VoteStateHandler) {
2433        let current_epoch = vote_state1.current_epoch();
2434
2435        let bad_votes: VecDeque<Lockout> = vec![
2436            Lockout::new_with_confirmation_count(1, 2),
2437            Lockout::new_with_confirmation_count(0, 1),
2438        ]
2439        .into_iter()
2440        .collect();
2441        assert_eq!(
2442            process_new_vote_state_from_lockouts(
2443                &mut vote_state1,
2444                bad_votes,
2445                None,
2446                None,
2447                current_epoch,
2448            ),
2449            Err(VoteError::SlotsNotOrdered)
2450        );
2451
2452        let bad_votes: VecDeque<Lockout> = vec![
2453            Lockout::new_with_confirmation_count(1, 2),
2454            Lockout::new_with_confirmation_count(1, 1),
2455        ]
2456        .into_iter()
2457        .collect();
2458        assert_eq!(
2459            process_new_vote_state_from_lockouts(
2460                &mut vote_state1,
2461                bad_votes,
2462                None,
2463                None,
2464                current_epoch,
2465            ),
2466            Err(VoteError::SlotsNotOrdered)
2467        );
2468    }
2469
2470    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2471    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2472    fn test_process_new_vote_state_confirmations_not_ordered(mut vote_state1: VoteStateHandler) {
2473        let current_epoch = vote_state1.current_epoch();
2474
2475        let bad_votes: VecDeque<Lockout> = vec![
2476            Lockout::new_with_confirmation_count(0, 1),
2477            Lockout::new_with_confirmation_count(1, 2),
2478        ]
2479        .into_iter()
2480        .collect();
2481        assert_eq!(
2482            process_new_vote_state_from_lockouts(
2483                &mut vote_state1,
2484                bad_votes,
2485                None,
2486                None,
2487                current_epoch,
2488            ),
2489            Err(VoteError::ConfirmationsNotOrdered)
2490        );
2491
2492        let bad_votes: VecDeque<Lockout> = vec![
2493            Lockout::new_with_confirmation_count(0, 1),
2494            Lockout::new_with_confirmation_count(1, 1),
2495        ]
2496        .into_iter()
2497        .collect();
2498        assert_eq!(
2499            process_new_vote_state_from_lockouts(
2500                &mut vote_state1,
2501                bad_votes,
2502                None,
2503                None,
2504                current_epoch,
2505            ),
2506            Err(VoteError::ConfirmationsNotOrdered)
2507        );
2508    }
2509
2510    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2511    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2512    fn test_process_new_vote_state_new_vote_state_lockout_mismatch(
2513        mut vote_state1: VoteStateHandler,
2514    ) {
2515        let current_epoch = vote_state1.current_epoch();
2516
2517        let bad_votes: VecDeque<Lockout> = vec![
2518            Lockout::new_with_confirmation_count(0, 2),
2519            Lockout::new_with_confirmation_count(7, 1),
2520        ]
2521        .into_iter()
2522        .collect();
2523
2524        // Slot 7 should have expired slot 0
2525        assert_eq!(
2526            process_new_vote_state_from_lockouts(
2527                &mut vote_state1,
2528                bad_votes,
2529                None,
2530                None,
2531                current_epoch,
2532            ),
2533            Err(VoteError::NewVoteStateLockoutMismatch)
2534        );
2535    }
2536
2537    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2538    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2539    fn test_process_new_vote_state_confirmation_rollback(mut vote_state1: VoteStateHandler) {
2540        let current_epoch = vote_state1.current_epoch();
2541        let votes: VecDeque<Lockout> = vec![
2542            Lockout::new_with_confirmation_count(0, 4),
2543            Lockout::new_with_confirmation_count(1, 3),
2544        ]
2545        .into_iter()
2546        .collect();
2547        process_new_vote_state_from_lockouts(&mut vote_state1, votes, None, None, current_epoch)
2548            .unwrap();
2549
2550        let votes: VecDeque<Lockout> = vec![
2551            Lockout::new_with_confirmation_count(0, 4),
2552            // Confirmation count lowered illegally
2553            Lockout::new_with_confirmation_count(1, 2),
2554            Lockout::new_with_confirmation_count(2, 1),
2555        ]
2556        .into_iter()
2557        .collect();
2558        // Should error because newer vote state should not have lower confirmation the same slot
2559        // 1
2560        assert_eq!(
2561            process_new_vote_state_from_lockouts(
2562                &mut vote_state1,
2563                votes,
2564                None,
2565                None,
2566                current_epoch,
2567            ),
2568            Err(VoteError::ConfirmationRollBack)
2569        );
2570    }
2571
2572    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2573    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2574    fn test_process_new_vote_state_root_progress(mut vote_state1: VoteStateHandler) {
2575        for i in 0..MAX_LOCKOUT_HISTORY {
2576            process_slot_vote_unchecked(&mut vote_state1, i as u64);
2577        }
2578
2579        assert!(vote_state1.root_slot().is_none());
2580        let mut vote_state2 = vote_state1.clone();
2581
2582        // 1) Try to update `vote_state1` with no root,
2583        // to `vote_state2`, which has a new root, should succeed.
2584        //
2585        // 2) Then try to update`vote_state1` with an existing root,
2586        // to `vote_state2`, which has a newer root, which
2587        // should succeed.
2588        for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
2589            process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
2590            assert_ne!(vote_state1.root_slot(), vote_state2.root_slot());
2591
2592            process_new_vote_state(
2593                &mut vote_state1,
2594                vote_state2.votes().clone(),
2595                vote_state2.root_slot(),
2596                None,
2597                vote_state2.current_epoch(),
2598                0,
2599            )
2600            .unwrap();
2601
2602            assert_eq!(vote_state1, vote_state2);
2603        }
2604    }
2605
2606    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2607    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2608    fn test_process_new_vote_state_same_slot_but_not_common_ancestor(
2609        initial_vote_state: VoteStateHandler,
2610    ) {
2611        // It might be possible that during the switch from old vote instructions
2612        // to new vote instructions, new_state contains votes for slots LESS
2613        // than the current state, for instance:
2614        //
2615        // Current on-chain state: 1, 5
2616        // New state: 1, 2 (lockout: 4), 3, 5, 7
2617        //
2618        // Imagine the validator made two of these votes:
2619        // 1) The first vote {1, 2, 3} didn't land in the old state, but didn't
2620        // land on chain
2621        // 2) A second vote {1, 2, 5} was then submitted, which landed
2622        //
2623        //
2624        // 2 is not popped off in the local tower because 3 doubled the lockout.
2625        // However, 3 did not land in the on-chain state, so the vote {1, 2, 6}
2626        // will immediately pop off 2.
2627
2628        // Construct on-chain vote state
2629        let mut vote_state1 = initial_vote_state.clone();
2630        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
2631        assert_eq!(
2632            vote_state1
2633                .votes()
2634                .iter()
2635                .map(|vote| vote.slot())
2636                .collect::<Vec<Slot>>(),
2637            vec![1, 5]
2638        );
2639
2640        // Construct local tower state
2641        let mut vote_state2 = initial_vote_state;
2642        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
2643        assert_eq!(
2644            vote_state2
2645                .votes()
2646                .iter()
2647                .map(|vote| vote.slot())
2648                .collect::<Vec<Slot>>(),
2649            vec![1, 2, 3, 5, 7]
2650        );
2651
2652        // See that on-chain vote state can update properly
2653        process_new_vote_state(
2654            &mut vote_state1,
2655            vote_state2.votes().clone(),
2656            vote_state2.root_slot(),
2657            None,
2658            vote_state2.current_epoch(),
2659            0,
2660        )
2661        .unwrap();
2662
2663        assert_eq!(vote_state1, vote_state2);
2664    }
2665
2666    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2667    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2668    fn test_process_new_vote_state_lockout_violation(initial_vote_state: VoteStateHandler) {
2669        // Construct on-chain vote state
2670        let mut vote_state1 = initial_vote_state.clone();
2671        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
2672        assert_eq!(
2673            vote_state1
2674                .votes()
2675                .iter()
2676                .map(|vote| vote.slot())
2677                .collect::<Vec<Slot>>(),
2678            vec![1, 2, 4, 5]
2679        );
2680
2681        // Construct conflicting tower state. Vote 4 is missing,
2682        // but 5 should not have popped off vote 4.
2683        let mut vote_state2 = initial_vote_state;
2684        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
2685        assert_eq!(
2686            vote_state2
2687                .votes()
2688                .iter()
2689                .map(|vote| vote.slot())
2690                .collect::<Vec<Slot>>(),
2691            vec![1, 2, 3, 5, 7]
2692        );
2693
2694        // See that on-chain vote state can update properly
2695        assert_eq!(
2696            process_new_vote_state(
2697                &mut vote_state1,
2698                vote_state2.votes().clone(),
2699                vote_state2.root_slot(),
2700                None,
2701                vote_state2.current_epoch(),
2702                0,
2703            ),
2704            Err(VoteError::LockoutConflict)
2705        );
2706    }
2707
2708    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2709    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2710    fn test_process_new_vote_state_lockout_violation2(initial_vote_state: VoteStateHandler) {
2711        // Construct on-chain vote state
2712        let mut vote_state1 = initial_vote_state.clone();
2713        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
2714        assert_eq!(
2715            vote_state1
2716                .votes()
2717                .iter()
2718                .map(|vote| vote.slot())
2719                .collect::<Vec<Slot>>(),
2720            vec![1, 5, 6, 7]
2721        );
2722
2723        // Construct a new vote state. Violates on-chain state because 8
2724        // should not have popped off 7
2725        let mut vote_state2 = initial_vote_state;
2726        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
2727        assert_eq!(
2728            vote_state2
2729                .votes()
2730                .iter()
2731                .map(|vote| vote.slot())
2732                .collect::<Vec<Slot>>(),
2733            vec![1, 2, 3, 5, 6, 8]
2734        );
2735
2736        // Both vote states contain `5`, but `5` is not part of the common prefix
2737        // of both vote states. However, the violation should still be detected.
2738        assert_eq!(
2739            process_new_vote_state(
2740                &mut vote_state1,
2741                vote_state2.votes().clone(),
2742                vote_state2.root_slot(),
2743                None,
2744                vote_state2.current_epoch(),
2745                0,
2746            ),
2747            Err(VoteError::LockoutConflict)
2748        );
2749    }
2750
2751    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2752    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2753    fn test_process_new_vote_state_expired_ancestor_not_removed(mut vote_state1: VoteStateHandler) {
2754        // Construct on-chain vote state
2755        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
2756        assert_eq!(
2757            vote_state1
2758                .votes()
2759                .iter()
2760                .map(|vote| vote.slot())
2761                .collect::<Vec<Slot>>(),
2762            vec![1, 9]
2763        );
2764
2765        // Example: {1: lockout 8, 9: lockout 2}, vote on 10 will not pop off 1
2766        // because 9 is not popped off yet
2767        let mut vote_state2 = vote_state1.clone();
2768        process_slot_vote_unchecked(&mut vote_state2, 10);
2769
2770        // Slot 1 has been expired by 10, but is kept alive by its descendant
2771        // 9 which has not been expired yet.
2772        assert_eq!(vote_state2.votes()[0].slot(), 1);
2773        assert_eq!(vote_state2.votes()[0].lockout.last_locked_out_slot(), 9);
2774        assert_eq!(
2775            vote_state2
2776                .votes()
2777                .iter()
2778                .map(|vote| vote.slot())
2779                .collect::<Vec<Slot>>(),
2780            vec![1, 9, 10]
2781        );
2782
2783        // Should be able to update vote_state1
2784        process_new_vote_state(
2785            &mut vote_state1,
2786            vote_state2.votes().clone(),
2787            vote_state2.root_slot(),
2788            None,
2789            vote_state2.current_epoch(),
2790            0,
2791        )
2792        .unwrap();
2793        assert_eq!(vote_state1, vote_state2,);
2794    }
2795
2796    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2797    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2798    fn test_process_new_vote_current_state_contains_bigger_slots(
2799        mut vote_state1: VoteStateHandler,
2800    ) {
2801        process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
2802        assert_eq!(
2803            vote_state1
2804                .votes()
2805                .iter()
2806                .map(|vote| vote.slot())
2807                .collect::<Vec<Slot>>(),
2808            vec![6, 7, 8]
2809        );
2810
2811        // Try to process something with lockout violations
2812        let bad_votes: VecDeque<Lockout> = vec![
2813            Lockout::new_with_confirmation_count(2, 5),
2814            // Slot 14 could not have popped off slot 6 yet
2815            Lockout::new_with_confirmation_count(14, 1),
2816        ]
2817        .into_iter()
2818        .collect();
2819        let root = Some(1);
2820
2821        let current_epoch = vote_state1.current_epoch();
2822        assert_eq!(
2823            process_new_vote_state_from_lockouts(
2824                &mut vote_state1,
2825                bad_votes,
2826                root,
2827                None,
2828                current_epoch,
2829            ),
2830            Err(VoteError::LockoutConflict)
2831        );
2832
2833        let good_votes: VecDeque<LandedVote> = vec![
2834            Lockout::new_with_confirmation_count(2, 5).into(),
2835            Lockout::new_with_confirmation_count(15, 1).into(),
2836        ]
2837        .into_iter()
2838        .collect();
2839
2840        let current_epoch = vote_state1.current_epoch();
2841        process_new_vote_state(
2842            &mut vote_state1,
2843            good_votes.clone(),
2844            root,
2845            None,
2846            current_epoch,
2847            0,
2848        )
2849        .unwrap();
2850        assert_eq!(*vote_state1.votes(), good_votes);
2851    }
2852
2853    #[test_case(VoteStateHandler::default_v3() ; "VoteStateV3")]
2854    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2855    fn test_filter_old_votes(mut vote_state: VoteStateHandler) {
2856        let old_vote_slot = 1;
2857        let vote = Vote::new(vec![old_vote_slot], Hash::default());
2858
2859        // Vote with all slots that are all older than the SlotHashes history should
2860        // error with `VotesTooOldAllFiltered`
2861        let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
2862        assert_eq!(
2863            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2864            Err(VoteError::VotesTooOldAllFiltered)
2865        );
2866
2867        // Vote with only some slots older than the SlotHashes history should
2868        // filter out those older slots
2869        let vote_slot = 2;
2870        let vote_slot_hash = slot_hashes
2871            .iter()
2872            .find(|(slot, _hash)| *slot == vote_slot)
2873            .unwrap()
2874            .1;
2875
2876        let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
2877        process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap();
2878        assert_eq!(
2879            vote_state
2880                .votes()
2881                .iter()
2882                .map(|vote| vote.lockout)
2883                .collect::<Vec<Lockout>>(),
2884            vec![Lockout::new_with_confirmation_count(vote_slot, 1)]
2885        );
2886    }
2887
2888    fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
2889        slots
2890            .iter()
2891            .rev()
2892            .map(|x| (*x, Hash::new_unique()))
2893            .collect()
2894    }
2895
2896    fn build_vote_state(
2897        target_version: VoteStateTargetVersion,
2898        vote_slots: Vec<Slot>,
2899        slot_hashes: &[(Slot, Hash)],
2900    ) -> VoteStateHandler {
2901        let mut vote_state = match target_version {
2902            VoteStateTargetVersion::V3 => VoteStateHandler::default_v3(),
2903            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2904        };
2905
2906        if !vote_slots.is_empty() {
2907            let vote_hash = slot_hashes
2908                .iter()
2909                .find(|(slot, _hash)| slot == vote_slots.last().unwrap())
2910                .unwrap()
2911                .1;
2912            let vote = Vote::new(vote_slots, vote_hash);
2913            process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0)
2914                .unwrap();
2915        }
2916
2917        vote_state
2918    }
2919
2920    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
2921    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2922    fn test_check_and_filter_proposed_vote_state_empty(target_version: VoteStateTargetVersion) {
2923        let empty_slot_hashes = build_slot_hashes(vec![]);
2924        let empty_vote_state = build_vote_state(target_version, vec![], &empty_slot_hashes);
2925
2926        // Test with empty TowerSync, should return EmptySlots error
2927        let mut tower_sync = TowerSync::from(vec![]);
2928        assert_eq!(
2929            check_and_filter_proposed_vote_state(
2930                &empty_vote_state,
2931                &mut tower_sync.lockouts,
2932                &mut tower_sync.root,
2933                tower_sync.hash,
2934                &empty_slot_hashes
2935            ),
2936            Err(VoteError::EmptySlots),
2937        );
2938
2939        // Test with non-empty TowerSync, should return SlotsMismatch since nothing exists in SlotHashes
2940        let mut tower_sync = TowerSync::from(vec![(0, 1)]);
2941        assert_eq!(
2942            check_and_filter_proposed_vote_state(
2943                &empty_vote_state,
2944                &mut tower_sync.lockouts,
2945                &mut tower_sync.root,
2946                tower_sync.hash,
2947                &empty_slot_hashes
2948            ),
2949            Err(VoteError::SlotsMismatch),
2950        );
2951    }
2952
2953    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
2954    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2955    fn test_check_and_filter_proposed_vote_state_too_old(target_version: VoteStateTargetVersion) {
2956        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
2957        let latest_vote = 4;
2958        let vote_state = build_vote_state(target_version, vec![1, 2, 3, latest_vote], &slot_hashes);
2959
2960        // Test with a vote for a slot less than the latest vote in the vote_state,
2961        // should return error `VoteTooOld`
2962        let mut tower_sync = TowerSync::from(vec![(latest_vote, 1)]);
2963        assert_eq!(
2964            check_and_filter_proposed_vote_state(
2965                &vote_state,
2966                &mut tower_sync.lockouts,
2967                &mut tower_sync.root,
2968                tower_sync.hash,
2969                &slot_hashes
2970            ),
2971            Err(VoteError::VoteTooOld),
2972        );
2973
2974        // Test with a vote state update where the latest slot `X` in the update is
2975        // 1) Less than the earliest slot in slot_hashes history, AND
2976        // 2) `X` > latest_vote
2977        let earliest_slot_in_history = latest_vote + 2;
2978        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
2979        let mut tower_sync = TowerSync::from(vec![(earliest_slot_in_history - 1, 1)]);
2980        assert_eq!(
2981            check_and_filter_proposed_vote_state(
2982                &vote_state,
2983                &mut tower_sync.lockouts,
2984                &mut tower_sync.root,
2985                tower_sync.hash,
2986                &slot_hashes
2987            ),
2988            Err(VoteError::VoteTooOld),
2989        );
2990    }
2991
2992    fn run_test_check_and_filter_proposed_vote_state_older_than_history_root(
2993        target_version: VoteStateTargetVersion,
2994        earliest_slot_in_history: Slot,
2995        current_vote_state_slots: Vec<Slot>,
2996        current_vote_state_root: Option<Slot>,
2997        proposed_slots_and_lockouts: Vec<(Slot, u32)>,
2998        proposed_root: Slot,
2999        expected_root: Option<Slot>,
3000        expected_vote_state: Vec<Lockout>,
3001    ) {
3002        assert!(proposed_root < earliest_slot_in_history);
3003        assert_eq!(
3004            expected_root,
3005            current_vote_state_slots
3006                .iter()
3007                .rev()
3008                .find(|slot| **slot <= proposed_root)
3009                .cloned()
3010        );
3011        let latest_slot_in_history = proposed_slots_and_lockouts
3012            .last()
3013            .unwrap()
3014            .0
3015            .max(earliest_slot_in_history);
3016        let mut slot_hashes = build_slot_hashes(
3017            (current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
3018                .collect::<Vec<Slot>>(),
3019        );
3020
3021        let mut vote_state =
3022            build_vote_state(target_version, current_vote_state_slots, &slot_hashes);
3023        vote_state.set_root_slot(current_vote_state_root);
3024
3025        slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
3026        assert!(!proposed_slots_and_lockouts.is_empty());
3027        let proposed_hash = slot_hashes
3028            .iter()
3029            .find(|(slot, _hash)| *slot == proposed_slots_and_lockouts.last().unwrap().0)
3030            .unwrap()
3031            .1;
3032
3033        // Test with a `TowerSync` where the root is less than `earliest_slot_in_history`.
3034        // Root slot in the `TowerSync` should be updated to match the root slot in the
3035        // current vote state
3036        let mut tower_sync = TowerSync::from(proposed_slots_and_lockouts);
3037        tower_sync.hash = proposed_hash;
3038        tower_sync.root = Some(proposed_root);
3039        check_and_filter_proposed_vote_state(
3040            &vote_state,
3041            &mut tower_sync.lockouts,
3042            &mut tower_sync.root,
3043            tower_sync.hash,
3044            &slot_hashes,
3045        )
3046        .unwrap();
3047        assert_eq!(tower_sync.root, expected_root);
3048
3049        // The proposed root slot should become the biggest slot in the current vote state less than
3050        // `earliest_slot_in_history`.
3051        assert!(
3052            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync.clone(),).is_ok()
3053        );
3054        assert_eq!(vote_state.root_slot(), expected_root);
3055        assert_eq!(
3056            vote_state
3057                .votes()
3058                .iter()
3059                .map(|vote| vote.lockout)
3060                .collect::<Vec<Lockout>>(),
3061            expected_vote_state,
3062        );
3063    }
3064
3065    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3066    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3067    fn test_check_and_filter_proposed_vote_state_older_than_history_root(
3068        target_version: VoteStateTargetVersion,
3069    ) {
3070        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3071        // slot
3072        let earliest_slot_in_history = 5;
3073        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3074        let current_vote_state_root = None;
3075        let proposed_slots_and_lockouts = vec![(5, 1)];
3076        let proposed_root = 4;
3077        let expected_root = Some(4);
3078        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3079        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3080            target_version,
3081            earliest_slot_in_history,
3082            current_vote_state_slots,
3083            current_vote_state_root,
3084            proposed_slots_and_lockouts,
3085            proposed_root,
3086            expected_root,
3087            expected_vote_state,
3088        );
3089
3090        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3091        // slot and the `current_vote_state_root.is_some()`.
3092        let earliest_slot_in_history = 5;
3093        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3094        let current_vote_state_root = Some(0);
3095        let proposed_slots_and_lockouts = vec![(5, 1)];
3096        let proposed_root = 4;
3097        let expected_root = Some(4);
3098        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3099        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3100            target_version,
3101            earliest_slot_in_history,
3102            current_vote_state_slots,
3103            current_vote_state_root,
3104            proposed_slots_and_lockouts,
3105            proposed_root,
3106            expected_root,
3107            expected_vote_state,
3108        );
3109
3110        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3111        // slot
3112        let earliest_slot_in_history = 5;
3113        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3114        let current_vote_state_root = Some(0);
3115        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3116        let proposed_root = 3;
3117        let expected_root = Some(3);
3118        let expected_vote_state = vec![
3119            Lockout::new_with_confirmation_count(4, 2),
3120            Lockout::new_with_confirmation_count(5, 1),
3121        ];
3122        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3123            target_version,
3124            earliest_slot_in_history,
3125            current_vote_state_slots,
3126            current_vote_state_root,
3127            proposed_slots_and_lockouts,
3128            proposed_root,
3129            expected_root,
3130            expected_vote_state,
3131        );
3132
3133        // Test when `proposed_root` is not in `current_vote_state_slots`
3134        let earliest_slot_in_history = 5;
3135        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
3136        let current_vote_state_root = Some(0);
3137        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3138        let proposed_root = 3;
3139        let expected_root = Some(2);
3140        let expected_vote_state = vec![
3141            Lockout::new_with_confirmation_count(4, 2),
3142            Lockout::new_with_confirmation_count(5, 1),
3143        ];
3144        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3145            target_version,
3146            earliest_slot_in_history,
3147            current_vote_state_slots,
3148            current_vote_state_root,
3149            proposed_slots_and_lockouts,
3150            proposed_root,
3151            expected_root,
3152            expected_vote_state,
3153        );
3154
3155        // Test when the `proposed_root` is smaller than all the slots in
3156        // `current_vote_state_slots`, no roots should be set.
3157        let earliest_slot_in_history = 4;
3158        let current_vote_state_slots: Vec<Slot> = vec![3, 4];
3159        let current_vote_state_root = None;
3160        let proposed_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
3161        let proposed_root = 2;
3162        let expected_root = None;
3163        let expected_vote_state = vec![
3164            Lockout::new_with_confirmation_count(3, 3),
3165            Lockout::new_with_confirmation_count(4, 2),
3166            Lockout::new_with_confirmation_count(5, 1),
3167        ];
3168        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3169            target_version,
3170            earliest_slot_in_history,
3171            current_vote_state_slots,
3172            current_vote_state_root,
3173            proposed_slots_and_lockouts,
3174            proposed_root,
3175            expected_root,
3176            expected_vote_state,
3177        );
3178
3179        // Test when `current_vote_state_slots` is empty, no roots should be set
3180        let earliest_slot_in_history = 4;
3181        let current_vote_state_slots: Vec<Slot> = vec![];
3182        let current_vote_state_root = None;
3183        let proposed_slots_and_lockouts = vec![(5, 1)];
3184        let proposed_root = 2;
3185        let expected_root = None;
3186        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3187        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3188            target_version,
3189            earliest_slot_in_history,
3190            current_vote_state_slots,
3191            current_vote_state_root,
3192            proposed_slots_and_lockouts,
3193            proposed_root,
3194            expected_root,
3195            expected_vote_state,
3196        );
3197    }
3198
3199    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3200    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3201    fn test_check_and_filter_proposed_vote_state_slots_not_ordered(
3202        target_version: VoteStateTargetVersion,
3203    ) {
3204        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3205        let vote_state = build_vote_state(target_version, vec![1], &slot_hashes);
3206
3207        // Test with a `TowerSync` where the slots are out of order
3208        let vote_slot = 3;
3209        let vote_slot_hash = slot_hashes
3210            .iter()
3211            .find(|(slot, _hash)| *slot == vote_slot)
3212            .unwrap()
3213            .1;
3214        let mut tower_sync = TowerSync::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
3215        tower_sync.hash = vote_slot_hash;
3216        assert_eq!(
3217            check_and_filter_proposed_vote_state(
3218                &vote_state,
3219                &mut tower_sync.lockouts,
3220                &mut tower_sync.root,
3221                tower_sync.hash,
3222                &slot_hashes
3223            ),
3224            Err(VoteError::SlotsNotOrdered),
3225        );
3226
3227        // Test with a `TowerSync` where there are multiples of the same slot
3228        let mut tower_sync = TowerSync::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
3229        tower_sync.hash = vote_slot_hash;
3230        assert_eq!(
3231            check_and_filter_proposed_vote_state(
3232                &vote_state,
3233                &mut tower_sync.lockouts,
3234                &mut tower_sync.root,
3235                tower_sync.hash,
3236                &slot_hashes
3237            ),
3238            Err(VoteError::SlotsNotOrdered),
3239        );
3240    }
3241
3242    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3243    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3244    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered(
3245        target_version: VoteStateTargetVersion,
3246    ) {
3247        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3248        let mut vote_state = build_vote_state(target_version, vec![1, 2, 3, 4], &slot_hashes);
3249
3250        // Test with a `TowerSync` where there:
3251        // 1) Exists a slot less than `earliest_slot_in_history`
3252        // 2) This slot does not exist in the vote state already
3253        // This slot should be filtered out
3254        let earliest_slot_in_history = 11;
3255        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3256        let vote_slot = 12;
3257        let vote_slot_hash = slot_hashes
3258            .iter()
3259            .find(|(slot, _hash)| *slot == vote_slot)
3260            .unwrap()
3261            .1;
3262        let missing_older_than_history_slot = earliest_slot_in_history - 1;
3263        let mut tower_sync = TowerSync::from(vec![
3264            (1, 4),
3265            (missing_older_than_history_slot, 2),
3266            (vote_slot, 3),
3267        ]);
3268        tower_sync.hash = vote_slot_hash;
3269        check_and_filter_proposed_vote_state(
3270            &vote_state,
3271            &mut tower_sync.lockouts,
3272            &mut tower_sync.root,
3273            tower_sync.hash,
3274            &slot_hashes,
3275        )
3276        .unwrap();
3277
3278        // Check the earlier slot was filtered out
3279        assert_eq!(
3280            tower_sync
3281                .clone()
3282                .lockouts
3283                .into_iter()
3284                .collect::<Vec<Lockout>>(),
3285            vec![
3286                Lockout::new_with_confirmation_count(1, 4),
3287                Lockout::new_with_confirmation_count(vote_slot, 3)
3288            ]
3289        );
3290        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3291    }
3292
3293    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3294    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3295    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_not_filtered(
3296        target_version: VoteStateTargetVersion,
3297    ) {
3298        let slot_hashes = build_slot_hashes(vec![4]);
3299        let mut vote_state = build_vote_state(target_version, vec![4], &slot_hashes);
3300
3301        // Test with a `TowerSync` where there:
3302        // 1) Exists a slot less than `earliest_slot_in_history`
3303        // 2) This slot exists in the vote state already
3304        // This slot should *NOT* be filtered out
3305        let earliest_slot_in_history = 11;
3306        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3307        let vote_slot = 12;
3308        let vote_slot_hash = slot_hashes
3309            .iter()
3310            .find(|(slot, _hash)| *slot == vote_slot)
3311            .unwrap()
3312            .1;
3313        let existing_older_than_history_slot = 4;
3314        let mut tower_sync =
3315            TowerSync::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
3316        tower_sync.hash = vote_slot_hash;
3317        check_and_filter_proposed_vote_state(
3318            &vote_state,
3319            &mut tower_sync.lockouts,
3320            &mut tower_sync.root,
3321            tower_sync.hash,
3322            &slot_hashes,
3323        )
3324        .unwrap();
3325        // Check the earlier slot was *NOT* filtered out
3326        assert_eq!(tower_sync.lockouts.len(), 2);
3327        assert_eq!(
3328            tower_sync
3329                .clone()
3330                .lockouts
3331                .into_iter()
3332                .collect::<Vec<Lockout>>(),
3333            vec![
3334                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3335                Lockout::new_with_confirmation_count(vote_slot, 2)
3336            ]
3337        );
3338        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3339    }
3340
3341    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3342    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3343    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered_and_not_filtered(
3344        target_version: VoteStateTargetVersion,
3345    ) {
3346        let slot_hashes = build_slot_hashes(vec![6]);
3347        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3348
3349        // Test with a `TowerSync` where there exists both a slot:
3350        // 1) Less than `earliest_slot_in_history`
3351        // 2) This slot exists in the vote state already
3352        // which should not be filtered
3353        //
3354        // AND a slot that
3355        //
3356        // 1) Less than `earliest_slot_in_history`
3357        // 2) This slot does not exist in the vote state already
3358        // which should be filtered
3359        let earliest_slot_in_history = 11;
3360        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3361        let vote_slot = 14;
3362        let vote_slot_hash = slot_hashes
3363            .iter()
3364            .find(|(slot, _hash)| *slot == vote_slot)
3365            .unwrap()
3366            .1;
3367
3368        let missing_older_than_history_slot = 4;
3369        let existing_older_than_history_slot = 6;
3370
3371        let mut tower_sync = TowerSync::from(vec![
3372            (missing_older_than_history_slot, 4),
3373            (existing_older_than_history_slot, 3),
3374            (12, 2),
3375            (vote_slot, 1),
3376        ]);
3377        tower_sync.hash = vote_slot_hash;
3378        check_and_filter_proposed_vote_state(
3379            &vote_state,
3380            &mut tower_sync.lockouts,
3381            &mut tower_sync.root,
3382            tower_sync.hash,
3383            &slot_hashes,
3384        )
3385        .unwrap();
3386        assert_eq!(tower_sync.lockouts.len(), 3);
3387        assert_eq!(
3388            tower_sync
3389                .clone()
3390                .lockouts
3391                .into_iter()
3392                .collect::<Vec<Lockout>>(),
3393            vec![
3394                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3395                Lockout::new_with_confirmation_count(12, 2),
3396                Lockout::new_with_confirmation_count(vote_slot, 1)
3397            ]
3398        );
3399        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3400    }
3401
3402    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3403    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3404    fn test_check_and_filter_proposed_vote_state_slot_not_on_fork(
3405        target_version: VoteStateTargetVersion,
3406    ) {
3407        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3408        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3409
3410        // Test with a `TowerSync` where there:
3411        // 1) Exists a slot not in the slot hashes history
3412        // 2) The slot is greater than the earliest slot in the history
3413        // Thus this slot is not part of the fork and the update should be rejected
3414        // with error `SlotsMismatch`
3415        let missing_vote_slot = 3;
3416
3417        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3418        // errors
3419        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3420        let vote_slot_hash = slot_hashes
3421            .iter()
3422            .find(|(slot, _hash)| *slot == vote_slot)
3423            .unwrap()
3424            .1;
3425        let mut tower_sync = TowerSync::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
3426        tower_sync.hash = vote_slot_hash;
3427        assert_eq!(
3428            check_and_filter_proposed_vote_state(
3429                &vote_state,
3430                &mut tower_sync.lockouts,
3431                &mut tower_sync.root,
3432                tower_sync.hash,
3433                &slot_hashes
3434            ),
3435            Err(VoteError::SlotsMismatch),
3436        );
3437
3438        // Test where some earlier vote slots exist in the history, but others don't
3439        let missing_vote_slot = 7;
3440        let mut tower_sync = TowerSync::from(vec![
3441            (2, 5),
3442            (4, 4),
3443            (6, 3),
3444            (missing_vote_slot, 2),
3445            (vote_slot, 1),
3446        ]);
3447        tower_sync.hash = vote_slot_hash;
3448        assert_eq!(
3449            check_and_filter_proposed_vote_state(
3450                &vote_state,
3451                &mut tower_sync.lockouts,
3452                &mut tower_sync.root,
3453                tower_sync.hash,
3454                &slot_hashes
3455            ),
3456            Err(VoteError::SlotsMismatch),
3457        );
3458    }
3459
3460    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3461    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3462    fn test_check_and_filter_proposed_vote_state_root_on_different_fork(
3463        target_version: VoteStateTargetVersion,
3464    ) {
3465        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3466        let vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3467
3468        // Test with a `TowerSync` where:
3469        // 1) The root is not present in slot hashes history
3470        // 2) The slot is greater than the earliest slot in the history
3471        // Thus this slot is not part of the fork and the update should be rejected
3472        // with error `RootOnDifferentFork`
3473        let new_root = 3;
3474
3475        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3476        // errors, but also this slot must be present in SlotHashes
3477        let vote_slot = 8;
3478        assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
3479        let vote_slot_hash = slot_hashes
3480            .iter()
3481            .find(|(slot, _hash)| *slot == vote_slot)
3482            .unwrap()
3483            .1;
3484        let mut tower_sync = TowerSync::from(vec![(vote_slot, 1)]);
3485        tower_sync.hash = vote_slot_hash;
3486        tower_sync.root = Some(new_root);
3487        assert_eq!(
3488            check_and_filter_proposed_vote_state(
3489                &vote_state,
3490                &mut tower_sync.lockouts,
3491                &mut tower_sync.root,
3492                tower_sync.hash,
3493                &slot_hashes
3494            ),
3495            Err(VoteError::RootOnDifferentFork),
3496        );
3497    }
3498
3499    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3500    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3501    fn test_check_and_filter_proposed_vote_state_slot_newer_than_slot_history(
3502        target_version: VoteStateTargetVersion,
3503    ) {
3504        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3505        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3506
3507        // Test with a `TowerSync` where there:
3508        // 1) The last slot in the update is a slot not in the slot hashes history
3509        // 2) The slot is greater than the newest slot in the slot history
3510        // Thus this slot is not part of the fork and the update should be rejected
3511        // with error `SlotsMismatch`
3512        let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
3513        let vote_slot_hash = Hash::new_unique();
3514        let mut tower_sync = TowerSync::from(vec![(8, 2), (missing_vote_slot, 3)]);
3515        tower_sync.hash = vote_slot_hash;
3516        assert_eq!(
3517            check_and_filter_proposed_vote_state(
3518                &vote_state,
3519                &mut tower_sync.lockouts,
3520                &mut tower_sync.root,
3521                tower_sync.hash,
3522                &slot_hashes
3523            ),
3524            Err(VoteError::SlotsMismatch),
3525        );
3526    }
3527
3528    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3529    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3530    fn test_check_and_filter_proposed_vote_state_slot_all_slot_hashes_in_update_ok(
3531        target_version: VoteStateTargetVersion,
3532    ) {
3533        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3534        let mut vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3535
3536        // Test with a `TowerSync` where every slot in the history is
3537        // in the update
3538
3539        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3540        // errors
3541        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3542        let vote_slot_hash = slot_hashes
3543            .iter()
3544            .find(|(slot, _hash)| *slot == vote_slot)
3545            .unwrap()
3546            .1;
3547        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3548        tower_sync.hash = vote_slot_hash;
3549        check_and_filter_proposed_vote_state(
3550            &vote_state,
3551            &mut tower_sync.lockouts,
3552            &mut tower_sync.root,
3553            tower_sync.hash,
3554            &slot_hashes,
3555        )
3556        .unwrap();
3557
3558        // Nothing in the update should have been filtered out
3559        assert_eq!(
3560            tower_sync
3561                .clone()
3562                .lockouts
3563                .into_iter()
3564                .collect::<Vec<Lockout>>(),
3565            vec![
3566                Lockout::new_with_confirmation_count(2, 4),
3567                Lockout::new_with_confirmation_count(4, 3),
3568                Lockout::new_with_confirmation_count(6, 2),
3569                Lockout::new_with_confirmation_count(vote_slot, 1)
3570            ]
3571        );
3572
3573        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3574    }
3575
3576    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3577    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3578    fn test_check_and_filter_proposed_vote_state_slot_some_slot_hashes_in_update_ok(
3579        target_version: VoteStateTargetVersion,
3580    ) {
3581        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3582        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3583
3584        // Test with a `TowerSync` where only some slots in the history are
3585        // in the update, and others slots in the history are missing.
3586
3587        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3588        // errors
3589        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3590        let vote_slot_hash = slot_hashes
3591            .iter()
3592            .find(|(slot, _hash)| *slot == vote_slot)
3593            .unwrap()
3594            .1;
3595        let mut tower_sync = TowerSync::from(vec![(4, 2), (vote_slot, 1)]);
3596        tower_sync.hash = vote_slot_hash;
3597        check_and_filter_proposed_vote_state(
3598            &vote_state,
3599            &mut tower_sync.lockouts,
3600            &mut tower_sync.root,
3601            tower_sync.hash,
3602            &slot_hashes,
3603        )
3604        .unwrap();
3605
3606        // Nothing in the update should have been filtered out
3607        assert_eq!(
3608            tower_sync
3609                .clone()
3610                .lockouts
3611                .into_iter()
3612                .collect::<Vec<Lockout>>(),
3613            vec![
3614                Lockout::new_with_confirmation_count(4, 2),
3615                Lockout::new_with_confirmation_count(vote_slot, 1)
3616            ]
3617        );
3618
3619        // Because 6 from the original VoteStateV3
3620        // should not have been popped off in the proposed state,
3621        // we should get a lockout conflict
3622        assert_eq!(
3623            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,),
3624            Err(VoteError::LockoutConflict)
3625        );
3626    }
3627
3628    #[test_case(VoteStateTargetVersion::V3 ; "VoteStateV3")]
3629    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3630    fn test_check_and_filter_proposed_vote_state_slot_hash_mismatch(
3631        target_version: VoteStateTargetVersion,
3632    ) {
3633        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3634        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3635
3636        // Test with a `TowerSync` where the hash is mismatched
3637
3638        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3639        // errors
3640        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3641        let vote_slot_hash = Hash::new_unique();
3642        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3643        tower_sync.hash = vote_slot_hash;
3644        assert_eq!(
3645            check_and_filter_proposed_vote_state(
3646                &vote_state,
3647                &mut tower_sync.lockouts,
3648                &mut tower_sync.root,
3649                tower_sync.hash,
3650                &slot_hashes,
3651            ),
3652            Err(VoteError::SlotHashMismatch),
3653        );
3654    }
3655
3656    #[test_case(0, true; "first slot")]
3657    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
3658    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
3659    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
3660    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
3661    fn test_epoch_half_check(slot: Slot, expected_allowed: bool) {
3662        let epoch_schedule = EpochSchedule::without_warmup();
3663        assert_eq!(
3664            is_commission_update_allowed(slot, &epoch_schedule),
3665            expected_allowed
3666        );
3667    }
3668
3669    #[test]
3670    fn test_warmup_epoch_half_check_with_warmup() {
3671        let epoch_schedule = EpochSchedule::default();
3672        let first_normal_slot = epoch_schedule.first_normal_slot;
3673        // first slot works
3674        assert!(is_commission_update_allowed(0, &epoch_schedule));
3675        // right before first normal slot works, since all warmup slots allow
3676        // commission updates
3677        assert!(is_commission_update_allowed(
3678            first_normal_slot - 1,
3679            &epoch_schedule
3680        ));
3681    }
3682
3683    #[test_case(0, true; "first slot")]
3684    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
3685    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
3686    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
3687    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
3688    fn test_epoch_half_check_with_warmup(slot: Slot, expected_allowed: bool) {
3689        let epoch_schedule = EpochSchedule::default();
3690        let first_normal_slot = epoch_schedule.first_normal_slot;
3691        assert_eq!(
3692            is_commission_update_allowed(first_normal_slot.saturating_add(slot), &epoch_schedule),
3693            expected_allowed
3694        );
3695    }
3696
3697    #[test]
3698    fn test_create_v4_account_with_authorized() {
3699        let node_pubkey = Pubkey::new_unique();
3700        let authorized_voter = Pubkey::new_unique();
3701        let authorized_withdrawer = Pubkey::new_unique();
3702        let bls_pubkey_compressed = [42; 48];
3703        let inflation_rewards_commission_bps = 10000;
3704        let lamports = 100;
3705        let vote_account = create_v4_account_with_authorized(
3706            &node_pubkey,
3707            &authorized_voter,
3708            &authorized_withdrawer,
3709            Some(bls_pubkey_compressed),
3710            inflation_rewards_commission_bps,
3711            lamports,
3712        );
3713        assert_eq!(vote_account.lamports(), lamports);
3714        assert_eq!(vote_account.owner(), &id());
3715        assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
3716        let vote_state_v4 = VoteStateV4::deserialize(vote_account.data(), &node_pubkey).unwrap();
3717        assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
3718        assert_eq!(
3719            vote_state_v4.authorized_voters,
3720            AuthorizedVoters::new(0, authorized_voter)
3721        );
3722        assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
3723        assert_eq!(
3724            vote_state_v4.bls_pubkey_compressed,
3725            Some(bls_pubkey_compressed)
3726        );
3727        assert_eq!(
3728            vote_state_v4.inflation_rewards_commission_bps,
3729            inflation_rewards_commission_bps
3730        );
3731    }
3732
3733    #[test]
3734    fn test_update_validator_identity_syncs_block_revenue_collector() {
3735        let vote_state =
3736            vote_state_new_for_test(&solana_pubkey::new_rand(), VoteStateTargetVersion::V4);
3737        let node_pubkey = *vote_state.node_pubkey();
3738        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
3739
3740        let serialized = vote_state.serialize();
3741        let serialized_len = serialized.len();
3742        let rent = Rent::default();
3743        let lamports = rent.minimum_balance(serialized_len);
3744        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
3745        vote_account.set_data_from_slice(&serialized);
3746
3747        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
3748        let mut transaction_context = TransactionContext::new(
3749            vec![(id(), processor_account), (node_pubkey, vote_account)],
3750            rent,
3751            0,
3752            0,
3753        );
3754        transaction_context
3755            .configure_next_instruction_for_tests(
3756                0,
3757                vec![InstructionAccount::new(1, false, true)],
3758                vec![],
3759            )
3760            .unwrap();
3761        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
3762        let mut borrowed_account = instruction_context
3763            .try_borrow_instruction_account(0)
3764            .unwrap();
3765
3766        let new_node_pubkey = solana_pubkey::new_rand();
3767        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
3768            .into_iter()
3769            .collect();
3770
3771        update_validator_identity(
3772            &mut borrowed_account,
3773            VoteStateTargetVersion::V4,
3774            &new_node_pubkey,
3775            &signers,
3776        )
3777        .unwrap();
3778
3779        // Both `node_pubkey` and `block_revenue_collector` should be set to
3780        // the new node pubkey.
3781        let vote_state =
3782            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
3783        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
3784        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
3785
3786        // Run it again.
3787        let new_node_pubkey = solana_pubkey::new_rand();
3788        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
3789            .into_iter()
3790            .collect();
3791
3792        update_validator_identity(
3793            &mut borrowed_account,
3794            VoteStateTargetVersion::V4,
3795            &new_node_pubkey,
3796            &signers,
3797        )
3798        .unwrap();
3799
3800        let vote_state =
3801            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
3802        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
3803        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
3804    }
3805}