Skip to main content

solana_vote_program/vote_state/
mod.rs

1//! Vote state, vote program
2//! Receive and processes votes from validators
3
4pub mod handler;
5
6pub use solana_vote_interface::state::{vote_state_versions::*, *};
7use {
8    handler::{VoteStateHandler, VoteStateTargetVersion},
9    log::*,
10    solana_account::{AccountSharedData, WritableAccount},
11    solana_bls_signatures::{VerifiableProofOfPossession, keypair::Keypair as BLSKeypair},
12    solana_clock::{Clock, Epoch, Slot},
13    solana_epoch_schedule::EpochSchedule,
14    solana_hash::Hash,
15    solana_instruction::error::InstructionError,
16    solana_program_runtime::invoke_context::InvokeContext,
17    solana_pubkey::Pubkey,
18    solana_rent::Rent,
19    solana_sdk_ids::system_program,
20    solana_slot_hashes::SlotHash,
21    solana_system_interface::instruction as system_instruction,
22    solana_transaction_context::{
23        IndexOfAccount, instruction::InstructionContext,
24        instruction_accounts::BorrowedInstructionAccount,
25    },
26    solana_vote_interface::{error::VoteError, instruction::CommissionKind, program::id},
27    std::{
28        cmp::Ordering,
29        collections::{HashSet, VecDeque},
30    },
31};
32
33fn get_vote_state_handler_checked(
34    vote_account: &BorrowedInstructionAccount,
35    target_version: VoteStateTargetVersion,
36) -> Result<VoteStateHandler, InstructionError> {
37    match target_version {
38        VoteStateTargetVersion::V4 => {
39            // New flow after v4 feature gate activation:
40            // 1. Deserialize as `VoteStateVersions`
41            // 2. Check for uninitialized
42            // 3. Convert
43            let versioned = VoteStateVersions::deserialize(vote_account.get_data())?;
44            if versioned.is_uninitialized() {
45                return Err(InstructionError::UninitializedAccount);
46            }
47            let vote_state =
48                handler::try_convert_to_vote_state_v4(versioned, vote_account.get_key())?;
49            Ok(VoteStateHandler::new_v4(vote_state))
50        }
51    }
52}
53
54/// Checks the proposed vote state with the current and
55/// slot hashes, making adjustments to the root / filtering
56/// votes as needed.
57fn check_and_filter_proposed_vote_state(
58    vote_state: &VoteStateHandler,
59    proposed_lockouts: &mut VecDeque<Lockout>,
60    proposed_root: &mut Option<Slot>,
61    proposed_hash: Hash,
62    slot_hashes: &[(Slot, Hash)],
63) -> Result<(), VoteError> {
64    if proposed_lockouts.is_empty() {
65        return Err(VoteError::EmptySlots);
66    }
67
68    let last_proposed_slot = proposed_lockouts
69        .back()
70        .expect("must be nonempty, checked above")
71        .slot();
72
73    // If the proposed state is not new enough, return
74    if let Some(last_vote_slot) = vote_state.votes().back().map(|lockout| lockout.slot())
75        && last_proposed_slot <= last_vote_slot
76    {
77        return Err(VoteError::VoteTooOld);
78    }
79
80    if slot_hashes.is_empty() {
81        return Err(VoteError::SlotsMismatch);
82    }
83    let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
84
85    // Check if the proposed vote state is too old to be in the SlotHash history
86    if last_proposed_slot < earliest_slot_hash_in_history {
87        // If this is the last slot in the vote update, it must be in SlotHashes,
88        // otherwise we have no way of confirming if the hash matches
89        return Err(VoteError::VoteTooOld);
90    }
91
92    // Overwrite the proposed root if it is too old to be in the SlotHash history
93    if let Some(root) = *proposed_root {
94        // If the new proposed root `R` is less than the earliest slot hash in the history
95        // such that we cannot verify whether the slot was actually was on this fork, set
96        // the root to the latest vote in the vote state that's less than R. If no
97        // votes from the vote state are less than R, use its root instead.
98        if root < earliest_slot_hash_in_history {
99            // First overwrite the proposed root with the vote state's root
100            *proposed_root = vote_state.root_slot();
101
102            // Then try to find the latest vote in vote state that's less than R
103            for vote in vote_state.votes().iter().rev() {
104                if vote.slot() <= root {
105                    *proposed_root = Some(vote.slot());
106                    break;
107                }
108            }
109        }
110    }
111
112    // Index into the new proposed vote state's slots, starting with the root if it exists then
113    // we use this mutable root to fold checking the root slot into the below loop
114    // for performance
115    let mut root_to_check = *proposed_root;
116    let mut proposed_lockouts_index = 0;
117
118    // index into the slot_hashes, starting at the oldest known
119    // slot hash
120    let mut slot_hashes_index = slot_hashes.len();
121
122    let mut proposed_lockouts_indices_to_filter = vec![];
123
124    // Note:
125    //
126    // 1) `proposed_lockouts` is sorted from oldest/smallest vote to newest/largest
127    // vote, due to the way votes are applied to the vote state (newest votes
128    // pushed to the back).
129    //
130    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
131    // the oldest/smallest vote
132    //
133    // We check every proposed lockout because have to ensure that every slot is actually part of
134    // the history, not just the most recent ones
135    while proposed_lockouts_index < proposed_lockouts.len() && slot_hashes_index > 0 {
136        let proposed_vote_slot = if let Some(root) = root_to_check {
137            root
138        } else {
139            proposed_lockouts[proposed_lockouts_index].slot()
140        };
141        if root_to_check.is_none()
142            && proposed_lockouts_index > 0
143            && proposed_vote_slot
144                <= proposed_lockouts[proposed_lockouts_index.checked_sub(1).expect(
145                    "`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`",
146                )]
147                .slot()
148        {
149            return Err(VoteError::SlotsNotOrdered);
150        }
151        let ancestor_slot = slot_hashes[slot_hashes_index
152            .checked_sub(1)
153            .expect("`slot_hashes_index` is positive when computing `ancestor_slot`")]
154        .0;
155
156        // Find if this slot in the proposed vote state exists in the SlotHashes history
157        // to confirm if it was a valid ancestor on this fork
158        match proposed_vote_slot.cmp(&ancestor_slot) {
159            Ordering::Less => {
160                if slot_hashes_index == slot_hashes.len() {
161                    // The vote slot does not exist in the SlotHashes history because it's too old,
162                    // i.e. older than the oldest slot in the history.
163                    if proposed_vote_slot >= earliest_slot_hash_in_history {
164                        return Err(VoteError::AssertionFailed);
165                    }
166                    if !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
167                        // If the vote slot is both:
168                        // 1) Too old
169                        // 2) Doesn't already exist in vote state
170                        //
171                        // Then filter it out
172                        proposed_lockouts_indices_to_filter.push(proposed_lockouts_index);
173                    }
174                    if let Some(new_proposed_root) = root_to_check {
175                        // 1. Because `root_to_check.is_some()`, then we know that
176                        // we haven't checked the root yet in this loop, so
177                        // `proposed_vote_slot` == `new_proposed_root` == `proposed_root`.
178                        assert_eq!(new_proposed_root, proposed_vote_slot);
179                        // 2. We know from the assert earlier in the function that
180                        // `proposed_vote_slot < earliest_slot_hash_in_history`,
181                        // so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history`.
182                        if new_proposed_root >= earliest_slot_hash_in_history {
183                            return Err(VoteError::AssertionFailed);
184                        }
185                        root_to_check = None;
186                    } else {
187                        proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
188                            "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
189                             `proposed_vote_slot` is too old to be in SlotHashes history",
190                        );
191                    }
192                    continue;
193                } else {
194                    // If the vote slot is new enough to be in the slot history,
195                    // but is not part of the slot history, then it must belong to another fork,
196                    // which means this proposed vote state is invalid.
197                    if root_to_check.is_some() {
198                        return Err(VoteError::RootOnDifferentFork);
199                    } else {
200                        return Err(VoteError::SlotsMismatch);
201                    }
202                }
203            }
204            Ordering::Greater => {
205                // Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
206                slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
207                    "`slot_hashes_index` is positive when finding newer slots in SlotHashes \
208                     history",
209                );
210                continue;
211            }
212            Ordering::Equal => {
213                // Once the slot in `proposed_lockouts` is found, bump to the next slot
214                // in `proposed_lockouts` and continue. If we were checking the root,
215                // start checking the vote state instead.
216                if root_to_check.is_some() {
217                    root_to_check = None;
218                } else {
219                    proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
220                        "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when match \
221                         is found in SlotHashes history",
222                    );
223                    slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
224                        "`slot_hashes_index` is positive when match is found in SlotHashes history",
225                    );
226                }
227            }
228        }
229    }
230
231    if proposed_lockouts_index != proposed_lockouts.len() {
232        // The last vote slot in the proposed vote state did not exist in SlotHashes
233        return Err(VoteError::SlotsMismatch);
234    }
235
236    // This assertion must be true at this point because we can assume by now:
237    // 1) proposed_lockouts_index == proposed_lockouts.len()
238    // 2) last_proposed_slot >= earliest_slot_hash_in_history
239    // 3) !proposed_lockouts.is_empty()
240    //
241    // 1) implies that during the last iteration of the loop above,
242    // `proposed_lockouts_index` was equal to `proposed_lockouts.len() - 1`,
243    // and was then incremented to `proposed_lockouts.len()`.
244    // This means in that last loop iteration,
245    // `proposed_vote_slot ==
246    //  proposed_lockouts[proposed_lockouts.len() - 1] ==
247    //  last_proposed_slot`.
248    //
249    // Then we know the last comparison `match proposed_vote_slot.cmp(&ancestor_slot)`
250    // is equivalent to `match last_proposed_slot.cmp(&ancestor_slot)`. The result
251    // of this match to increment `proposed_lockouts_index` must have been either:
252    //
253    // 1) The Equal case ran, in which case then we know this assertion must be true
254    // 2) The Less case ran, and more specifically the case
255    // `proposed_vote_slot < earliest_slot_hash_in_history` ran, which is equivalent to
256    // `last_proposed_slot < earliest_slot_hash_in_history`, but this is impossible
257    // due to assumption 3) above.
258    assert_eq!(last_proposed_slot, slot_hashes[slot_hashes_index].0);
259
260    if slot_hashes[slot_hashes_index].1 != proposed_hash {
261        // This means the newest vote in the slot has a match that
262        // doesn't match the expected hash for that slot on this
263        // fork
264        warn!(
265            "{} dropped vote {:?} root {:?} failed to match hash {} {}",
266            vote_state.node_pubkey(),
267            proposed_lockouts,
268            proposed_root,
269            proposed_hash,
270            slot_hashes[slot_hashes_index].1
271        );
272        return Err(VoteError::SlotHashMismatch);
273    }
274
275    // Filter out the irrelevant votes
276    let mut proposed_lockouts_index = 0;
277    let mut filter_votes_index = 0;
278    proposed_lockouts.retain(|_lockout| {
279        let should_retain = if filter_votes_index == proposed_lockouts_indices_to_filter.len() {
280            true
281        } else if proposed_lockouts_index == proposed_lockouts_indices_to_filter[filter_votes_index]
282        {
283            filter_votes_index = filter_votes_index.checked_add(1).unwrap();
284            false
285        } else {
286            true
287        };
288
289        proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
290            "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when filtering out \
291             irrelevant votes",
292        );
293        should_retain
294    });
295
296    Ok(())
297}
298
299fn check_slots_are_valid(
300    vote_state: &VoteStateHandler,
301    vote_slots: &[Slot],
302    vote_hash: &Hash,
303    slot_hashes: &[(Slot, Hash)],
304) -> Result<(), VoteError> {
305    // index into the vote's slots, starting at the oldest
306    // slot
307    let mut i = 0;
308
309    // index into the slot_hashes, starting at the oldest known
310    // slot hash
311    let mut j = slot_hashes.len();
312
313    // Note:
314    //
315    // 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
316    // vote, due to the way votes are applied to the vote state (newest votes
317    // pushed to the back).
318    //
319    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
320    // the oldest/smallest vote
321    while i < vote_slots.len() && j > 0 {
322        // 1) increment `i` to find the smallest slot `s` in `vote_slots`
323        // where `s` >= `last_voted_slot`
324        if vote_state
325            .last_voted_slot()
326            .is_some_and(|last_voted_slot| vote_slots[i] <= last_voted_slot)
327        {
328            i = i
329                .checked_add(1)
330                .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots");
331            continue;
332        }
333
334        // 2) Find the hash for this slot `s`.
335        if vote_slots[i] != slot_hashes[j.checked_sub(1).expect("`j` is positive")].0 {
336            // Decrement `j` to find newer slots
337            j = j
338                .checked_sub(1)
339                .expect("`j` is positive when finding newer slots");
340            continue;
341        }
342
343        // 3) Once the hash for `s` is found, bump `s` to the next slot
344        // in `vote_slots` and continue.
345        i = i
346            .checked_add(1)
347            .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found");
348        j = j
349            .checked_sub(1)
350            .expect("`j` is positive when hash is found");
351    }
352
353    if j == slot_hashes.len() {
354        // This means we never made it to steps 2) or 3) above, otherwise
355        // `j` would have been decremented at least once. This means
356        // there are not slots in `vote_slots` greater than `last_voted_slot`
357        debug!(
358            "{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
359            vote_state.node_pubkey(),
360            vote_slots,
361            vote_hash,
362            slot_hashes
363        );
364        return Err(VoteError::VoteTooOld);
365    }
366    if i != vote_slots.len() {
367        // This means there existed some slot for which we couldn't find
368        // a matching slot hash in step 2)
369        info!(
370            "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
371            vote_state.node_pubkey(),
372            vote_slots,
373            slot_hashes,
374        );
375        return Err(VoteError::SlotsMismatch);
376    }
377    if &slot_hashes[j].1 != vote_hash {
378        // This means the newest slot in the `vote_slots` has a match that
379        // doesn't match the expected hash for that slot on this
380        // fork
381        warn!(
382            "{} dropped vote slots {:?} failed to match hash {} {}",
383            vote_state.node_pubkey(),
384            vote_slots,
385            vote_hash,
386            slot_hashes[j].1
387        );
388        return Err(VoteError::SlotHashMismatch);
389    }
390    Ok(())
391}
392
393// Ensure `check_and_filter_proposed_vote_state(&)` runs on the slots in `new_state`
394// before `process_new_vote_state()` is called
395
396// This function should guarantee the following about `new_state`:
397//
398// 1) It's well ordered, i.e. the slots are sorted from smallest to largest,
399// and the confirmations sorted from largest to smallest.
400// 2) Confirmations `c` on any vote slot satisfy `0 < c <= MAX_LOCKOUT_HISTORY`
401// 3) Lockouts are not expired by consecutive votes, i.e. for every consecutive
402// `v_i`, `v_{i + 1}` satisfy `v_i.last_locked_out_slot() >= v_{i + 1}`.
403
404// We also guarantee that compared to the current vote state, `new_state`
405// introduces no rollback. This means:
406//
407// 1) The last slot in `new_state` is always greater than any slot in the
408// current vote state.
409//
410// 2) From 1), this means that for every vote `s` in the current state:
411//    a) If there exists an `s'` in `new_state` where `s.slot == s'.slot`, then
412//    we must guarantee `s.confirmations <= s'.confirmations`
413//
414//    b) If there does not exist any such `s'` in `new_state`, then there exists
415//    some `t` that is the smallest vote in `new_state` where `t.slot > s.slot`.
416//    `t` must have expired/popped off s', so it must be guaranteed that
417//    `s.last_locked_out_slot() < t`.
418
419// Note these two above checks do not guarantee that the vote state being submitted
420// is a vote state that could have been created by iteratively building a tower
421// by processing one vote at a time. For instance, the tower:
422//
423// { slot 0, confirmations: 31 }
424// { slot 1, confirmations: 30 }
425//
426// is a legal tower that could be submitted on top of a previously empty tower. However,
427// there is no way to create this tower from the iterative process, because slot 1 would
428// have to have at least one other slot on top of it, even if the first 30 votes were all
429// popped off.
430pub fn process_new_vote_state(
431    vote_state: &mut VoteStateHandler,
432    mut new_state: VecDeque<LandedVote>,
433    new_root: Option<Slot>,
434    timestamp: Option<i64>,
435    epoch: Epoch,
436    current_slot: Slot,
437) -> Result<(), VoteError> {
438    assert!(!new_state.is_empty());
439    if new_state.len() > MAX_LOCKOUT_HISTORY {
440        return Err(VoteError::TooManyVotes);
441    }
442
443    match (new_root, vote_state.root_slot()) {
444        (Some(new_root), Some(current_root)) if new_root < current_root => {
445            return Err(VoteError::RootRollBack);
446        }
447        (None, Some(_)) => {
448            return Err(VoteError::RootRollBack);
449        }
450        _ => (),
451    }
452
453    let mut previous_vote: Option<&LandedVote> = None;
454
455    // Check that all the votes in the new proposed state are:
456    // 1) Strictly sorted from oldest to newest vote
457    // 2) The confirmations are strictly decreasing
458    // 3) Not zero confirmation votes
459    for vote in &new_state {
460        if vote.confirmation_count() == 0 {
461            return Err(VoteError::ZeroConfirmations);
462        } else if vote.confirmation_count() > MAX_LOCKOUT_HISTORY as u32 {
463            return Err(VoteError::ConfirmationTooLarge);
464        } else if let Some(new_root) = new_root
465            && vote.slot() <= new_root
466                &&
467                // This check is necessary because
468                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L120
469                // always sets a root for even empty towers, which is then hard unwrapped here
470                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L776
471                new_root != Slot::default()
472        {
473            return Err(VoteError::SlotSmallerThanRoot);
474        }
475
476        if let Some(previous_vote) = previous_vote {
477            if previous_vote.slot() >= vote.slot() {
478                return Err(VoteError::SlotsNotOrdered);
479            } else if previous_vote.confirmation_count() <= vote.confirmation_count() {
480                return Err(VoteError::ConfirmationsNotOrdered);
481            } else if vote.slot() > previous_vote.lockout.last_locked_out_slot() {
482                return Err(VoteError::NewVoteStateLockoutMismatch);
483            }
484        }
485        previous_vote = Some(vote);
486    }
487
488    // Find the first vote in the current vote state for a slot greater
489    // than the new proposed root
490    let mut current_vote_state_index: usize = 0;
491    let mut new_vote_state_index = 0;
492
493    // Accumulate credits earned by newly rooted slots
494    let mut earned_credits = 0_u64;
495
496    if let Some(new_root) = new_root {
497        for current_vote in vote_state.votes() {
498            // Find the first vote in the current vote state for a slot greater
499            // than the new proposed root
500            if current_vote.slot() <= new_root {
501                earned_credits = earned_credits
502                    .checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index))
503                    .expect("`earned_credits` does not overflow");
504                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
505                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
506                     processing new root",
507                );
508                continue;
509            }
510
511            break;
512        }
513    }
514
515    // For any slots newly added to the new vote state, the vote latency of that slot is not provided by the
516    // vote instruction contents, but instead is computed from the actual latency of the vote
517    // instruction. This prevents other validators from manipulating their own vote latencies within their vote states
518    // and forcing the rest of the cluster to accept these possibly fraudulent latency values.  If the
519    // timly_vote_credits feature is not enabled then vote latency is set to 0 for new votes.
520    //
521    // For any slot that is in both the new state and the current state, the vote latency of the new state is taken
522    // from the current state.
523    //
524    // Thus vote latencies are set here for any newly vote-on slots when a vote instruction is received.
525    // They are copied into the new vote state after every vote for already voted-on slots.
526    // And when voted-on slots are rooted, the vote latencies stored in the vote state of all the rooted slots is used
527    // to compute credits earned.
528    // All validators compute the same vote latencies because all process the same vote instruction at the
529    // same slot, and the only time vote latencies are ever computed is at the time that their slot is first voted on;
530    // after that, the latencies are retained unaltered until the slot is rooted.
531
532    // All the votes in our current vote state that are missing from the new vote state
533    // must have been expired by later votes. Check that the lockouts match this assumption.
534    while current_vote_state_index < vote_state.votes().len()
535        && new_vote_state_index < new_state.len()
536    {
537        let current_vote = &vote_state.votes()[current_vote_state_index];
538        let new_vote = &mut new_state[new_vote_state_index];
539
540        // If the current slot is less than the new proposed slot, then the
541        // new slot must have popped off the old slot, so check that the
542        // lockouts are corrects.
543        match current_vote.slot().cmp(&new_vote.slot()) {
544            Ordering::Less => {
545                if current_vote.lockout.last_locked_out_slot() >= new_vote.slot() {
546                    return Err(VoteError::LockoutConflict);
547                }
548                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
549                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
550                     less than proposed",
551                );
552            }
553            Ordering::Equal => {
554                // The new vote state should never have less lockout than
555                // the previous vote state for the same slot
556                if new_vote.confirmation_count() < current_vote.confirmation_count() {
557                    return Err(VoteError::ConfirmationRollBack);
558                }
559
560                // Copy the vote slot latency in from the current state to the new state
561                new_vote.latency = vote_state.votes()[current_vote_state_index].latency;
562
563                current_vote_state_index = current_vote_state_index.checked_add(1).expect(
564                    "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
565                     equal to proposed",
566                );
567                new_vote_state_index = new_vote_state_index.checked_add(1).expect(
568                    "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
569                     equal to proposed",
570                );
571            }
572            Ordering::Greater => {
573                new_vote_state_index = new_vote_state_index.checked_add(1).expect(
574                    "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
575                     greater than proposed",
576                );
577            }
578        }
579    }
580
581    // `new_vote_state` passed all the checks, finalize the change by rewriting
582    // our state.
583
584    // Now set the vote latencies on new slots not in the current state.  New slots not in the current vote state will
585    // have had their latency initialized to 0 by the above loop.  Those will now be updated to their actual latency.
586    for new_vote in new_state.iter_mut() {
587        if new_vote.latency == 0 {
588            new_vote.latency = handler::compute_vote_latency(new_vote.slot(), current_slot);
589        }
590    }
591
592    if vote_state.root_slot() != new_root {
593        // Award vote credits based on the number of slots that were voted on and have reached finality
594        // For each finalized slot, there was one voted-on slot in the new vote state that was responsible for
595        // finalizing it. Each of those votes is awarded 1 credit.
596        vote_state.increment_credits(epoch, earned_credits);
597    }
598    if let Some(timestamp) = timestamp {
599        let last_slot = new_state.back().unwrap().slot();
600        vote_state.process_timestamp(last_slot, timestamp)?;
601    }
602    vote_state.set_root_slot(new_root);
603    vote_state.set_votes(new_state);
604
605    Ok(())
606}
607
608pub fn process_vote_unfiltered(
609    vote_state: &mut VoteStateHandler,
610    vote_slots: &[Slot],
611    vote: &Vote,
612    slot_hashes: &[SlotHash],
613    epoch: Epoch,
614    current_slot: Slot,
615) -> Result<(), VoteError> {
616    check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?;
617    vote_slots
618        .iter()
619        .for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot));
620    Ok(())
621}
622
623pub fn process_vote(
624    vote_state: &mut VoteStateHandler,
625    vote: &Vote,
626    slot_hashes: &[SlotHash],
627    epoch: Epoch,
628    current_slot: Slot,
629) -> Result<(), VoteError> {
630    if vote.slots.is_empty() {
631        return Err(VoteError::EmptySlots);
632    }
633    let earliest_slot_in_history = slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
634    let vote_slots = vote
635        .slots
636        .iter()
637        .filter(|slot| **slot >= earliest_slot_in_history)
638        .cloned()
639        .collect::<Vec<Slot>>();
640    if vote_slots.is_empty() {
641        return Err(VoteError::VotesTooOldAllFiltered);
642    }
643    process_vote_unfiltered(
644        vote_state,
645        &vote_slots,
646        vote,
647        slot_hashes,
648        epoch,
649        current_slot,
650    )
651}
652
653/// "unchecked" functions used by tests and Tower
654pub fn process_vote_unchecked(
655    vote_state: &mut VoteStateHandler,
656    vote: Vote,
657) -> Result<(), VoteError> {
658    if vote.slots.is_empty() {
659        return Err(VoteError::EmptySlots);
660    }
661    let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
662    process_vote_unfiltered(
663        vote_state,
664        &vote.slots,
665        &vote,
666        &slot_hashes,
667        vote_state.current_epoch(),
668        0,
669    )
670}
671
672#[cfg(test)]
673pub fn process_slot_votes_unchecked(vote_state: &mut VoteStateHandler, slots: &[Slot]) {
674    for slot in slots {
675        process_slot_vote_unchecked(vote_state, *slot);
676    }
677}
678
679pub fn process_slot_vote_unchecked(vote_state: &mut VoteStateHandler, slot: Slot) {
680    let _ = process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
681}
682
683/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
684/// but will implicitly withdraw authorization from the previously authorized
685/// key
686pub fn authorize<S: std::hash::BuildHasher, F>(
687    vote_account: &mut BorrowedInstructionAccount,
688    target_version: VoteStateTargetVersion,
689    authorized: &Pubkey,
690    vote_authorize: VoteAuthorize,
691    signers: &HashSet<Pubkey, S>,
692    clock: &Clock,
693    is_vote_authorize_with_bls_enabled: bool,
694    consume_pop_compute_units: F,
695) -> Result<(), InstructionError>
696where
697    F: FnOnce() -> Result<(), InstructionError>,
698{
699    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
700
701    match vote_authorize {
702        VoteAuthorize::Voter => {
703            if is_vote_authorize_with_bls_enabled && vote_state.has_bls_pubkey() {
704                return Err(InstructionError::InvalidInstructionData);
705            }
706            let authorized_withdrawer_signer =
707                verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
708
709            vote_state.set_new_authorized_voter(
710                authorized,
711                clock.epoch,
712                clock
713                    .leader_schedule_epoch
714                    .checked_add(1)
715                    .ok_or(InstructionError::InvalidAccountData)?,
716                None,
717                |epoch_authorized_voter| {
718                    // current authorized withdrawer or authorized voter must say "yay"
719                    if authorized_withdrawer_signer {
720                        Ok(())
721                    } else {
722                        verify_authorized_signer(&epoch_authorized_voter, signers)
723                    }
724                },
725            )?;
726        }
727        VoteAuthorize::Withdrawer => {
728            // current authorized withdrawer must say "yay"
729            verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
730            vote_state.set_authorized_withdrawer(*authorized);
731        }
732        VoteAuthorize::VoterWithBLS(args) => {
733            if !is_vote_authorize_with_bls_enabled {
734                return Err(InstructionError::InvalidInstructionData);
735            }
736            let authorized_withdrawer_signer =
737                verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
738
739            verify_bls_proof_of_possession(
740                vote_account.get_key(),
741                &args.bls_pubkey,
742                &args.bls_proof_of_possession,
743                consume_pop_compute_units,
744            )?;
745
746            vote_state.set_new_authorized_voter(
747                authorized,
748                clock.epoch,
749                clock
750                    .leader_schedule_epoch
751                    .checked_add(1)
752                    .ok_or(InstructionError::InvalidAccountData)?,
753                Some(&args.bls_pubkey),
754                |epoch_authorized_voter| {
755                    // current authorized withdrawer or authorized voter must say "yay"
756                    if authorized_withdrawer_signer {
757                        Ok(())
758                    } else {
759                        verify_authorized_signer(&epoch_authorized_voter, signers)
760                    }
761                },
762            )?;
763        }
764    }
765
766    vote_state.set_vote_account_state(vote_account)
767}
768
769/// Update the node_pubkey, requires signature of the authorized voter
770pub fn update_validator_identity<S: std::hash::BuildHasher>(
771    vote_account: &mut BorrowedInstructionAccount,
772    target_version: VoteStateTargetVersion,
773    node_pubkey: &Pubkey,
774    signers: &HashSet<Pubkey, S>,
775    custom_commission_collector_enabled: bool,
776) -> Result<(), InstructionError> {
777    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
778
779    // current authorized withdrawer must say "yay"
780    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
781
782    // new node must say "yay"
783    verify_authorized_signer(node_pubkey, signers)?;
784
785    vote_state.set_node_pubkey(*node_pubkey);
786
787    // Before SIMD-0232, block_revenue_collector is always synced with node_pubkey.
788    // After SIMD-0232, the collector can be set independently.
789    if !custom_commission_collector_enabled {
790        vote_state.set_block_revenue_collector(*node_pubkey);
791    }
792
793    vote_state.set_vote_account_state(vote_account)
794}
795
796/// Update the vote account's commission
797pub fn update_commission<S: std::hash::BuildHasher>(
798    vote_account: &mut BorrowedInstructionAccount,
799    target_version: VoteStateTargetVersion,
800    commission: u8,
801    signers: &HashSet<Pubkey, S>,
802    epoch_schedule: &EpochSchedule,
803    clock: &Clock,
804    disable_commission_update_rule: bool,
805) -> Result<(), InstructionError> {
806    let vote_state_result = get_vote_state_handler_checked(vote_account, target_version);
807    let enforce_commission_update_rule = !disable_commission_update_rule
808        && match vote_state_result.as_ref() {
809            Ok(decoded_vote_state) => commission > decoded_vote_state.commission(),
810            Err(_) => true,
811        };
812
813    if enforce_commission_update_rule && !is_commission_update_allowed(clock.slot, epoch_schedule) {
814        return Err(VoteError::CommissionUpdateTooLate.into());
815    }
816
817    let mut vote_state = vote_state_result?;
818
819    // current authorized withdrawer must say "yay"
820    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
821
822    vote_state.set_commission(commission);
823
824    vote_state.set_vote_account_state(vote_account)
825}
826
827/// Update the vote account's commission in basis points (SIMD-0291, SIMD-0123).
828pub fn update_commission_bps<S: std::hash::BuildHasher>(
829    vote_account: &mut BorrowedInstructionAccount,
830    target_version: VoteStateTargetVersion,
831    commission_bps: u16,
832    kind: CommissionKind,
833    signers: &HashSet<Pubkey, S>,
834    block_revenue_sharing_enabled: bool,
835) -> Result<(), InstructionError> {
836    // Per SIMD-0291: BlockRevenue returns InvalidInstructionData unless
837    // SIMD-0123 (block_revenue_sharing) is enabled.
838    if matches!(kind, CommissionKind::BlockRevenue) && !block_revenue_sharing_enabled {
839        return Err(InstructionError::InvalidInstructionData);
840    }
841
842    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
843
844    // No commission update rule, per SIMD-0249 and SIMD-0291.
845
846    // Require authorized withdrawer to sign.
847    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
848
849    match kind {
850        CommissionKind::InflationRewards => {
851            vote_state.set_inflation_rewards_commission_bps(commission_bps);
852        }
853        CommissionKind::BlockRevenue => {
854            vote_state.set_block_revenue_commission_bps(commission_bps);
855        }
856    }
857
858    vote_state.set_vote_account_state(vote_account)
859}
860
861pub enum NewCommissionCollector<'a, 'b> {
862    VoteAccount,
863    NewAccount(BorrowedInstructionAccount<'a, 'b>),
864}
865
866impl NewCommissionCollector<'_, '_> {
867    /// Validates the collector per SIMD-0232 and returns its pubkey.
868    ///
869    /// The designated commission collector must either be equal to the vote
870    /// account's address OR satisfy ALL of the following constraints:
871    ///
872    /// 1. Must be a system program owned account.
873    /// 2. Must be rent-exempt.
874    /// 3. Must not be a reserved account (checked via writable flag).
875    pub fn validate_and_resolve_key(
876        &self,
877        vote_account: &BorrowedInstructionAccount,
878        rent: &Rent,
879    ) -> Result<Pubkey, InstructionError> {
880        match self {
881            NewCommissionCollector::VoteAccount => Ok(*vote_account.get_key()),
882            NewCommissionCollector::NewAccount(collector_account) => {
883                // 1. Must be a system program owned account.
884                if collector_account.get_owner() != &system_program::id() {
885                    return Err(InstructionError::InvalidAccountOwner);
886                }
887
888                // 2. Must be rent-exempt.
889                if !rent.is_exempt(
890                    collector_account.get_lamports(),
891                    collector_account.get_data().len(),
892                ) {
893                    return Err(InstructionError::InsufficientFunds);
894                }
895
896                // 3. Must not be a reserved account (checked via writable flag).
897                if !collector_account.is_writable() {
898                    return Err(InstructionError::InvalidArgument);
899                }
900
901                Ok(*collector_account.get_key())
902            }
903        }
904    }
905}
906
907/// Update the vote account's commission collector (SIMD-0232).
908pub fn update_commission_collector<S: std::hash::BuildHasher>(
909    vote_account: &mut BorrowedInstructionAccount,
910    target_version: VoteStateTargetVersion,
911    new_collector: NewCommissionCollector,
912    kind: CommissionKind,
913    signers: &HashSet<Pubkey, S>,
914    rent: &Rent,
915) -> Result<(), InstructionError> {
916    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
917
918    // Require authorized withdrawer to sign.
919    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
920
921    let new_collector_key = new_collector.validate_and_resolve_key(vote_account, rent)?;
922
923    match kind {
924        CommissionKind::InflationRewards => {
925            vote_state.set_inflation_rewards_collector(new_collector_key);
926        }
927        CommissionKind::BlockRevenue => {
928            vote_state.set_block_revenue_collector(new_collector_key);
929        }
930    }
931
932    vote_state.set_vote_account_state(vote_account)
933}
934
935/// Deposit delegator rewards into a vote account (SIMD-0123).
936pub fn deposit_delegator_rewards<S: std::hash::BuildHasher>(
937    invoke_context: &mut InvokeContext,
938    vote_account_index: IndexOfAccount,
939    sender_account_index: IndexOfAccount,
940    deposit: u64,
941    signers: &HashSet<Pubkey, S>,
942) -> Result<(), InstructionError> {
943    let transaction_context = &invoke_context.transaction_context;
944    let instruction_context = transaction_context.get_current_instruction_context()?;
945
946    let vote_address = *instruction_context.get_key_of_instruction_account(vote_account_index)?;
947    let source_address =
948        *instruction_context.get_key_of_instruction_account(sender_account_index)?;
949
950    // Source account must sign the transfer.
951    verify_authorized_signer(&source_address, signers)?;
952
953    // SIMD-0123 states we must validate the vote account deserializes to a v4
954    // *before* attempting CPI, then update the `pending_delegator_rewards`
955    // field *last*.
956    // We can deserialize it, and hold onto the deserialized payload in-memory.
957    // This way, we can drop the account borrow but avoid re-deserializing
958    // later, since we know only lamports will change.
959    let mut vote_state = {
960        let vote_account =
961            instruction_context.try_borrow_instruction_account(vote_account_index)?;
962
963        // Can't use `get_vote_state_handler_checked`, since it will convert
964        // the underlying vote state to v4.
965        // SIMD-0123 requires an *initialized v4*.
966        let versioned = VoteStateVersions::deserialize(vote_account.get_data())?;
967        if let VoteStateVersions::V4(vote_state_v4) = versioned {
968            Ok(VoteStateHandler::new_v4(*vote_state_v4))
969        } else {
970            Err(InstructionError::InvalidAccountData)
971        }
972    }?;
973
974    // CPI to System: Transfer from sender to vote account.
975    invoke_context.native_invoke_signed(
976        system_instruction::transfer(&source_address, &vote_address, deposit),
977        &[],
978    )?;
979
980    // Update `pending_delegator_rewards`.
981    let transaction_context = &invoke_context.transaction_context;
982    let instruction_context = transaction_context.get_current_instruction_context()?;
983    let mut vote_account =
984        instruction_context.try_borrow_instruction_account(vote_account_index)?;
985
986    vote_state.add_pending_delegator_rewards(deposit)?;
987    vote_state.set_vote_account_state(&mut vote_account)
988}
989
990/// Given the current slot and epoch schedule, determine if a commission change
991/// is allowed
992pub fn is_commission_update_allowed(slot: Slot, epoch_schedule: &EpochSchedule) -> bool {
993    // always allowed during warmup epochs
994    if let Some(relative_slot) = slot
995        .saturating_sub(epoch_schedule.first_normal_slot)
996        .checked_rem(epoch_schedule.slots_per_epoch)
997    {
998        // allowed up to the midpoint of the epoch
999        relative_slot.saturating_mul(2) <= epoch_schedule.slots_per_epoch
1000    } else {
1001        // no slots per epoch, just allow it, even though this should never happen
1002        true
1003    }
1004}
1005
1006fn verify_authorized_signer<S: std::hash::BuildHasher>(
1007    authorized: &Pubkey,
1008    signers: &HashSet<Pubkey, S>,
1009) -> Result<(), InstructionError> {
1010    if signers.contains(authorized) {
1011        Ok(())
1012    } else {
1013        Err(InstructionError::MissingRequiredSignature)
1014    }
1015}
1016
1017// The message size is fixed:
1018// "ALPENGLOW" (9) + Vote Pubkey (32) = 41 bytes
1019// Note: The BLS Pubkey (48 bytes) is appended dynamically by the
1020// solana-bls-signatures crate.
1021const POP_MESSAGE_SIZE: usize = 9 + size_of::<Pubkey>();
1022
1023pub(crate) fn generate_pop_message(vote_account_pubkey: &Pubkey) -> [u8; POP_MESSAGE_SIZE] {
1024    const LABEL_LEN: usize = 9;
1025    const PUBKEY_LEN: usize = size_of::<Pubkey>();
1026
1027    const LABEL_START: usize = 0;
1028    const LABEL_END: usize = LABEL_START + LABEL_LEN;
1029
1030    const PUBKEY_START: usize = LABEL_END;
1031    const PUBKEY_END: usize = PUBKEY_START + PUBKEY_LEN;
1032
1033    // Make sure POP_MESSAGE_SIZE matches the layout at compile time
1034    const _: () = assert!(PUBKEY_END == POP_MESSAGE_SIZE);
1035
1036    let mut message = [0u8; POP_MESSAGE_SIZE];
1037
1038    message[LABEL_START..LABEL_END].copy_from_slice(b"ALPENGLOW");
1039    message[PUBKEY_START..PUBKEY_END].copy_from_slice(vote_account_pubkey.as_ref());
1040
1041    message
1042}
1043
1044pub fn verify_bls_proof_of_possession<F>(
1045    vote_account_pubkey: &Pubkey,
1046    bls_pubkey_compressed_bytes: &[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1047    bls_proof_of_possession_compressed_bytes: &[u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1048    consume_pop_compute_units: F,
1049) -> Result<(), InstructionError>
1050where
1051    F: FnOnce() -> Result<(), InstructionError>,
1052{
1053    // Consume CUs for BLS verification (SIMD-0387).
1054    consume_pop_compute_units()?;
1055
1056    let message = generate_pop_message(vote_account_pubkey);
1057    bls_proof_of_possession_compressed_bytes
1058        .verify(bls_pubkey_compressed_bytes, Some(&message))
1059        .map_err(|_| InstructionError::InvalidArgument)
1060}
1061
1062/// Withdraw funds from the vote account
1063pub fn withdraw<S: std::hash::BuildHasher>(
1064    instruction_context: &InstructionContext,
1065    vote_account_index: IndexOfAccount,
1066    target_version: VoteStateTargetVersion,
1067    lamports: u64,
1068    to_account_index: IndexOfAccount,
1069    signers: &HashSet<Pubkey, S>,
1070    rent_sysvar: &Rent,
1071    clock: &Clock,
1072) -> Result<(), InstructionError> {
1073    let mut vote_account =
1074        instruction_context.try_borrow_instruction_account(vote_account_index)?;
1075    let vote_state = get_vote_state_handler_checked(&vote_account, target_version)?;
1076
1077    verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
1078
1079    let remaining_balance = vote_account
1080        .get_lamports()
1081        .checked_sub(lamports)
1082        .ok_or(InstructionError::InsufficientFunds)?;
1083
1084    // Always zero until SIMD-0123 is activated.
1085    let pending_delegator_rewards = vote_state.pending_delegator_rewards();
1086
1087    if remaining_balance == 0 {
1088        // SIMD-0123: vote account cannot be closed if
1089        // pending_delegator_rewards > 0.
1090        if pending_delegator_rewards > 0 {
1091            return Err(InstructionError::InsufficientFunds);
1092        }
1093
1094        let reject_active_vote_account_close = vote_state
1095            .epoch_credits()
1096            .last()
1097            .map(|(last_epoch_with_credits, _, _)| {
1098                let current_epoch = clock.epoch;
1099                // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits
1100                // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least
1101                // one full epoch since the validator has received credits.
1102                current_epoch.saturating_sub(*last_epoch_with_credits) < 2
1103            })
1104            .unwrap_or(false);
1105
1106        if reject_active_vote_account_close {
1107            return Err(VoteError::ActiveVoteAccountClose.into());
1108        } else {
1109            // Deinitialize upon zero-balance
1110            VoteStateHandler::deinitialize_vote_account_state(&mut vote_account, target_version)?;
1111        }
1112    } else {
1113        // SIMD-0123: withdrawable balance when pending_delegator_rewards > 0
1114        // is lamports - pending_delegator_rewards - rent_exempt_minimum.
1115        let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
1116        let min_balance = min_rent_exempt_balance
1117            .checked_add(pending_delegator_rewards)
1118            .ok_or(InstructionError::ArithmeticOverflow)?;
1119        if remaining_balance < min_balance {
1120            return Err(InstructionError::InsufficientFunds);
1121        }
1122    }
1123
1124    vote_account.checked_sub_lamports(lamports)?;
1125    drop(vote_account);
1126    let mut to_account = instruction_context.try_borrow_instruction_account(to_account_index)?;
1127    to_account.checked_add_lamports(lamports)?;
1128    Ok(())
1129}
1130
1131/// Initialize the vote_state for a vote account using VoteInitV2
1132/// Assumes that the account is being init as part of a account creation or
1133/// balance transfer and that the transaction must be signed by the staker's
1134/// keys.
1135///
1136/// Also validates the inflation-rewards and block-revenue collector accounts
1137/// per SIMD-0464 (which delegates to the SIMD-0232 collector checks) and
1138/// verifies the BLS proof of possession for the authorized voter BLS pubkey.
1139pub fn initialize_account_v2<S: std::hash::BuildHasher, F>(
1140    vote_account: &mut BorrowedInstructionAccount,
1141    target_version: VoteStateTargetVersion,
1142    vote_init: &VoteInitV2,
1143    inflation_rewards_collector: NewCommissionCollector,
1144    block_revenue_collector: NewCommissionCollector,
1145    signers: &HashSet<Pubkey, S>,
1146    clock: &Clock,
1147    rent: &Rent,
1148    consume_pop_compute_units: F,
1149) -> Result<(), InstructionError>
1150where
1151    F: FnOnce() -> Result<(), InstructionError>,
1152{
1153    VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
1154    let versioned = vote_account.get_state::<VoteStateVersions>()?;
1155
1156    if !versioned.is_uninitialized() {
1157        return Err(InstructionError::AccountAlreadyInitialized);
1158    }
1159
1160    // node must agree to accept this vote account
1161    verify_authorized_signer(&vote_init.node_pubkey, signers)?;
1162
1163    // Per SIMD-0464, validate the collector accounts using the same checks as
1164    // `UpdateCommissionCollector` (SIMD-0232).
1165    let inflation_rewards_collector_key =
1166        inflation_rewards_collector.validate_and_resolve_key(vote_account, rent)?;
1167    let block_revenue_collector_key =
1168        block_revenue_collector.validate_and_resolve_key(vote_account, rent)?;
1169
1170    // verify the BLS pubkey proof of possession
1171    verify_bls_proof_of_possession(
1172        vote_account.get_key(),
1173        &vote_init.authorized_voter_bls_pubkey,
1174        &vote_init.authorized_voter_bls_proof_of_possession,
1175        consume_pop_compute_units,
1176    )?;
1177
1178    VoteStateHandler::init_vote_account_state_v2(
1179        vote_account,
1180        vote_init,
1181        &inflation_rewards_collector_key,
1182        &block_revenue_collector_key,
1183        clock,
1184        target_version,
1185    )
1186}
1187
1188/// Initialize the vote_state for a vote account
1189/// Assumes that the account is being init as part of a account creation or balance transfer and
1190/// that the transaction must be signed by the staker's keys
1191pub fn initialize_account<S: std::hash::BuildHasher>(
1192    vote_account: &mut BorrowedInstructionAccount,
1193    target_version: VoteStateTargetVersion,
1194    vote_init: &VoteInit,
1195    signers: &HashSet<Pubkey, S>,
1196    clock: &Clock,
1197) -> Result<(), InstructionError> {
1198    VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
1199    let versioned = vote_account.get_state::<VoteStateVersions>()?;
1200
1201    if !versioned.is_uninitialized() {
1202        return Err(InstructionError::AccountAlreadyInitialized);
1203    }
1204
1205    // node must agree to accept this vote account
1206    verify_authorized_signer(&vote_init.node_pubkey, signers)?;
1207
1208    VoteStateHandler::init_vote_account_state(vote_account, vote_init, clock, target_version)
1209}
1210
1211pub fn process_vote_with_account<S: std::hash::BuildHasher>(
1212    vote_account: &mut BorrowedInstructionAccount,
1213    target_version: VoteStateTargetVersion,
1214    slot_hashes: &[SlotHash],
1215    clock: &Clock,
1216    vote: &Vote,
1217    signers: &HashSet<Pubkey, S>,
1218) -> Result<(), InstructionError> {
1219    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1220
1221    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1222    verify_authorized_signer(&authorized_voter, signers)?;
1223
1224    process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?;
1225    if let Some(timestamp) = vote.timestamp {
1226        vote.slots
1227            .iter()
1228            .max()
1229            .ok_or(VoteError::EmptySlots)
1230            .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
1231    }
1232    vote_state.set_vote_account_state(vote_account)
1233}
1234
1235pub fn process_vote_state_update<S: std::hash::BuildHasher>(
1236    vote_account: &mut BorrowedInstructionAccount,
1237    target_version: VoteStateTargetVersion,
1238    slot_hashes: &[SlotHash],
1239    clock: &Clock,
1240    vote_state_update: VoteStateUpdate,
1241    signers: &HashSet<Pubkey, S>,
1242) -> Result<(), InstructionError> {
1243    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1244
1245    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1246    verify_authorized_signer(&authorized_voter, signers)?;
1247
1248    do_process_vote_state_update(
1249        &mut vote_state,
1250        slot_hashes,
1251        clock.epoch,
1252        clock.slot,
1253        vote_state_update,
1254    )?;
1255    vote_state.set_vote_account_state(vote_account)
1256}
1257
1258pub fn do_process_vote_state_update(
1259    vote_state: &mut VoteStateHandler,
1260    slot_hashes: &[SlotHash],
1261    epoch: u64,
1262    slot: u64,
1263    mut vote_state_update: VoteStateUpdate,
1264) -> Result<(), VoteError> {
1265    check_and_filter_proposed_vote_state(
1266        vote_state,
1267        &mut vote_state_update.lockouts,
1268        &mut vote_state_update.root,
1269        vote_state_update.hash,
1270        slot_hashes,
1271    )?;
1272    process_new_vote_state(
1273        vote_state,
1274        vote_state_update
1275            .lockouts
1276            .iter()
1277            .map(|lockout| LandedVote::from(*lockout))
1278            .collect(),
1279        vote_state_update.root,
1280        vote_state_update.timestamp,
1281        epoch,
1282        slot,
1283    )
1284}
1285
1286pub fn process_tower_sync<S: std::hash::BuildHasher>(
1287    vote_account: &mut BorrowedInstructionAccount,
1288    target_version: VoteStateTargetVersion,
1289    slot_hashes: &[SlotHash],
1290    clock: &Clock,
1291    tower_sync: TowerSync,
1292    signers: &HashSet<Pubkey, S>,
1293) -> Result<(), InstructionError> {
1294    let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1295
1296    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1297    verify_authorized_signer(&authorized_voter, signers)?;
1298
1299    do_process_tower_sync(
1300        &mut vote_state,
1301        slot_hashes,
1302        clock.epoch,
1303        clock.slot,
1304        tower_sync,
1305    )?;
1306    vote_state.set_vote_account_state(vote_account)
1307}
1308
1309fn do_process_tower_sync(
1310    vote_state: &mut VoteStateHandler,
1311    slot_hashes: &[SlotHash],
1312    epoch: u64,
1313    slot: u64,
1314    mut tower_sync: TowerSync,
1315) -> Result<(), VoteError> {
1316    check_and_filter_proposed_vote_state(
1317        vote_state,
1318        &mut tower_sync.lockouts,
1319        &mut tower_sync.root,
1320        tower_sync.hash,
1321        slot_hashes,
1322    )?;
1323    process_new_vote_state(
1324        vote_state,
1325        tower_sync
1326            .lockouts
1327            .iter()
1328            .map(|lockout| LandedVote::from(*lockout))
1329            .collect(),
1330        tower_sync.root,
1331        tower_sync.timestamp,
1332        epoch,
1333        slot,
1334    )
1335}
1336
1337pub fn create_v4_account_with_authorized(
1338    node_pubkey: &Pubkey,
1339    authorized_voter: &Pubkey,
1340    authorized_voter_bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1341    authorized_withdrawer: &Pubkey,
1342    inflation_rewards_commission_bps: u16,
1343    inflation_rewards_collector: &Pubkey,
1344    block_revenue_commission_bps: u16,
1345    block_revenue_collector: &Pubkey,
1346    lamports: u64,
1347) -> AccountSharedData {
1348    let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id());
1349
1350    // PoP is stubbed here, since creation of an account assumes the account
1351    // was already initialized via `IntializeAccount` or `InitializeAccountV2`.
1352    let authorized_voter_bls_proof_of_possession = [0; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE];
1353
1354    let vote_state = VoteStateV4::new(
1355        &VoteInitV2 {
1356            node_pubkey: *node_pubkey,
1357            authorized_voter: *authorized_voter,
1358            authorized_voter_bls_pubkey,
1359            authorized_voter_bls_proof_of_possession,
1360            authorized_withdrawer: *authorized_withdrawer,
1361            inflation_rewards_commission_bps,
1362            block_revenue_commission_bps,
1363        },
1364        inflation_rewards_collector,
1365        block_revenue_collector,
1366        &Clock::default(),
1367    );
1368
1369    VoteStateV4::serialize(
1370        &VoteStateVersions::V4(Box::new(vote_state)),
1371        vote_account.data_as_mut_slice(),
1372    )
1373    .unwrap();
1374
1375    vote_account
1376}
1377
1378pub fn create_bls_pubkey_and_proof_of_possession(
1379    vote_account_pubkey: &Pubkey,
1380) -> (
1381    [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1382    [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1383) {
1384    let bls_keypair = BLSKeypair::new();
1385    create_bls_proof_of_possession(vote_account_pubkey, &bls_keypair)
1386}
1387
1388pub fn create_bls_proof_of_possession(
1389    vote_account_pubkey: &Pubkey,
1390    bls_keypair: &BLSKeypair,
1391) -> (
1392    [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1393    [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1394) {
1395    let bls_pubkey_bytes = bls_keypair.public.to_bytes_compressed();
1396    let message = generate_pop_message(vote_account_pubkey);
1397
1398    let proof_of_possession = bls_keypair.proof_of_possession(Some(&message));
1399    let proof_of_possession_bytes = proof_of_possession.to_bytes_compressed();
1400
1401    (bls_pubkey_bytes, proof_of_possession_bytes)
1402}
1403
1404#[allow(clippy::arithmetic_side_effects)]
1405#[cfg(test)]
1406mod tests {
1407    use {
1408        super::*,
1409        assert_matches::assert_matches,
1410        solana_account::{AccountSharedData, ReadableAccount},
1411        solana_clock::DEFAULT_SLOTS_PER_EPOCH,
1412        solana_sha256_hasher::hash,
1413        solana_transaction_context::{
1414            instruction_accounts::InstructionAccount, transaction::TransactionContext,
1415        },
1416        solana_vote_interface::authorized_voters::AuthorizedVoters,
1417        test_case::{test_case, test_matrix},
1418    };
1419
1420    const MAX_RECENT_VOTES: usize = 16;
1421
1422    fn vote_state_new_for_test(
1423        vote_pubkey: &Pubkey,
1424        target_version: VoteStateTargetVersion,
1425    ) -> VoteStateHandler {
1426        let auth_pubkey = solana_pubkey::new_rand();
1427        let vote_init = VoteInit {
1428            node_pubkey: solana_pubkey::new_rand(),
1429            authorized_voter: auth_pubkey,
1430            authorized_withdrawer: auth_pubkey,
1431            commission: 0,
1432        };
1433        let clock = Clock::default();
1434
1435        match target_version {
1436            VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1437                vote_pubkey,
1438                &vote_init,
1439                &clock,
1440            )),
1441        }
1442    }
1443
1444    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1445    fn test_vote_state_upgrade_from_1_14_11(target_version: VoteStateTargetVersion) {
1446        let vote_pubkey = solana_pubkey::new_rand();
1447        let mut vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
1448
1449        // Simulate prior epochs completed with credits and each setting a new authorized voter
1450        vote_state.increment_credits(0, 100);
1451        assert_eq!(
1452            vote_state.set_new_authorized_voter(
1453                &solana_pubkey::new_rand(),
1454                0,
1455                1,
1456                None,
1457                |_pubkey| Ok(())
1458            ),
1459            Ok(())
1460        );
1461        vote_state.increment_credits(1, 200);
1462        assert_eq!(
1463            vote_state.set_new_authorized_voter(
1464                &solana_pubkey::new_rand(),
1465                1,
1466                2,
1467                None,
1468                |_pubkey| Ok(())
1469            ),
1470            Ok(())
1471        );
1472        vote_state.increment_credits(2, 300);
1473        assert_eq!(
1474            vote_state.set_new_authorized_voter(
1475                &solana_pubkey::new_rand(),
1476                2,
1477                3,
1478                None,
1479                |_pubkey| Ok(())
1480            ),
1481            Ok(())
1482        );
1483
1484        // Simulate votes having occurred
1485        vec![
1486            100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
1487            117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
1488            134, 135,
1489        ]
1490        .into_iter()
1491        .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0));
1492
1493        // Create an initial vote account that is sized for the 1_14_11 version of vote state, and has only the
1494        // required lamports for rent exempt minimum at that size
1495        let vote_state_v1_14_11 = match target_version {
1496            VoteStateTargetVersion::V4 => {
1497                // v4 cannot be converted directly to V1_14_11.
1498                VoteState1_14_11 {
1499                    node_pubkey: *vote_state.node_pubkey(),
1500                    authorized_withdrawer: *vote_state.authorized_withdrawer(),
1501                    commission: vote_state.commission(),
1502                    votes: vote_state
1503                        .votes()
1504                        .iter()
1505                        .map(|landed_vote| (*landed_vote).into())
1506                        .collect(),
1507                    root_slot: vote_state.root_slot(),
1508                    authorized_voters: vote_state.authorized_voters().clone(),
1509                    epoch_credits: vote_state.epoch_credits().clone(),
1510                    last_timestamp: vote_state.last_timestamp().clone(),
1511                    prior_voters: CircBuf::default(), // v4 does not store prior_voters
1512                }
1513            }
1514        };
1515        let version1_14_11_serialized =
1516            bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(vote_state_v1_14_11)))
1517                .unwrap();
1518        let version1_14_11_serialized_len = version1_14_11_serialized.len();
1519        let rent = Rent::default();
1520        let lamports = rent.minimum_balance(version1_14_11_serialized_len);
1521        let mut vote_account =
1522            AccountSharedData::new(lamports, version1_14_11_serialized_len, &id());
1523        vote_account.set_data_from_slice(&version1_14_11_serialized);
1524
1525        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1526        // vote account that was just created
1527        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1528        let mut transaction_context = TransactionContext::new(
1529            vec![(id(), processor_account), (vote_pubkey, vote_account)],
1530            rent.clone(),
1531            0,
1532            0,
1533            1,
1534        );
1535        transaction_context
1536            .configure_top_level_instruction_for_tests(
1537                0,
1538                vec![InstructionAccount::new(1, false, true)],
1539                vec![],
1540            )
1541            .unwrap();
1542        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1543
1544        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1545        // state
1546        let mut borrowed_account = instruction_context
1547            .try_borrow_instruction_account(0)
1548            .unwrap();
1549
1550        // Ensure that the vote state started out at 1_14_11
1551        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1552        assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1553
1554        // Convert the vote state to current as would occur during vote instructions
1555        let converted_vote_state =
1556            get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1557
1558        // Check to make sure that the vote_state is unchanged
1559        assert!(vote_state == converted_vote_state);
1560
1561        let vote_state = converted_vote_state;
1562
1563        // Now re-set the vote account state, knowing the account only has
1564        // enough lamports for V1_14_11.
1565        match target_version {
1566            VoteStateTargetVersion::V4 => {
1567                // V4 will throw an error.
1568                assert_eq!(
1569                    vote_state
1570                        .clone()
1571                        .set_vote_account_state(&mut borrowed_account),
1572                    Err(InstructionError::AccountNotRentExempt)
1573                );
1574            }
1575        }
1576
1577        // Convert the vote state to current as would occur during vote instructions
1578        let converted_vote_state =
1579            get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1580
1581        // Check to make sure that the vote_state is unchanged
1582        assert!(vote_state == converted_vote_state);
1583
1584        let vote_state = converted_vote_state;
1585
1586        // Now top-up the vote account's lamports to be rent exempt for the target version.
1587        let space = match target_version {
1588            VoteStateTargetVersion::V4 => VoteStateV4::size_of(),
1589        };
1590        assert_eq!(
1591            borrowed_account.set_lamports(rent.minimum_balance(space)),
1592            Ok(())
1593        );
1594        assert_eq!(
1595            vote_state
1596                .clone()
1597                .set_vote_account_state(&mut borrowed_account),
1598            Ok(())
1599        );
1600
1601        // The vote state version should match the target version.
1602        let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1603        match target_version {
1604            VoteStateTargetVersion::V4 => {
1605                assert_matches!(vote_state_version, VoteStateVersions::V4(_));
1606            }
1607        }
1608
1609        // Convert the vote state to current as would occur during vote instructions
1610        let converted_vote_state =
1611            get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1612
1613        // Check to make sure that the vote_state is unchanged
1614        assert_eq!(vote_state, converted_vote_state);
1615    }
1616
1617    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1618    fn test_vote_lockout(target_version: VoteStateTargetVersion) {
1619        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1620
1621        for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
1622            process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
1623        }
1624
1625        // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
1626        assert_eq!(vote_state.votes().len(), MAX_LOCKOUT_HISTORY);
1627        assert_eq!(vote_state.root_slot(), Some(0));
1628        check_lockouts(&vote_state);
1629
1630        // One more vote that confirms the entire stack,
1631        // the root_slot should change to the
1632        // second vote
1633        let top_vote = vote_state.votes().front().unwrap().slot();
1634        let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
1635        process_slot_vote_unchecked(&mut vote_state, slot);
1636        assert_eq!(Some(top_vote), vote_state.root_slot());
1637
1638        // Expire everything except the first vote
1639        let slot = vote_state
1640            .votes()
1641            .front()
1642            .unwrap()
1643            .lockout
1644            .last_locked_out_slot();
1645        process_slot_vote_unchecked(&mut vote_state, slot);
1646        // First vote and new vote are both stored for a total of 2 votes
1647        assert_eq!(vote_state.votes().len(), 2);
1648    }
1649
1650    #[test_matrix(
1651        [VoteStateTargetVersion::V4],
1652        [true, false]
1653    )]
1654    fn test_update_commission(
1655        target_version: VoteStateTargetVersion,
1656        disable_commission_update_rule: bool,
1657    ) {
1658        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1659        let node_pubkey = *vote_state.node_pubkey();
1660        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1661
1662        // Set commission to start.
1663        vote_state.set_commission(10);
1664
1665        let serialized = vote_state.serialize();
1666        let serialized_len = serialized.len();
1667        let rent = Rent::default();
1668        let lamports = rent.minimum_balance(serialized_len);
1669        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1670        vote_account.set_data_from_slice(&serialized);
1671
1672        // Create a fake TransactionContext with a fake InstructionContext with a single account which is the
1673        // vote account that was just created
1674        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1675        let mut transaction_context = TransactionContext::new(
1676            vec![(id(), processor_account), (node_pubkey, vote_account)],
1677            rent,
1678            0,
1679            0,
1680            1,
1681        );
1682        transaction_context
1683            .configure_top_level_instruction_for_tests(
1684                0,
1685                vec![InstructionAccount::new(1, false, true)],
1686                vec![],
1687            )
1688            .unwrap();
1689        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1690
1691        // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account
1692        // state
1693        let mut borrowed_account = instruction_context
1694            .try_borrow_instruction_account(0)
1695            .unwrap();
1696
1697        let epoch_schedule = std::sync::Arc::new(EpochSchedule::without_warmup());
1698
1699        let first_half_clock = std::sync::Arc::new(Clock {
1700            slot: epoch_schedule.slots_per_epoch / 4,
1701            ..Clock::default()
1702        });
1703
1704        let second_half_clock = std::sync::Arc::new(Clock {
1705            slot: (epoch_schedule.slots_per_epoch * 3) / 4,
1706            ..Clock::default()
1707        });
1708
1709        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1710
1711        // Increase commission in first half of epoch -- allowed
1712        assert_eq!(
1713            get_vote_state_handler_checked(&borrowed_account, target_version,)
1714                .unwrap()
1715                .commission(),
1716            10
1717        );
1718        assert_matches!(
1719            update_commission(
1720                &mut borrowed_account,
1721                target_version,
1722                11,
1723                &signers,
1724                &epoch_schedule,
1725                &first_half_clock,
1726                disable_commission_update_rule,
1727            ),
1728            Ok(())
1729        );
1730        assert_eq!(
1731            get_vote_state_handler_checked(&borrowed_account, target_version,)
1732                .unwrap()
1733                .commission(),
1734            11
1735        );
1736
1737        // Increase commission in second half of epoch -- disallowed if update rule is enabled
1738        let result = update_commission(
1739            &mut borrowed_account,
1740            target_version,
1741            12,
1742            &signers,
1743            &epoch_schedule,
1744            &second_half_clock,
1745            disable_commission_update_rule,
1746        );
1747        let state_commission = get_vote_state_handler_checked(&borrowed_account, target_version)
1748            .unwrap()
1749            .commission();
1750        if disable_commission_update_rule {
1751            assert_matches!(result, Ok(()));
1752            assert_eq!(state_commission, 12);
1753        } else {
1754            assert_matches!(result, Err(_));
1755            assert_eq!(state_commission, 11);
1756        }
1757
1758        // Decrease commission in first half of epoch -- always allowed
1759        assert_matches!(
1760            update_commission(
1761                &mut borrowed_account,
1762                target_version,
1763                10,
1764                &signers,
1765                &epoch_schedule,
1766                &first_half_clock,
1767                disable_commission_update_rule,
1768            ),
1769            Ok(())
1770        );
1771        assert_eq!(
1772            get_vote_state_handler_checked(&borrowed_account, target_version,)
1773                .unwrap()
1774                .commission(),
1775            10
1776        );
1777
1778        assert_eq!(
1779            get_vote_state_handler_checked(&borrowed_account, target_version,)
1780                .unwrap()
1781                .commission(),
1782            10
1783        );
1784
1785        // Decrease commission in second half of epoch -- always allowed
1786        assert_matches!(
1787            update_commission(
1788                &mut borrowed_account,
1789                target_version,
1790                9,
1791                &signers,
1792                &epoch_schedule,
1793                &second_half_clock,
1794                disable_commission_update_rule,
1795            ),
1796            Ok(())
1797        );
1798        assert_eq!(
1799            get_vote_state_handler_checked(&borrowed_account, target_version,)
1800                .unwrap()
1801                .commission(),
1802            9
1803        );
1804    }
1805
1806    /// Test update_commission_bps (SIMD-0291).
1807    ///
1808    /// Unlike test_update_commission, SIMD-0291 has no timing restrictions
1809    /// (per SIMD-0249). Updates are always allowed regardless of epoch position.
1810    ///
1811    /// This test only uses V4 since SIMD-0291 depends on SIMD-0185 (VoteStateV4).
1812    #[test]
1813    fn test_update_commission_bps() {
1814        let target_version = VoteStateTargetVersion::V4;
1815        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1816        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1817        let node_pubkey = *vote_state.node_pubkey();
1818
1819        // Set initial commission.
1820        vote_state.set_commission(10); // 10%
1821
1822        let serialized = vote_state.serialize();
1823        let serialized_len = serialized.len();
1824        let rent = Rent::default();
1825        let lamports = rent.minimum_balance(serialized_len);
1826        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1827        vote_account.set_data_from_slice(&serialized);
1828
1829        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1830        let mut transaction_context = TransactionContext::new(
1831            vec![(id(), processor_account), (node_pubkey, vote_account)],
1832            rent,
1833            0,
1834            0,
1835            1,
1836        );
1837        transaction_context
1838            .configure_top_level_instruction_for_tests(
1839                0,
1840                vec![InstructionAccount::new(1, false, true)],
1841                vec![],
1842            )
1843            .unwrap();
1844        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1845        let mut borrowed_account = instruction_context
1846            .try_borrow_instruction_account(0)
1847            .unwrap();
1848
1849        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1850        let non_signers: HashSet<Pubkey> = HashSet::new();
1851
1852        // `CommissionKind::BlockRevenue` returns `InvalidInstructionData` when
1853        // block_revenue_sharing is disabled.
1854        assert_eq!(
1855            update_commission_bps(
1856                &mut borrowed_account,
1857                target_version,
1858                500,
1859                CommissionKind::BlockRevenue,
1860                &signers,
1861                false, // block_revenue_sharing disabled
1862            ),
1863            Err(InstructionError::InvalidInstructionData)
1864        );
1865
1866        // Missing signature returns `MissingRequiredSignature`.
1867        assert_eq!(
1868            update_commission_bps(
1869                &mut borrowed_account,
1870                target_version,
1871                500,
1872                CommissionKind::InflationRewards,
1873                &non_signers,
1874                false,
1875            ),
1876            Err(InstructionError::MissingRequiredSignature)
1877        );
1878
1879        // Incorrect signature for withdraw authority returns `MissingRequiredSignature`.
1880        let wrong_signers: HashSet<Pubkey> = vec![Pubkey::new_unique()].into_iter().collect();
1881        assert_eq!(
1882            update_commission_bps(
1883                &mut borrowed_account,
1884                target_version,
1885                500,
1886                CommissionKind::InflationRewards,
1887                &wrong_signers,
1888                false,
1889            ),
1890            Err(InstructionError::MissingRequiredSignature)
1891        );
1892
1893        let mut commission_bps_roundtrip = |new_commission_bps: u16| {
1894            update_commission_bps(
1895                &mut borrowed_account,
1896                target_version,
1897                new_commission_bps,
1898                CommissionKind::InflationRewards,
1899                &signers,
1900                false,
1901            )
1902            .unwrap();
1903            update_commission_bps(
1904                &mut borrowed_account,
1905                target_version,
1906                new_commission_bps,
1907                CommissionKind::BlockRevenue,
1908                &signers,
1909                true,
1910            )
1911            .unwrap();
1912            let handler =
1913                get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1914            assert_eq!(
1915                handler.as_ref_v4().inflation_rewards_commission_bps,
1916                new_commission_bps
1917            );
1918            assert_eq!(
1919                handler.as_ref_v4().block_revenue_commission_bps,
1920                new_commission_bps
1921            );
1922        };
1923
1924        // There's no timing check for SIMD-0291, so just go back and forth
1925        // with new values.
1926
1927        commission_bps_roundtrip(1_100); // Increase to 11%
1928        commission_bps_roundtrip(5_000); // Increase to 50%
1929        commission_bps_roundtrip(4_400); // Decrease to 44%
1930        commission_bps_roundtrip(4_600); // Increase to 46%
1931
1932        // Values > 10,000 bps are allowed at program level.
1933        commission_bps_roundtrip(15_000); // 150%
1934        commission_bps_roundtrip(50_000); // 500%
1935    }
1936
1937    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1938    fn test_vote_double_lockout_after_expiration(target_version: VoteStateTargetVersion) {
1939        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1940
1941        for i in 0..3 {
1942            process_slot_vote_unchecked(&mut vote_state, i as u64);
1943        }
1944
1945        check_lockouts(&vote_state);
1946
1947        // Expire the third vote (which was a vote for slot 2). The height of the
1948        // vote stack is unchanged, so none of the previous votes should have
1949        // doubled in lockout
1950        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
1951        check_lockouts(&vote_state);
1952
1953        // Vote again, this time the vote stack depth increases, so the votes should
1954        // double for everybody
1955        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
1956        check_lockouts(&vote_state);
1957
1958        // Vote again, this time the vote stack depth increases, so the votes should
1959        // double for everybody
1960        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
1961        check_lockouts(&vote_state);
1962    }
1963
1964    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1965    fn test_expire_multiple_votes(target_version: VoteStateTargetVersion) {
1966        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1967
1968        for i in 0..3 {
1969            process_slot_vote_unchecked(&mut vote_state, i as u64);
1970        }
1971
1972        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1973
1974        // Expire the second and third votes
1975        let expire_slot =
1976            vote_state.votes()[1].slot() + vote_state.votes()[1].lockout.lockout() + 1;
1977        process_slot_vote_unchecked(&mut vote_state, expire_slot);
1978        assert_eq!(vote_state.votes().len(), 2);
1979
1980        // Check that the old votes expired
1981        assert_eq!(vote_state.votes()[0].slot(), 0);
1982        assert_eq!(vote_state.votes()[1].slot(), expire_slot);
1983
1984        // Process one more vote
1985        process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);
1986
1987        // Confirmation count for the older first vote should remain unchanged
1988        assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1989
1990        // The later votes should still have increasing confirmation counts
1991        assert_eq!(vote_state.votes()[1].confirmation_count(), 2);
1992        assert_eq!(vote_state.votes()[2].confirmation_count(), 1);
1993    }
1994
1995    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1996    fn test_vote_credits(target_version: VoteStateTargetVersion) {
1997        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1998
1999        for i in 0..MAX_LOCKOUT_HISTORY {
2000            process_slot_vote_unchecked(&mut vote_state, i as u64);
2001        }
2002
2003        assert_eq!(vote_state.credits(), 0);
2004
2005        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
2006        assert_eq!(vote_state.credits(), 1);
2007        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
2008        assert_eq!(vote_state.credits(), 2);
2009        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
2010        assert_eq!(vote_state.credits(), 3);
2011    }
2012
2013    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2014    fn test_duplicate_vote(target_version: VoteStateTargetVersion) {
2015        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2016        process_slot_vote_unchecked(&mut vote_state, 0);
2017        process_slot_vote_unchecked(&mut vote_state, 1);
2018        process_slot_vote_unchecked(&mut vote_state, 0);
2019        assert_eq!(vote_state.nth_recent_lockout(0).unwrap().slot(), 1);
2020        assert_eq!(vote_state.nth_recent_lockout(1).unwrap().slot(), 0);
2021        assert!(vote_state.nth_recent_lockout(2).is_none());
2022    }
2023
2024    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2025    fn test_nth_recent_lockout(target_version: VoteStateTargetVersion) {
2026        let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2027        for i in 0..MAX_LOCKOUT_HISTORY {
2028            process_slot_vote_unchecked(&mut vote_state, i as u64);
2029        }
2030        for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
2031            assert_eq!(
2032                vote_state.nth_recent_lockout(i).unwrap().slot() as usize,
2033                MAX_LOCKOUT_HISTORY - i - 1,
2034            );
2035        }
2036        assert!(vote_state.nth_recent_lockout(MAX_LOCKOUT_HISTORY).is_none());
2037    }
2038
2039    fn check_lockouts(vote_state: &VoteStateHandler) {
2040        let votes = vote_state.votes();
2041        for (i, vote) in votes.iter().enumerate() {
2042            let num_votes = votes
2043                .len()
2044                .checked_sub(i)
2045                .expect("`i` is less than `vote_state.votes().len()`");
2046            assert_eq!(
2047                vote.lockout.lockout(),
2048                INITIAL_LOCKOUT.pow(num_votes as u32) as u64
2049            );
2050        }
2051    }
2052
2053    fn recent_votes(vote_state: &VoteStateHandler) -> Vec<Vote> {
2054        let votes = vote_state.votes();
2055        let start = votes.len().saturating_sub(MAX_RECENT_VOTES);
2056        (start..votes.len())
2057            .map(|i| Vote::new(vec![votes.get(i).unwrap().slot()], Hash::default()))
2058            .collect()
2059    }
2060
2061    /// check that two accounts with different data can be brought to the same state with one vote submission
2062    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2063    fn test_process_missed_votes(target_version: VoteStateTargetVersion) {
2064        let mut vote_state_a = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2065        let mut vote_state_b = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2066
2067        // process some votes on account a
2068        (0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
2069        assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
2070
2071        // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
2072        let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
2073        let vote = Vote::new(slots, Hash::default());
2074        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2075
2076        assert_eq!(
2077            process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0),
2078            Ok(())
2079        );
2080        assert_eq!(
2081            process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0),
2082            Ok(())
2083        );
2084        assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
2085    }
2086
2087    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2088    fn test_process_vote_skips_old_vote(mut vote_state: VoteStateHandler) {
2089        let vote = Vote::new(vec![0], Hash::default());
2090        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
2091        assert_eq!(
2092            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2093            Ok(())
2094        );
2095        let recent = recent_votes(&vote_state);
2096        assert_eq!(
2097            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2098            Err(VoteError::VoteTooOld)
2099        );
2100        assert_eq!(recent, recent_votes(&vote_state));
2101    }
2102
2103    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2104    fn test_check_slots_are_valid_vote_empty_slot_hashes(vote_state: VoteStateHandler) {
2105        let vote = Vote::new(vec![0], Hash::default());
2106        assert_eq!(
2107            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
2108            Err(VoteError::VoteTooOld)
2109        );
2110    }
2111
2112    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2113    fn test_check_slots_are_valid_new_vote(vote_state: VoteStateHandler) {
2114        let vote = Vote::new(vec![0], Hash::default());
2115        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2116        assert_eq!(
2117            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2118            Ok(())
2119        );
2120    }
2121
2122    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2123    fn test_check_slots_are_valid_bad_hash(vote_state: VoteStateHandler) {
2124        let vote = Vote::new(vec![0], Hash::default());
2125        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
2126        assert_eq!(
2127            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2128            Err(VoteError::SlotHashMismatch)
2129        );
2130    }
2131
2132    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2133    fn test_check_slots_are_valid_bad_slot(vote_state: VoteStateHandler) {
2134        let vote = Vote::new(vec![1], Hash::default());
2135        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
2136        assert_eq!(
2137            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2138            Err(VoteError::SlotsMismatch)
2139        );
2140    }
2141
2142    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2143    fn test_check_slots_are_valid_duplicate_vote(mut vote_state: VoteStateHandler) {
2144        let vote = Vote::new(vec![0], Hash::default());
2145        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2146        assert_eq!(
2147            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2148            Ok(())
2149        );
2150        assert_eq!(
2151            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2152            Err(VoteError::VoteTooOld)
2153        );
2154    }
2155
2156    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2157    fn test_check_slots_are_valid_next_vote(mut vote_state: VoteStateHandler) {
2158        let vote = Vote::new(vec![0], Hash::default());
2159        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2160        assert_eq!(
2161            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2162            Ok(())
2163        );
2164
2165        let vote = Vote::new(vec![0, 1], Hash::default());
2166        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
2167        assert_eq!(
2168            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2169            Ok(())
2170        );
2171    }
2172
2173    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2174    fn test_check_slots_are_valid_next_vote_only(mut vote_state: VoteStateHandler) {
2175        let vote = Vote::new(vec![0], Hash::default());
2176        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2177        assert_eq!(
2178            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2179            Ok(())
2180        );
2181
2182        let vote = Vote::new(vec![1], Hash::default());
2183        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
2184        assert_eq!(
2185            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2186            Ok(())
2187        );
2188    }
2189
2190    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2191    fn test_process_vote_empty_slots(mut vote_state: VoteStateHandler) {
2192        let vote = Vote::new(vec![], Hash::default());
2193        assert_eq!(
2194            process_vote(&mut vote_state, &vote, &[], 0, 0),
2195            Err(VoteError::EmptySlots)
2196        );
2197    }
2198
2199    pub fn process_new_vote_state_from_lockouts(
2200        vote_state: &mut VoteStateHandler,
2201        new_state: VecDeque<Lockout>,
2202        new_root: Option<Slot>,
2203        timestamp: Option<i64>,
2204        epoch: Epoch,
2205    ) -> Result<(), VoteError> {
2206        process_new_vote_state(
2207            vote_state,
2208            new_state.into_iter().map(LandedVote::from).collect(),
2209            new_root,
2210            timestamp,
2211            epoch,
2212            0,
2213        )
2214    }
2215
2216    // Test vote credit updates after "one credit per slot" feature is enabled
2217    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2218    fn test_vote_state_update_increment_credits(mut vote_state: VoteStateHandler) {
2219        // Test data: a sequence of groups of votes to simulate having been cast, after each group a vote
2220        // state update is compared to "normal" vote processing to ensure that credits are earned equally
2221        let test_vote_groups: Vec<Vec<Slot>> = vec![
2222            // Initial set of votes that don't dequeue any slots, so no credits earned
2223            vec![1, 2, 3, 4, 5, 6, 7, 8],
2224            vec![
2225                9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
2226                30, 31,
2227            ],
2228            // Now a single vote which should result in the first root and first credit earned
2229            vec![32],
2230            // Now another vote, should earn one credit
2231            vec![33],
2232            // Two votes in sequence
2233            vec![34, 35],
2234            // 3 votes in sequence
2235            vec![36, 37, 38],
2236            // 30 votes in sequence
2237            vec![
2238                39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
2239                60, 61, 62, 63, 64, 65, 66, 67, 68,
2240            ],
2241            // 31 votes in sequence
2242            vec![
2243                69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
2244                90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2245            ],
2246            // Votes with expiry
2247            vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
2248            // More votes with expiry of a large number of votes
2249            vec![200, 201],
2250            vec![
2251                202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2252                218, 219, 220, 221, 222, 223, 224, 225, 226,
2253            ],
2254            vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2255        ];
2256
2257        for vote_group in test_vote_groups {
2258            // Duplicate vote_state so that the new vote can be applied
2259            let mut vote_state_after_vote = vote_state.clone();
2260
2261            process_vote_unchecked(
2262                &mut vote_state_after_vote,
2263                Vote {
2264                    slots: vote_group.clone(),
2265                    hash: Hash::new_unique(),
2266                    timestamp: None,
2267                },
2268            )
2269            .unwrap();
2270
2271            // Now use the resulting new vote state to perform a vote state update on vote_state
2272            assert_eq!(
2273                process_new_vote_state(
2274                    &mut vote_state,
2275                    vote_state_after_vote.votes().clone(),
2276                    vote_state_after_vote.root_slot(),
2277                    None,
2278                    0,
2279                    0,
2280                ),
2281                Ok(())
2282            );
2283
2284            // And ensure that the credits earned were the same
2285            assert_eq!(
2286                vote_state.epoch_credits(),
2287                vote_state_after_vote.epoch_credits()
2288            );
2289        }
2290    }
2291
2292    // Test vote credit updates after "timely vote credits" feature is enabled
2293    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2294    fn test_timely_credits(target_version: VoteStateTargetVersion) {
2295        // Each of the following (Vec<Slot>, Slot, u32) tuples gives a set of slots to cast votes on, a slot in which
2296        // the vote was cast, and the number of credits that should have been earned by the vote account after this
2297        // and all prior votes were cast.
2298        let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
2299            // Initial set of votes that don't dequeue any slots, so no credits earned
2300            (
2301                vec![1, 2, 3, 4, 5, 6, 7, 8],
2302                9,
2303                // root: none, no credits earned
2304                0,
2305            ),
2306            (
2307                vec![
2308                    9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
2309                    29, 30, 31,
2310                ],
2311                34,
2312                // lockouts full
2313                // root: none, no credits earned
2314                0,
2315            ),
2316            // Now a single vote which should result in the first root and first credit earned
2317            (
2318                vec![32],
2319                35,
2320                // root: 1
2321                // when slot 1 was voted on in slot 9, it earned 10 credits
2322                10,
2323            ),
2324            // Now another vote, should earn one credit
2325            (
2326                vec![33],
2327                36,
2328                // root: 2
2329                // when slot 2 was voted on in slot 9, it earned 11 credits
2330                10 + 11, // 21
2331            ),
2332            // Two votes in sequence
2333            (
2334                vec![34, 35],
2335                37,
2336                // root: 4
2337                // when slots 3 and 4 were voted on in slot 9, they earned 12 and 13 credits
2338                21 + 12 + 13, // 46
2339            ),
2340            // 3 votes in sequence
2341            (
2342                vec![36, 37, 38],
2343                39,
2344                // root: 7
2345                // slots 5, 6, and 7 earned 14, 15, and 16 credits when voted in slot 9
2346                46 + 14 + 15 + 16, // 91
2347            ),
2348            (
2349                // 30 votes in sequence
2350                vec![
2351                    39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
2352                    58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
2353                ],
2354                69,
2355                // root: 37
2356                // slot 8 was voted in slot 9, earning 16 credits
2357                // 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
2358                //   slot 34
2359                // slot 26, 27, 28, 29, 30, 31 earned 10, 11, 12, 13, 14, 15 credits when voted in slot 34
2360                // slot 32 earned 15 credits when voted in slot 35
2361                // slot 33 earned 15 credits when voted in slot 36
2362                // slot 34 and 35 earned 15 and 16 credits when voted in slot 37
2363                // slot 36 and 37 earned 15 and 16 credits when voted in slot 39
2364                91 + 16
2365                    + 9 // * 1
2366                    + 2
2367                    + 3
2368                    + 4
2369                    + 5
2370                    + 6
2371                    + 7
2372                    + 8
2373                    + 9
2374                    + 10
2375                    + 11
2376                    + 12
2377                    + 13
2378                    + 14
2379                    + 15
2380                    + 15
2381                    + 15
2382                    + 15
2383                    + 16
2384                    + 15
2385                    + 16, // 327
2386            ),
2387            // 31 votes in sequence
2388            (
2389                vec![
2390                    69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
2391                    89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2392                ],
2393                100,
2394                // root: 68
2395                // slot 38 earned 16 credits when voted in slot 39
2396                // 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
2397                //   when voted in slot 69
2398                // slot 61, 62, 63, 64, 65, 66, 67, 68 earned 10, 11, 12, 13, 14, 15, 16, and 16 credits when
2399                //   voted in slot 69
2400                327 + 16
2401                    + 14 // * 1
2402                    + 2
2403                    + 3
2404                    + 4
2405                    + 5
2406                    + 6
2407                    + 7
2408                    + 8
2409                    + 9
2410                    + 10
2411                    + 11
2412                    + 12
2413                    + 13
2414                    + 14
2415                    + 15
2416                    + 16
2417                    + 16, // 508
2418            ),
2419            // Votes with expiry
2420            (
2421                vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
2422                130,
2423                // root: 74
2424                // slots 96 - 114 expire
2425                // slots 69 - 74 earned 1 credit when voted in slot 100
2426                508 + ((74 - 69) + 1), // 514
2427            ),
2428            // More votes with expiry of a large number of votes
2429            (
2430                vec![200, 201],
2431                202,
2432                // root: 74
2433                // slots 119 - 124 expire
2434                514,
2435            ),
2436            (
2437                vec![
2438                    202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2439                    218, 219, 220, 221, 222, 223, 224, 225, 226,
2440                ],
2441                227,
2442                // root: 95
2443                // 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
2444                //   slot 100
2445                // slot 92, 93, 94, 95 earned 10, 11, 12, 13, credits when voted in slot 100
2446                514 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13, // 613
2447            ),
2448            (
2449                vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2450                237,
2451                // root: 205
2452                // slot 115 - 118 earned 3, 4, 5, and 6 credits when voted in slot 130
2453                // slot 200 and 201 earned 16 credits when voted in slot 202
2454                // slots 202 - 205 earned 1 credit when voted in slot 227
2455                613 + 3 + 4 + 5 + 6 + 16 + 16 + 1 + 1 + 1 + 1, // 667
2456            ),
2457        ];
2458
2459        let new_vote_state = || match target_version {
2460            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2461        };
2462
2463        // For each vote group, process all vote groups leading up to it and it itself, and ensure that the number of
2464        // credits earned is correct for both regular votes and vote state updates
2465        for i in 0..test_vote_groups.len() {
2466            // Create a new VoteStateV3 for vote transaction
2467            let mut vote_state_1 = new_vote_state();
2468            // Create a new VoteStateV3 for vote state update transaction
2469            let mut vote_state_2 = new_vote_state();
2470            test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
2471                let vote = Vote {
2472                    slots: vote_group.0.clone(), //vote_group.0 is the set of slots to cast votes on
2473                    hash: Hash::new_unique(),
2474                    timestamp: None,
2475                };
2476                let slot_hashes: Vec<_> =
2477                    vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2478                assert_eq!(
2479                    process_vote(
2480                        &mut vote_state_1,
2481                        &vote,
2482                        &slot_hashes,
2483                        0,
2484                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2485                    ),
2486                    Ok(())
2487                );
2488
2489                assert_eq!(
2490                    process_new_vote_state(
2491                        &mut vote_state_2,
2492                        vote_state_1.votes().clone(),
2493                        vote_state_1.root_slot(),
2494                        None,
2495                        0,
2496                        vote_group.1, // vote_group.1 is the slot in which the vote was cast
2497                    ),
2498                    Ok(())
2499                );
2500            });
2501
2502            // Ensure that the credits earned is correct for both vote states
2503            let vote_group = &test_vote_groups[i];
2504            assert_eq!(vote_state_1.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2505            assert_eq!(vote_state_2.credits(), vote_group.2 as u64); // vote_group.2 is the expected number of credits
2506        }
2507    }
2508
2509    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2510    fn test_retroactive_voting_timely_credits(mut vote_state: VoteStateHandler) {
2511        // Each of the following (Vec<(Slot, int)>, Slot, Option<Slot>, u32) tuples gives the following data:
2512        // Vec<(Slot, int)> -- the set of slots and confirmation_counts that is the proposed vote state
2513        // Slot -- the slot in which the proposed vote state landed
2514        // Option<Slot> -- the root after processing the proposed vote state
2515        // u32 -- the credits after processing the proposed vote state
2516        #[allow(clippy::type_complexity)]
2517        let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
2518            // proposed vote state to set initial vote state
2519            (
2520                vec![(7, 4), (8, 3), (9, 2), (10, 1)],
2521                11,
2522                // root: none
2523                None,
2524                // no credits earned
2525                0,
2526            ),
2527            // proposed vote state to include the missing slots *prior to previously included slots*
2528            (
2529                vec![
2530                    (1, 10),
2531                    (2, 9),
2532                    (3, 8),
2533                    (4, 7),
2534                    (5, 6),
2535                    (6, 5),
2536                    (7, 4),
2537                    (8, 3),
2538                    (9, 2),
2539                    (10, 1),
2540                ],
2541                12,
2542                // root: none
2543                None,
2544                // no credits earned
2545                0,
2546            ),
2547            // Now a single proposed vote state which roots all of the slots from 1 - 10
2548            (
2549                vec![
2550                    (11, 31),
2551                    (12, 30),
2552                    (13, 29),
2553                    (14, 28),
2554                    (15, 27),
2555                    (16, 26),
2556                    (17, 25),
2557                    (18, 24),
2558                    (19, 23),
2559                    (20, 22),
2560                    (21, 21),
2561                    (22, 20),
2562                    (23, 19),
2563                    (24, 18),
2564                    (25, 17),
2565                    (26, 16),
2566                    (27, 15),
2567                    (28, 14),
2568                    (29, 13),
2569                    (30, 12),
2570                    (31, 11),
2571                    (32, 10),
2572                    (33, 9),
2573                    (34, 8),
2574                    (35, 7),
2575                    (36, 6),
2576                    (37, 5),
2577                    (38, 4),
2578                    (39, 3),
2579                    (40, 2),
2580                    (41, 1),
2581                ],
2582                42,
2583                // root: 10
2584                Some(10),
2585                // when slots 1 - 6 were voted on in slot 12, they earned 7, 8, 9, 10, 11, and 12 credits
2586                // when slots 7 - 10 were voted on in slot 11, they earned 14, 15, 16, and 16 credits
2587                7 + 8 + 9 + 10 + 11 + 12 + 14 + 15 + 16 + 16,
2588            ),
2589        ];
2590
2591        // Process the vote state updates in sequence and ensure that the credits earned after each is processed is
2592        // correct
2593        test_vote_state_updates
2594            .iter()
2595            .for_each(|proposed_vote_state| {
2596                let new_state = proposed_vote_state
2597                    .0 // proposed_vote_state.0 is the set of slots and confirmation_counts that is the proposed vote state
2598                    .iter()
2599                    .map(|(slot, confirmation_count)| LandedVote {
2600                        latency: 0,
2601                        lockout: Lockout::new_with_confirmation_count(*slot, *confirmation_count),
2602                    })
2603                    .collect::<VecDeque<LandedVote>>();
2604                assert_eq!(
2605                    process_new_vote_state(
2606                        &mut vote_state,
2607                        new_state,
2608                        proposed_vote_state.2, // proposed_vote_state.2 is root after processing the proposed vote state
2609                        None,
2610                        0,
2611                        proposed_vote_state.1, // proposed_vote_state.1 is the slot in which the proposed vote state was applied
2612                    ),
2613                    Ok(())
2614                );
2615
2616                // Ensure that the credits earned is correct
2617                assert_eq!(vote_state.credits(), proposed_vote_state.3 as u64);
2618            });
2619    }
2620
2621    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2622    fn test_process_new_vote_too_many_votes(mut vote_state1: VoteStateHandler) {
2623        let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
2624            .map(|slot| {
2625                Lockout::new_with_confirmation_count(
2626                    slot as Slot,
2627                    (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
2628                )
2629            })
2630            .collect();
2631
2632        let current_epoch = vote_state1.current_epoch();
2633        assert_eq!(
2634            process_new_vote_state_from_lockouts(
2635                &mut vote_state1,
2636                bad_votes,
2637                None,
2638                None,
2639                current_epoch,
2640            ),
2641            Err(VoteError::TooManyVotes)
2642        );
2643    }
2644
2645    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2646    fn test_process_new_vote_state_root_rollback(mut vote_state1: VoteStateHandler) {
2647        for i in 0..MAX_LOCKOUT_HISTORY + 2 {
2648            process_slot_vote_unchecked(&mut vote_state1, i as Slot);
2649        }
2650        assert_eq!(vote_state1.root_slot().unwrap(), 1);
2651
2652        // Update vote_state2 with a higher slot so that `process_new_vote_state`
2653        // doesn't panic.
2654        let mut vote_state2 = vote_state1.clone();
2655        process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);
2656
2657        // Trying to set a lesser root should error
2658        let lesser_root = Some(0);
2659
2660        let current_epoch = vote_state2.current_epoch();
2661        assert_eq!(
2662            process_new_vote_state(
2663                &mut vote_state1,
2664                vote_state2.votes().clone(),
2665                lesser_root,
2666                None,
2667                current_epoch,
2668                0,
2669            ),
2670            Err(VoteError::RootRollBack)
2671        );
2672
2673        // Trying to set root to None should error
2674        let none_root = None;
2675        assert_eq!(
2676            process_new_vote_state(
2677                &mut vote_state1,
2678                vote_state2.votes().clone(),
2679                none_root,
2680                None,
2681                current_epoch,
2682                0,
2683            ),
2684            Err(VoteError::RootRollBack)
2685        );
2686    }
2687
2688    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2689    fn test_process_new_vote_state_zero_confirmations(mut vote_state1: VoteStateHandler) {
2690        let current_epoch = vote_state1.current_epoch();
2691
2692        let bad_votes: VecDeque<Lockout> = vec![
2693            Lockout::new_with_confirmation_count(0, 0),
2694            Lockout::new_with_confirmation_count(1, 1),
2695        ]
2696        .into_iter()
2697        .collect();
2698        assert_eq!(
2699            process_new_vote_state_from_lockouts(
2700                &mut vote_state1,
2701                bad_votes,
2702                None,
2703                None,
2704                current_epoch,
2705            ),
2706            Err(VoteError::ZeroConfirmations)
2707        );
2708
2709        let bad_votes: VecDeque<Lockout> = vec![
2710            Lockout::new_with_confirmation_count(0, 2),
2711            Lockout::new_with_confirmation_count(1, 0),
2712        ]
2713        .into_iter()
2714        .collect();
2715        assert_eq!(
2716            process_new_vote_state_from_lockouts(
2717                &mut vote_state1,
2718                bad_votes,
2719                None,
2720                None,
2721                current_epoch,
2722            ),
2723            Err(VoteError::ZeroConfirmations)
2724        );
2725    }
2726
2727    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2728    fn test_process_new_vote_state_confirmations_too_large(initial_vote_state: VoteStateHandler) {
2729        let mut vote_state1 = initial_vote_state.clone();
2730        let current_epoch = vote_state1.current_epoch();
2731
2732        let good_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2733            0,
2734            MAX_LOCKOUT_HISTORY as u32,
2735        )]
2736        .into_iter()
2737        .collect();
2738
2739        process_new_vote_state_from_lockouts(
2740            &mut vote_state1,
2741            good_votes,
2742            None,
2743            None,
2744            current_epoch,
2745        )
2746        .unwrap();
2747
2748        let mut vote_state1 = initial_vote_state;
2749        let bad_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2750            0,
2751            MAX_LOCKOUT_HISTORY as u32 + 1,
2752        )]
2753        .into_iter()
2754        .collect();
2755        assert_eq!(
2756            process_new_vote_state_from_lockouts(
2757                &mut vote_state1,
2758                bad_votes,
2759                None,
2760                None,
2761                current_epoch,
2762            ),
2763            Err(VoteError::ConfirmationTooLarge)
2764        );
2765    }
2766
2767    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2768    fn test_process_new_vote_state_slot_smaller_than_root(mut vote_state1: VoteStateHandler) {
2769        let current_epoch = vote_state1.current_epoch();
2770        let root_slot = 5;
2771
2772        let bad_votes: VecDeque<Lockout> = vec![
2773            Lockout::new_with_confirmation_count(root_slot, 2),
2774            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2775        ]
2776        .into_iter()
2777        .collect();
2778        assert_eq!(
2779            process_new_vote_state_from_lockouts(
2780                &mut vote_state1,
2781                bad_votes,
2782                Some(root_slot),
2783                None,
2784                current_epoch,
2785            ),
2786            Err(VoteError::SlotSmallerThanRoot)
2787        );
2788
2789        let bad_votes: VecDeque<Lockout> = vec![
2790            Lockout::new_with_confirmation_count(root_slot - 1, 2),
2791            Lockout::new_with_confirmation_count(root_slot + 1, 1),
2792        ]
2793        .into_iter()
2794        .collect();
2795        assert_eq!(
2796            process_new_vote_state_from_lockouts(
2797                &mut vote_state1,
2798                bad_votes,
2799                Some(root_slot),
2800                None,
2801                current_epoch,
2802            ),
2803            Err(VoteError::SlotSmallerThanRoot)
2804        );
2805    }
2806
2807    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2808    fn test_process_new_vote_state_slots_not_ordered(mut vote_state1: VoteStateHandler) {
2809        let current_epoch = vote_state1.current_epoch();
2810
2811        let bad_votes: VecDeque<Lockout> = vec![
2812            Lockout::new_with_confirmation_count(1, 2),
2813            Lockout::new_with_confirmation_count(0, 1),
2814        ]
2815        .into_iter()
2816        .collect();
2817        assert_eq!(
2818            process_new_vote_state_from_lockouts(
2819                &mut vote_state1,
2820                bad_votes,
2821                None,
2822                None,
2823                current_epoch,
2824            ),
2825            Err(VoteError::SlotsNotOrdered)
2826        );
2827
2828        let bad_votes: VecDeque<Lockout> = vec![
2829            Lockout::new_with_confirmation_count(1, 2),
2830            Lockout::new_with_confirmation_count(1, 1),
2831        ]
2832        .into_iter()
2833        .collect();
2834        assert_eq!(
2835            process_new_vote_state_from_lockouts(
2836                &mut vote_state1,
2837                bad_votes,
2838                None,
2839                None,
2840                current_epoch,
2841            ),
2842            Err(VoteError::SlotsNotOrdered)
2843        );
2844    }
2845
2846    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2847    fn test_process_new_vote_state_confirmations_not_ordered(mut vote_state1: VoteStateHandler) {
2848        let current_epoch = vote_state1.current_epoch();
2849
2850        let bad_votes: VecDeque<Lockout> = vec![
2851            Lockout::new_with_confirmation_count(0, 1),
2852            Lockout::new_with_confirmation_count(1, 2),
2853        ]
2854        .into_iter()
2855        .collect();
2856        assert_eq!(
2857            process_new_vote_state_from_lockouts(
2858                &mut vote_state1,
2859                bad_votes,
2860                None,
2861                None,
2862                current_epoch,
2863            ),
2864            Err(VoteError::ConfirmationsNotOrdered)
2865        );
2866
2867        let bad_votes: VecDeque<Lockout> = vec![
2868            Lockout::new_with_confirmation_count(0, 1),
2869            Lockout::new_with_confirmation_count(1, 1),
2870        ]
2871        .into_iter()
2872        .collect();
2873        assert_eq!(
2874            process_new_vote_state_from_lockouts(
2875                &mut vote_state1,
2876                bad_votes,
2877                None,
2878                None,
2879                current_epoch,
2880            ),
2881            Err(VoteError::ConfirmationsNotOrdered)
2882        );
2883    }
2884
2885    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2886    fn test_process_new_vote_state_new_vote_state_lockout_mismatch(
2887        mut vote_state1: VoteStateHandler,
2888    ) {
2889        let current_epoch = vote_state1.current_epoch();
2890
2891        let bad_votes: VecDeque<Lockout> = vec![
2892            Lockout::new_with_confirmation_count(0, 2),
2893            Lockout::new_with_confirmation_count(7, 1),
2894        ]
2895        .into_iter()
2896        .collect();
2897
2898        // Slot 7 should have expired slot 0
2899        assert_eq!(
2900            process_new_vote_state_from_lockouts(
2901                &mut vote_state1,
2902                bad_votes,
2903                None,
2904                None,
2905                current_epoch,
2906            ),
2907            Err(VoteError::NewVoteStateLockoutMismatch)
2908        );
2909    }
2910
2911    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2912    fn test_process_new_vote_state_confirmation_rollback(mut vote_state1: VoteStateHandler) {
2913        let current_epoch = vote_state1.current_epoch();
2914        let votes: VecDeque<Lockout> = vec![
2915            Lockout::new_with_confirmation_count(0, 4),
2916            Lockout::new_with_confirmation_count(1, 3),
2917        ]
2918        .into_iter()
2919        .collect();
2920        process_new_vote_state_from_lockouts(&mut vote_state1, votes, None, None, current_epoch)
2921            .unwrap();
2922
2923        let votes: VecDeque<Lockout> = vec![
2924            Lockout::new_with_confirmation_count(0, 4),
2925            // Confirmation count lowered illegally
2926            Lockout::new_with_confirmation_count(1, 2),
2927            Lockout::new_with_confirmation_count(2, 1),
2928        ]
2929        .into_iter()
2930        .collect();
2931        // Should error because newer vote state should not have lower confirmation the same slot
2932        // 1
2933        assert_eq!(
2934            process_new_vote_state_from_lockouts(
2935                &mut vote_state1,
2936                votes,
2937                None,
2938                None,
2939                current_epoch,
2940            ),
2941            Err(VoteError::ConfirmationRollBack)
2942        );
2943    }
2944
2945    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2946    fn test_process_new_vote_state_root_progress(mut vote_state1: VoteStateHandler) {
2947        for i in 0..MAX_LOCKOUT_HISTORY {
2948            process_slot_vote_unchecked(&mut vote_state1, i as u64);
2949        }
2950
2951        assert!(vote_state1.root_slot().is_none());
2952        let mut vote_state2 = vote_state1.clone();
2953
2954        // 1) Try to update `vote_state1` with no root,
2955        // to `vote_state2`, which has a new root, should succeed.
2956        //
2957        // 2) Then try to update`vote_state1` with an existing root,
2958        // to `vote_state2`, which has a newer root, which
2959        // should succeed.
2960        for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
2961            process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
2962            assert_ne!(vote_state1.root_slot(), vote_state2.root_slot());
2963
2964            process_new_vote_state(
2965                &mut vote_state1,
2966                vote_state2.votes().clone(),
2967                vote_state2.root_slot(),
2968                None,
2969                vote_state2.current_epoch(),
2970                0,
2971            )
2972            .unwrap();
2973
2974            assert_eq!(vote_state1, vote_state2);
2975        }
2976    }
2977
2978    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2979    fn test_process_new_vote_state_same_slot_but_not_common_ancestor(
2980        initial_vote_state: VoteStateHandler,
2981    ) {
2982        // It might be possible that during the switch from old vote instructions
2983        // to new vote instructions, new_state contains votes for slots LESS
2984        // than the current state, for instance:
2985        //
2986        // Current on-chain state: 1, 5
2987        // New state: 1, 2 (lockout: 4), 3, 5, 7
2988        //
2989        // Imagine the validator made two of these votes:
2990        // 1) The first vote {1, 2, 3} didn't land in the old state, but didn't
2991        // land on chain
2992        // 2) A second vote {1, 2, 5} was then submitted, which landed
2993        //
2994        //
2995        // 2 is not popped off in the local tower because 3 doubled the lockout.
2996        // However, 3 did not land in the on-chain state, so the vote {1, 2, 6}
2997        // will immediately pop off 2.
2998
2999        // Construct on-chain vote state
3000        let mut vote_state1 = initial_vote_state.clone();
3001        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
3002        assert_eq!(
3003            vote_state1
3004                .votes()
3005                .iter()
3006                .map(|vote| vote.slot())
3007                .collect::<Vec<Slot>>(),
3008            vec![1, 5]
3009        );
3010
3011        // Construct local tower state
3012        let mut vote_state2 = initial_vote_state;
3013        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
3014        assert_eq!(
3015            vote_state2
3016                .votes()
3017                .iter()
3018                .map(|vote| vote.slot())
3019                .collect::<Vec<Slot>>(),
3020            vec![1, 2, 3, 5, 7]
3021        );
3022
3023        // See that on-chain vote state can update properly
3024        process_new_vote_state(
3025            &mut vote_state1,
3026            vote_state2.votes().clone(),
3027            vote_state2.root_slot(),
3028            None,
3029            vote_state2.current_epoch(),
3030            0,
3031        )
3032        .unwrap();
3033
3034        assert_eq!(vote_state1, vote_state2);
3035    }
3036
3037    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3038    fn test_process_new_vote_state_lockout_violation(initial_vote_state: VoteStateHandler) {
3039        // Construct on-chain vote state
3040        let mut vote_state1 = initial_vote_state.clone();
3041        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
3042        assert_eq!(
3043            vote_state1
3044                .votes()
3045                .iter()
3046                .map(|vote| vote.slot())
3047                .collect::<Vec<Slot>>(),
3048            vec![1, 2, 4, 5]
3049        );
3050
3051        // Construct conflicting tower state. Vote 4 is missing,
3052        // but 5 should not have popped off vote 4.
3053        let mut vote_state2 = initial_vote_state;
3054        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
3055        assert_eq!(
3056            vote_state2
3057                .votes()
3058                .iter()
3059                .map(|vote| vote.slot())
3060                .collect::<Vec<Slot>>(),
3061            vec![1, 2, 3, 5, 7]
3062        );
3063
3064        // See that on-chain vote state can update properly
3065        assert_eq!(
3066            process_new_vote_state(
3067                &mut vote_state1,
3068                vote_state2.votes().clone(),
3069                vote_state2.root_slot(),
3070                None,
3071                vote_state2.current_epoch(),
3072                0,
3073            ),
3074            Err(VoteError::LockoutConflict)
3075        );
3076    }
3077
3078    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3079    fn test_process_new_vote_state_lockout_violation2(initial_vote_state: VoteStateHandler) {
3080        // Construct on-chain vote state
3081        let mut vote_state1 = initial_vote_state.clone();
3082        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
3083        assert_eq!(
3084            vote_state1
3085                .votes()
3086                .iter()
3087                .map(|vote| vote.slot())
3088                .collect::<Vec<Slot>>(),
3089            vec![1, 5, 6, 7]
3090        );
3091
3092        // Construct a new vote state. Violates on-chain state because 8
3093        // should not have popped off 7
3094        let mut vote_state2 = initial_vote_state;
3095        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
3096        assert_eq!(
3097            vote_state2
3098                .votes()
3099                .iter()
3100                .map(|vote| vote.slot())
3101                .collect::<Vec<Slot>>(),
3102            vec![1, 2, 3, 5, 6, 8]
3103        );
3104
3105        // Both vote states contain `5`, but `5` is not part of the common prefix
3106        // of both vote states. However, the violation should still be detected.
3107        assert_eq!(
3108            process_new_vote_state(
3109                &mut vote_state1,
3110                vote_state2.votes().clone(),
3111                vote_state2.root_slot(),
3112                None,
3113                vote_state2.current_epoch(),
3114                0,
3115            ),
3116            Err(VoteError::LockoutConflict)
3117        );
3118    }
3119
3120    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3121    fn test_process_new_vote_state_expired_ancestor_not_removed(mut vote_state1: VoteStateHandler) {
3122        // Construct on-chain vote state
3123        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
3124        assert_eq!(
3125            vote_state1
3126                .votes()
3127                .iter()
3128                .map(|vote| vote.slot())
3129                .collect::<Vec<Slot>>(),
3130            vec![1, 9]
3131        );
3132
3133        // Example: {1: lockout 8, 9: lockout 2}, vote on 10 will not pop off 1
3134        // because 9 is not popped off yet
3135        let mut vote_state2 = vote_state1.clone();
3136        process_slot_vote_unchecked(&mut vote_state2, 10);
3137
3138        // Slot 1 has been expired by 10, but is kept alive by its descendant
3139        // 9 which has not been expired yet.
3140        assert_eq!(vote_state2.votes()[0].slot(), 1);
3141        assert_eq!(vote_state2.votes()[0].lockout.last_locked_out_slot(), 9);
3142        assert_eq!(
3143            vote_state2
3144                .votes()
3145                .iter()
3146                .map(|vote| vote.slot())
3147                .collect::<Vec<Slot>>(),
3148            vec![1, 9, 10]
3149        );
3150
3151        // Should be able to update vote_state1
3152        process_new_vote_state(
3153            &mut vote_state1,
3154            vote_state2.votes().clone(),
3155            vote_state2.root_slot(),
3156            None,
3157            vote_state2.current_epoch(),
3158            0,
3159        )
3160        .unwrap();
3161        assert_eq!(vote_state1, vote_state2,);
3162    }
3163
3164    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3165    fn test_process_new_vote_current_state_contains_bigger_slots(
3166        mut vote_state1: VoteStateHandler,
3167    ) {
3168        process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
3169        assert_eq!(
3170            vote_state1
3171                .votes()
3172                .iter()
3173                .map(|vote| vote.slot())
3174                .collect::<Vec<Slot>>(),
3175            vec![6, 7, 8]
3176        );
3177
3178        // Try to process something with lockout violations
3179        let bad_votes: VecDeque<Lockout> = vec![
3180            Lockout::new_with_confirmation_count(2, 5),
3181            // Slot 14 could not have popped off slot 6 yet
3182            Lockout::new_with_confirmation_count(14, 1),
3183        ]
3184        .into_iter()
3185        .collect();
3186        let root = Some(1);
3187
3188        let current_epoch = vote_state1.current_epoch();
3189        assert_eq!(
3190            process_new_vote_state_from_lockouts(
3191                &mut vote_state1,
3192                bad_votes,
3193                root,
3194                None,
3195                current_epoch,
3196            ),
3197            Err(VoteError::LockoutConflict)
3198        );
3199
3200        let good_votes: VecDeque<LandedVote> = vec![
3201            Lockout::new_with_confirmation_count(2, 5).into(),
3202            Lockout::new_with_confirmation_count(15, 1).into(),
3203        ]
3204        .into_iter()
3205        .collect();
3206
3207        let current_epoch = vote_state1.current_epoch();
3208        process_new_vote_state(
3209            &mut vote_state1,
3210            good_votes.clone(),
3211            root,
3212            None,
3213            current_epoch,
3214            0,
3215        )
3216        .unwrap();
3217        assert_eq!(*vote_state1.votes(), good_votes);
3218    }
3219
3220    #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3221    fn test_filter_old_votes(mut vote_state: VoteStateHandler) {
3222        let old_vote_slot = 1;
3223        let vote = Vote::new(vec![old_vote_slot], Hash::default());
3224
3225        // Vote with all slots that are all older than the SlotHashes history should
3226        // error with `VotesTooOldAllFiltered`
3227        let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
3228        assert_eq!(
3229            process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
3230            Err(VoteError::VotesTooOldAllFiltered)
3231        );
3232
3233        // Vote with only some slots older than the SlotHashes history should
3234        // filter out those older slots
3235        let vote_slot = 2;
3236        let vote_slot_hash = slot_hashes
3237            .iter()
3238            .find(|(slot, _hash)| *slot == vote_slot)
3239            .unwrap()
3240            .1;
3241
3242        let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
3243        process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap();
3244        assert_eq!(
3245            vote_state
3246                .votes()
3247                .iter()
3248                .map(|vote| vote.lockout)
3249                .collect::<Vec<Lockout>>(),
3250            vec![Lockout::new_with_confirmation_count(vote_slot, 1)]
3251        );
3252    }
3253
3254    fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
3255        slots
3256            .iter()
3257            .rev()
3258            .map(|x| (*x, Hash::new_unique()))
3259            .collect()
3260    }
3261
3262    fn build_vote_state(
3263        target_version: VoteStateTargetVersion,
3264        vote_slots: Vec<Slot>,
3265        slot_hashes: &[(Slot, Hash)],
3266    ) -> VoteStateHandler {
3267        let mut vote_state = match target_version {
3268            VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
3269        };
3270
3271        if !vote_slots.is_empty() {
3272            let vote_hash = slot_hashes
3273                .iter()
3274                .find(|(slot, _hash)| slot == vote_slots.last().unwrap())
3275                .unwrap()
3276                .1;
3277            let vote = Vote::new(vote_slots, vote_hash);
3278            process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0)
3279                .unwrap();
3280        }
3281
3282        vote_state
3283    }
3284
3285    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3286    fn test_check_and_filter_proposed_vote_state_empty(target_version: VoteStateTargetVersion) {
3287        let empty_slot_hashes = build_slot_hashes(vec![]);
3288        let empty_vote_state = build_vote_state(target_version, vec![], &empty_slot_hashes);
3289
3290        // Test with empty TowerSync, should return EmptySlots error
3291        let mut tower_sync = TowerSync::from(vec![]);
3292        assert_eq!(
3293            check_and_filter_proposed_vote_state(
3294                &empty_vote_state,
3295                &mut tower_sync.lockouts,
3296                &mut tower_sync.root,
3297                tower_sync.hash,
3298                &empty_slot_hashes
3299            ),
3300            Err(VoteError::EmptySlots),
3301        );
3302
3303        // Test with non-empty TowerSync, should return SlotsMismatch since nothing exists in SlotHashes
3304        let mut tower_sync = TowerSync::from(vec![(0, 1)]);
3305        assert_eq!(
3306            check_and_filter_proposed_vote_state(
3307                &empty_vote_state,
3308                &mut tower_sync.lockouts,
3309                &mut tower_sync.root,
3310                tower_sync.hash,
3311                &empty_slot_hashes
3312            ),
3313            Err(VoteError::SlotsMismatch),
3314        );
3315    }
3316
3317    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3318    fn test_check_and_filter_proposed_vote_state_too_old(target_version: VoteStateTargetVersion) {
3319        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3320        let latest_vote = 4;
3321        let vote_state = build_vote_state(target_version, vec![1, 2, 3, latest_vote], &slot_hashes);
3322
3323        // Test with a vote for a slot less than the latest vote in the vote_state,
3324        // should return error `VoteTooOld`
3325        let mut tower_sync = TowerSync::from(vec![(latest_vote, 1)]);
3326        assert_eq!(
3327            check_and_filter_proposed_vote_state(
3328                &vote_state,
3329                &mut tower_sync.lockouts,
3330                &mut tower_sync.root,
3331                tower_sync.hash,
3332                &slot_hashes
3333            ),
3334            Err(VoteError::VoteTooOld),
3335        );
3336
3337        // Test with a vote state update where the latest slot `X` in the update is
3338        // 1) Less than the earliest slot in slot_hashes history, AND
3339        // 2) `X` > latest_vote
3340        let earliest_slot_in_history = latest_vote + 2;
3341        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
3342        let mut tower_sync = TowerSync::from(vec![(earliest_slot_in_history - 1, 1)]);
3343        assert_eq!(
3344            check_and_filter_proposed_vote_state(
3345                &vote_state,
3346                &mut tower_sync.lockouts,
3347                &mut tower_sync.root,
3348                tower_sync.hash,
3349                &slot_hashes
3350            ),
3351            Err(VoteError::VoteTooOld),
3352        );
3353    }
3354
3355    fn run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3356        target_version: VoteStateTargetVersion,
3357        earliest_slot_in_history: Slot,
3358        current_vote_state_slots: Vec<Slot>,
3359        current_vote_state_root: Option<Slot>,
3360        proposed_slots_and_lockouts: Vec<(Slot, u32)>,
3361        proposed_root: Slot,
3362        expected_root: Option<Slot>,
3363        expected_vote_state: Vec<Lockout>,
3364    ) {
3365        assert!(proposed_root < earliest_slot_in_history);
3366        assert_eq!(
3367            expected_root,
3368            current_vote_state_slots
3369                .iter()
3370                .rev()
3371                .find(|slot| **slot <= proposed_root)
3372                .cloned()
3373        );
3374        let latest_slot_in_history = proposed_slots_and_lockouts
3375            .last()
3376            .unwrap()
3377            .0
3378            .max(earliest_slot_in_history);
3379        let mut slot_hashes = build_slot_hashes(
3380            (current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
3381                .collect::<Vec<Slot>>(),
3382        );
3383
3384        let mut vote_state =
3385            build_vote_state(target_version, current_vote_state_slots, &slot_hashes);
3386        vote_state.set_root_slot(current_vote_state_root);
3387
3388        slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
3389        assert!(!proposed_slots_and_lockouts.is_empty());
3390        let proposed_hash = slot_hashes
3391            .iter()
3392            .find(|(slot, _hash)| *slot == proposed_slots_and_lockouts.last().unwrap().0)
3393            .unwrap()
3394            .1;
3395
3396        // Test with a `TowerSync` where the root is less than `earliest_slot_in_history`.
3397        // Root slot in the `TowerSync` should be updated to match the root slot in the
3398        // current vote state
3399        let mut tower_sync = TowerSync::from(proposed_slots_and_lockouts);
3400        tower_sync.hash = proposed_hash;
3401        tower_sync.root = Some(proposed_root);
3402        check_and_filter_proposed_vote_state(
3403            &vote_state,
3404            &mut tower_sync.lockouts,
3405            &mut tower_sync.root,
3406            tower_sync.hash,
3407            &slot_hashes,
3408        )
3409        .unwrap();
3410        assert_eq!(tower_sync.root, expected_root);
3411
3412        // The proposed root slot should become the biggest slot in the current vote state less than
3413        // `earliest_slot_in_history`.
3414        assert!(
3415            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync.clone(),).is_ok()
3416        );
3417        assert_eq!(vote_state.root_slot(), expected_root);
3418        assert_eq!(
3419            vote_state
3420                .votes()
3421                .iter()
3422                .map(|vote| vote.lockout)
3423                .collect::<Vec<Lockout>>(),
3424            expected_vote_state,
3425        );
3426    }
3427
3428    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3429    fn test_check_and_filter_proposed_vote_state_older_than_history_root(
3430        target_version: VoteStateTargetVersion,
3431    ) {
3432        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3433        // slot
3434        let earliest_slot_in_history = 5;
3435        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3436        let current_vote_state_root = None;
3437        let proposed_slots_and_lockouts = vec![(5, 1)];
3438        let proposed_root = 4;
3439        let expected_root = Some(4);
3440        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3441        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3442            target_version,
3443            earliest_slot_in_history,
3444            current_vote_state_slots,
3445            current_vote_state_root,
3446            proposed_slots_and_lockouts,
3447            proposed_root,
3448            expected_root,
3449            expected_vote_state,
3450        );
3451
3452        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3453        // slot and the `current_vote_state_root.is_some()`.
3454        let earliest_slot_in_history = 5;
3455        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3456        let current_vote_state_root = Some(0);
3457        let proposed_slots_and_lockouts = vec![(5, 1)];
3458        let proposed_root = 4;
3459        let expected_root = Some(4);
3460        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3461        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3462            target_version,
3463            earliest_slot_in_history,
3464            current_vote_state_slots,
3465            current_vote_state_root,
3466            proposed_slots_and_lockouts,
3467            proposed_root,
3468            expected_root,
3469            expected_vote_state,
3470        );
3471
3472        // Test when `proposed_root` is in `current_vote_state_slots` but it's not the latest
3473        // slot
3474        let earliest_slot_in_history = 5;
3475        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3476        let current_vote_state_root = Some(0);
3477        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3478        let proposed_root = 3;
3479        let expected_root = Some(3);
3480        let expected_vote_state = vec![
3481            Lockout::new_with_confirmation_count(4, 2),
3482            Lockout::new_with_confirmation_count(5, 1),
3483        ];
3484        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3485            target_version,
3486            earliest_slot_in_history,
3487            current_vote_state_slots,
3488            current_vote_state_root,
3489            proposed_slots_and_lockouts,
3490            proposed_root,
3491            expected_root,
3492            expected_vote_state,
3493        );
3494
3495        // Test when `proposed_root` is not in `current_vote_state_slots`
3496        let earliest_slot_in_history = 5;
3497        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
3498        let current_vote_state_root = Some(0);
3499        let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3500        let proposed_root = 3;
3501        let expected_root = Some(2);
3502        let expected_vote_state = vec![
3503            Lockout::new_with_confirmation_count(4, 2),
3504            Lockout::new_with_confirmation_count(5, 1),
3505        ];
3506        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3507            target_version,
3508            earliest_slot_in_history,
3509            current_vote_state_slots,
3510            current_vote_state_root,
3511            proposed_slots_and_lockouts,
3512            proposed_root,
3513            expected_root,
3514            expected_vote_state,
3515        );
3516
3517        // Test when the `proposed_root` is smaller than all the slots in
3518        // `current_vote_state_slots`, no roots should be set.
3519        let earliest_slot_in_history = 4;
3520        let current_vote_state_slots: Vec<Slot> = vec![3, 4];
3521        let current_vote_state_root = None;
3522        let proposed_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
3523        let proposed_root = 2;
3524        let expected_root = None;
3525        let expected_vote_state = vec![
3526            Lockout::new_with_confirmation_count(3, 3),
3527            Lockout::new_with_confirmation_count(4, 2),
3528            Lockout::new_with_confirmation_count(5, 1),
3529        ];
3530        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3531            target_version,
3532            earliest_slot_in_history,
3533            current_vote_state_slots,
3534            current_vote_state_root,
3535            proposed_slots_and_lockouts,
3536            proposed_root,
3537            expected_root,
3538            expected_vote_state,
3539        );
3540
3541        // Test when `current_vote_state_slots` is empty, no roots should be set
3542        let earliest_slot_in_history = 4;
3543        let current_vote_state_slots: Vec<Slot> = vec![];
3544        let current_vote_state_root = None;
3545        let proposed_slots_and_lockouts = vec![(5, 1)];
3546        let proposed_root = 2;
3547        let expected_root = None;
3548        let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3549        run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3550            target_version,
3551            earliest_slot_in_history,
3552            current_vote_state_slots,
3553            current_vote_state_root,
3554            proposed_slots_and_lockouts,
3555            proposed_root,
3556            expected_root,
3557            expected_vote_state,
3558        );
3559    }
3560
3561    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3562    fn test_check_and_filter_proposed_vote_state_slots_not_ordered(
3563        target_version: VoteStateTargetVersion,
3564    ) {
3565        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3566        let vote_state = build_vote_state(target_version, vec![1], &slot_hashes);
3567
3568        // Test with a `TowerSync` where the slots are out of order
3569        let vote_slot = 3;
3570        let vote_slot_hash = slot_hashes
3571            .iter()
3572            .find(|(slot, _hash)| *slot == vote_slot)
3573            .unwrap()
3574            .1;
3575        let mut tower_sync = TowerSync::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
3576        tower_sync.hash = vote_slot_hash;
3577        assert_eq!(
3578            check_and_filter_proposed_vote_state(
3579                &vote_state,
3580                &mut tower_sync.lockouts,
3581                &mut tower_sync.root,
3582                tower_sync.hash,
3583                &slot_hashes
3584            ),
3585            Err(VoteError::SlotsNotOrdered),
3586        );
3587
3588        // Test with a `TowerSync` where there are multiples of the same slot
3589        let mut tower_sync = TowerSync::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
3590        tower_sync.hash = vote_slot_hash;
3591        assert_eq!(
3592            check_and_filter_proposed_vote_state(
3593                &vote_state,
3594                &mut tower_sync.lockouts,
3595                &mut tower_sync.root,
3596                tower_sync.hash,
3597                &slot_hashes
3598            ),
3599            Err(VoteError::SlotsNotOrdered),
3600        );
3601    }
3602
3603    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3604    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered(
3605        target_version: VoteStateTargetVersion,
3606    ) {
3607        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3608        let mut vote_state = build_vote_state(target_version, vec![1, 2, 3, 4], &slot_hashes);
3609
3610        // Test with a `TowerSync` where there:
3611        // 1) Exists a slot less than `earliest_slot_in_history`
3612        // 2) This slot does not exist in the vote state already
3613        // This slot should be filtered out
3614        let earliest_slot_in_history = 11;
3615        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3616        let vote_slot = 12;
3617        let vote_slot_hash = slot_hashes
3618            .iter()
3619            .find(|(slot, _hash)| *slot == vote_slot)
3620            .unwrap()
3621            .1;
3622        let missing_older_than_history_slot = earliest_slot_in_history - 1;
3623        let mut tower_sync = TowerSync::from(vec![
3624            (1, 4),
3625            (missing_older_than_history_slot, 2),
3626            (vote_slot, 3),
3627        ]);
3628        tower_sync.hash = vote_slot_hash;
3629        check_and_filter_proposed_vote_state(
3630            &vote_state,
3631            &mut tower_sync.lockouts,
3632            &mut tower_sync.root,
3633            tower_sync.hash,
3634            &slot_hashes,
3635        )
3636        .unwrap();
3637
3638        // Check the earlier slot was filtered out
3639        assert_eq!(
3640            tower_sync
3641                .clone()
3642                .lockouts
3643                .into_iter()
3644                .collect::<Vec<Lockout>>(),
3645            vec![
3646                Lockout::new_with_confirmation_count(1, 4),
3647                Lockout::new_with_confirmation_count(vote_slot, 3)
3648            ]
3649        );
3650        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3651    }
3652
3653    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3654    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_not_filtered(
3655        target_version: VoteStateTargetVersion,
3656    ) {
3657        let slot_hashes = build_slot_hashes(vec![4]);
3658        let mut vote_state = build_vote_state(target_version, vec![4], &slot_hashes);
3659
3660        // Test with a `TowerSync` where there:
3661        // 1) Exists a slot less than `earliest_slot_in_history`
3662        // 2) This slot exists in the vote state already
3663        // This slot should *NOT* be filtered out
3664        let earliest_slot_in_history = 11;
3665        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3666        let vote_slot = 12;
3667        let vote_slot_hash = slot_hashes
3668            .iter()
3669            .find(|(slot, _hash)| *slot == vote_slot)
3670            .unwrap()
3671            .1;
3672        let existing_older_than_history_slot = 4;
3673        let mut tower_sync =
3674            TowerSync::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
3675        tower_sync.hash = vote_slot_hash;
3676        check_and_filter_proposed_vote_state(
3677            &vote_state,
3678            &mut tower_sync.lockouts,
3679            &mut tower_sync.root,
3680            tower_sync.hash,
3681            &slot_hashes,
3682        )
3683        .unwrap();
3684        // Check the earlier slot was *NOT* filtered out
3685        assert_eq!(tower_sync.lockouts.len(), 2);
3686        assert_eq!(
3687            tower_sync
3688                .clone()
3689                .lockouts
3690                .into_iter()
3691                .collect::<Vec<Lockout>>(),
3692            vec![
3693                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3694                Lockout::new_with_confirmation_count(vote_slot, 2)
3695            ]
3696        );
3697        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3698    }
3699
3700    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3701    fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered_and_not_filtered(
3702        target_version: VoteStateTargetVersion,
3703    ) {
3704        let slot_hashes = build_slot_hashes(vec![6]);
3705        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3706
3707        // Test with a `TowerSync` where there exists both a slot:
3708        // 1) Less than `earliest_slot_in_history`
3709        // 2) This slot exists in the vote state already
3710        // which should not be filtered
3711        //
3712        // AND a slot that
3713        //
3714        // 1) Less than `earliest_slot_in_history`
3715        // 2) This slot does not exist in the vote state already
3716        // which should be filtered
3717        let earliest_slot_in_history = 11;
3718        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3719        let vote_slot = 14;
3720        let vote_slot_hash = slot_hashes
3721            .iter()
3722            .find(|(slot, _hash)| *slot == vote_slot)
3723            .unwrap()
3724            .1;
3725
3726        let missing_older_than_history_slot = 4;
3727        let existing_older_than_history_slot = 6;
3728
3729        let mut tower_sync = TowerSync::from(vec![
3730            (missing_older_than_history_slot, 4),
3731            (existing_older_than_history_slot, 3),
3732            (12, 2),
3733            (vote_slot, 1),
3734        ]);
3735        tower_sync.hash = vote_slot_hash;
3736        check_and_filter_proposed_vote_state(
3737            &vote_state,
3738            &mut tower_sync.lockouts,
3739            &mut tower_sync.root,
3740            tower_sync.hash,
3741            &slot_hashes,
3742        )
3743        .unwrap();
3744        assert_eq!(tower_sync.lockouts.len(), 3);
3745        assert_eq!(
3746            tower_sync
3747                .clone()
3748                .lockouts
3749                .into_iter()
3750                .collect::<Vec<Lockout>>(),
3751            vec![
3752                Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3753                Lockout::new_with_confirmation_count(12, 2),
3754                Lockout::new_with_confirmation_count(vote_slot, 1)
3755            ]
3756        );
3757        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3758    }
3759
3760    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3761    fn test_check_and_filter_proposed_vote_state_slot_not_on_fork(
3762        target_version: VoteStateTargetVersion,
3763    ) {
3764        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3765        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3766
3767        // Test with a `TowerSync` where there:
3768        // 1) Exists a slot not in the slot hashes history
3769        // 2) The slot is greater than the earliest slot in the history
3770        // Thus this slot is not part of the fork and the update should be rejected
3771        // with error `SlotsMismatch`
3772        let missing_vote_slot = 3;
3773
3774        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3775        // errors
3776        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3777        let vote_slot_hash = slot_hashes
3778            .iter()
3779            .find(|(slot, _hash)| *slot == vote_slot)
3780            .unwrap()
3781            .1;
3782        let mut tower_sync = TowerSync::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
3783        tower_sync.hash = vote_slot_hash;
3784        assert_eq!(
3785            check_and_filter_proposed_vote_state(
3786                &vote_state,
3787                &mut tower_sync.lockouts,
3788                &mut tower_sync.root,
3789                tower_sync.hash,
3790                &slot_hashes
3791            ),
3792            Err(VoteError::SlotsMismatch),
3793        );
3794
3795        // Test where some earlier vote slots exist in the history, but others don't
3796        let missing_vote_slot = 7;
3797        let mut tower_sync = TowerSync::from(vec![
3798            (2, 5),
3799            (4, 4),
3800            (6, 3),
3801            (missing_vote_slot, 2),
3802            (vote_slot, 1),
3803        ]);
3804        tower_sync.hash = vote_slot_hash;
3805        assert_eq!(
3806            check_and_filter_proposed_vote_state(
3807                &vote_state,
3808                &mut tower_sync.lockouts,
3809                &mut tower_sync.root,
3810                tower_sync.hash,
3811                &slot_hashes
3812            ),
3813            Err(VoteError::SlotsMismatch),
3814        );
3815    }
3816
3817    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3818    fn test_check_and_filter_proposed_vote_state_root_on_different_fork(
3819        target_version: VoteStateTargetVersion,
3820    ) {
3821        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3822        let vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3823
3824        // Test with a `TowerSync` where:
3825        // 1) The root is not present in slot hashes history
3826        // 2) The slot is greater than the earliest slot in the history
3827        // Thus this slot is not part of the fork and the update should be rejected
3828        // with error `RootOnDifferentFork`
3829        let new_root = 3;
3830
3831        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3832        // errors, but also this slot must be present in SlotHashes
3833        let vote_slot = 8;
3834        assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
3835        let vote_slot_hash = slot_hashes
3836            .iter()
3837            .find(|(slot, _hash)| *slot == vote_slot)
3838            .unwrap()
3839            .1;
3840        let mut tower_sync = TowerSync::from(vec![(vote_slot, 1)]);
3841        tower_sync.hash = vote_slot_hash;
3842        tower_sync.root = Some(new_root);
3843        assert_eq!(
3844            check_and_filter_proposed_vote_state(
3845                &vote_state,
3846                &mut tower_sync.lockouts,
3847                &mut tower_sync.root,
3848                tower_sync.hash,
3849                &slot_hashes
3850            ),
3851            Err(VoteError::RootOnDifferentFork),
3852        );
3853    }
3854
3855    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3856    fn test_check_and_filter_proposed_vote_state_slot_newer_than_slot_history(
3857        target_version: VoteStateTargetVersion,
3858    ) {
3859        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3860        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3861
3862        // Test with a `TowerSync` where there:
3863        // 1) The last slot in the update is a slot not in the slot hashes history
3864        // 2) The slot is greater than the newest slot in the slot history
3865        // Thus this slot is not part of the fork and the update should be rejected
3866        // with error `SlotsMismatch`
3867        let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
3868        let vote_slot_hash = Hash::new_unique();
3869        let mut tower_sync = TowerSync::from(vec![(8, 2), (missing_vote_slot, 3)]);
3870        tower_sync.hash = vote_slot_hash;
3871        assert_eq!(
3872            check_and_filter_proposed_vote_state(
3873                &vote_state,
3874                &mut tower_sync.lockouts,
3875                &mut tower_sync.root,
3876                tower_sync.hash,
3877                &slot_hashes
3878            ),
3879            Err(VoteError::SlotsMismatch),
3880        );
3881    }
3882
3883    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3884    fn test_check_and_filter_proposed_vote_state_slot_all_slot_hashes_in_update_ok(
3885        target_version: VoteStateTargetVersion,
3886    ) {
3887        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3888        let mut vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3889
3890        // Test with a `TowerSync` where every slot in the history is
3891        // in the update
3892
3893        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3894        // errors
3895        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3896        let vote_slot_hash = slot_hashes
3897            .iter()
3898            .find(|(slot, _hash)| *slot == vote_slot)
3899            .unwrap()
3900            .1;
3901        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3902        tower_sync.hash = vote_slot_hash;
3903        check_and_filter_proposed_vote_state(
3904            &vote_state,
3905            &mut tower_sync.lockouts,
3906            &mut tower_sync.root,
3907            tower_sync.hash,
3908            &slot_hashes,
3909        )
3910        .unwrap();
3911
3912        // Nothing in the update should have been filtered out
3913        assert_eq!(
3914            tower_sync
3915                .clone()
3916                .lockouts
3917                .into_iter()
3918                .collect::<Vec<Lockout>>(),
3919            vec![
3920                Lockout::new_with_confirmation_count(2, 4),
3921                Lockout::new_with_confirmation_count(4, 3),
3922                Lockout::new_with_confirmation_count(6, 2),
3923                Lockout::new_with_confirmation_count(vote_slot, 1)
3924            ]
3925        );
3926
3927        assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3928    }
3929
3930    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3931    fn test_check_and_filter_proposed_vote_state_slot_some_slot_hashes_in_update_ok(
3932        target_version: VoteStateTargetVersion,
3933    ) {
3934        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3935        let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3936
3937        // Test with a `TowerSync` where only some slots in the history are
3938        // in the update, and others slots in the history are missing.
3939
3940        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3941        // errors
3942        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3943        let vote_slot_hash = slot_hashes
3944            .iter()
3945            .find(|(slot, _hash)| *slot == vote_slot)
3946            .unwrap()
3947            .1;
3948        let mut tower_sync = TowerSync::from(vec![(4, 2), (vote_slot, 1)]);
3949        tower_sync.hash = vote_slot_hash;
3950        check_and_filter_proposed_vote_state(
3951            &vote_state,
3952            &mut tower_sync.lockouts,
3953            &mut tower_sync.root,
3954            tower_sync.hash,
3955            &slot_hashes,
3956        )
3957        .unwrap();
3958
3959        // Nothing in the update should have been filtered out
3960        assert_eq!(
3961            tower_sync
3962                .clone()
3963                .lockouts
3964                .into_iter()
3965                .collect::<Vec<Lockout>>(),
3966            vec![
3967                Lockout::new_with_confirmation_count(4, 2),
3968                Lockout::new_with_confirmation_count(vote_slot, 1)
3969            ]
3970        );
3971
3972        // Because 6 from the original VoteStateV3
3973        // should not have been popped off in the proposed state,
3974        // we should get a lockout conflict
3975        assert_eq!(
3976            do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,),
3977            Err(VoteError::LockoutConflict)
3978        );
3979    }
3980
3981    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3982    fn test_check_and_filter_proposed_vote_state_slot_hash_mismatch(
3983        target_version: VoteStateTargetVersion,
3984    ) {
3985        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3986        let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3987
3988        // Test with a `TowerSync` where the hash is mismatched
3989
3990        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
3991        // errors
3992        let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3993        let vote_slot_hash = Hash::new_unique();
3994        let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3995        tower_sync.hash = vote_slot_hash;
3996        assert_eq!(
3997            check_and_filter_proposed_vote_state(
3998                &vote_state,
3999                &mut tower_sync.lockouts,
4000                &mut tower_sync.root,
4001                tower_sync.hash,
4002                &slot_hashes,
4003            ),
4004            Err(VoteError::SlotHashMismatch),
4005        );
4006    }
4007
4008    #[test_case(0, true; "first slot")]
4009    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
4010    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
4011    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
4012    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
4013    fn test_epoch_half_check(slot: Slot, expected_allowed: bool) {
4014        let epoch_schedule = EpochSchedule::without_warmup();
4015        assert_eq!(
4016            is_commission_update_allowed(slot, &epoch_schedule),
4017            expected_allowed
4018        );
4019    }
4020
4021    #[test]
4022    fn test_warmup_epoch_half_check_with_warmup() {
4023        let epoch_schedule = EpochSchedule::default();
4024        let first_normal_slot = epoch_schedule.first_normal_slot;
4025        // first slot works
4026        assert!(is_commission_update_allowed(0, &epoch_schedule));
4027        // right before first normal slot works, since all warmup slots allow
4028        // commission updates
4029        assert!(is_commission_update_allowed(
4030            first_normal_slot - 1,
4031            &epoch_schedule
4032        ));
4033    }
4034
4035    #[test_case(0, true; "first slot")]
4036    #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
4037    #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
4038    #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
4039    #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
4040    fn test_epoch_half_check_with_warmup(slot: Slot, expected_allowed: bool) {
4041        let epoch_schedule = EpochSchedule::default();
4042        let first_normal_slot = epoch_schedule.first_normal_slot;
4043        assert_eq!(
4044            is_commission_update_allowed(first_normal_slot.saturating_add(slot), &epoch_schedule),
4045            expected_allowed
4046        );
4047    }
4048
4049    #[test]
4050    fn test_create_v4_account_with_authorized() {
4051        let node_pubkey = Pubkey::new_unique();
4052        let authorized_voter = Pubkey::new_unique();
4053        let authorized_withdrawer = Pubkey::new_unique();
4054        let bls_pubkey_compressed = [42; 48];
4055        let inflation_rewards_commission_bps = 10000;
4056        let lamports = 100;
4057        let vote_account = create_v4_account_with_authorized(
4058            &node_pubkey,
4059            &authorized_voter,
4060            bls_pubkey_compressed,
4061            &authorized_withdrawer,
4062            inflation_rewards_commission_bps,
4063            &authorized_withdrawer,
4064            0,
4065            &node_pubkey,
4066            lamports,
4067        );
4068        assert_eq!(vote_account.lamports(), lamports);
4069        assert_eq!(vote_account.owner(), &id());
4070        assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
4071        let vote_state_v4 = VoteStateV4::deserialize(vote_account.data(), &node_pubkey).unwrap();
4072        assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
4073        assert_eq!(
4074            vote_state_v4.authorized_voters,
4075            AuthorizedVoters::new(0, authorized_voter)
4076        );
4077        assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
4078        assert_eq!(
4079            vote_state_v4.bls_pubkey_compressed,
4080            Some(bls_pubkey_compressed)
4081        );
4082        assert_eq!(
4083            vote_state_v4.inflation_rewards_commission_bps,
4084            inflation_rewards_commission_bps
4085        );
4086    }
4087
4088    #[test]
4089    fn test_update_validator_identity_syncs_block_revenue_collector() {
4090        // Feature disabled; block revenue collector should always sync.
4091        let custom_commission_collector_enabled = false;
4092
4093        let vote_state =
4094            vote_state_new_for_test(&solana_pubkey::new_rand(), VoteStateTargetVersion::V4);
4095        let node_pubkey = *vote_state.node_pubkey();
4096        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4097
4098        let serialized = vote_state.serialize();
4099        let serialized_len = serialized.len();
4100        let rent = Rent::default();
4101        let lamports = rent.minimum_balance(serialized_len);
4102        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4103        vote_account.set_data_from_slice(&serialized);
4104
4105        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4106        let mut transaction_context = TransactionContext::new(
4107            vec![(id(), processor_account), (node_pubkey, vote_account)],
4108            rent,
4109            0,
4110            0,
4111            1,
4112        );
4113        transaction_context
4114            .configure_top_level_instruction_for_tests(
4115                0,
4116                vec![InstructionAccount::new(1, false, true)],
4117                vec![],
4118            )
4119            .unwrap();
4120        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4121        let mut borrowed_account = instruction_context
4122            .try_borrow_instruction_account(0)
4123            .unwrap();
4124
4125        let new_node_pubkey = solana_pubkey::new_rand();
4126        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4127            .into_iter()
4128            .collect();
4129
4130        update_validator_identity(
4131            &mut borrowed_account,
4132            VoteStateTargetVersion::V4,
4133            &new_node_pubkey,
4134            &signers,
4135            custom_commission_collector_enabled,
4136        )
4137        .unwrap();
4138
4139        // Both `node_pubkey` and `block_revenue_collector` should be set to
4140        // the new node pubkey.
4141        let vote_state =
4142            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4143        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4144        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
4145
4146        // Run it again.
4147        let new_node_pubkey = solana_pubkey::new_rand();
4148        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4149            .into_iter()
4150            .collect();
4151
4152        update_validator_identity(
4153            &mut borrowed_account,
4154            VoteStateTargetVersion::V4,
4155            &new_node_pubkey,
4156            &signers,
4157            custom_commission_collector_enabled,
4158        )
4159        .unwrap();
4160
4161        let vote_state =
4162            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4163        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4164        assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
4165    }
4166
4167    #[test]
4168    fn test_update_validator_identity_preserves_custom_block_revenue_collector() {
4169        // SIMD-0232 enabled.
4170        //
4171        // Once a validator has set a custom block_revenue_collector, rotating
4172        // the validator identity via UpdateValidatorIdentity must NOT clobber
4173        // the custom collector.
4174        let custom_commission_collector_enabled = true;
4175
4176        let vote_pubkey = solana_pubkey::new_rand();
4177        let mut vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
4178        let node_pubkey = *vote_state.node_pubkey();
4179        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4180
4181        // Seed a custom block_revenue_collector distinct from the node identity.
4182        let custom_collector = solana_pubkey::new_rand();
4183        vote_state.set_block_revenue_collector(custom_collector);
4184
4185        let serialized = vote_state.serialize();
4186        let serialized_len = serialized.len();
4187        let rent = Rent::default();
4188        let lamports = rent.minimum_balance(serialized_len);
4189        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4190        vote_account.set_data_from_slice(&serialized);
4191
4192        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4193        let mut transaction_context = TransactionContext::new(
4194            vec![(id(), processor_account), (node_pubkey, vote_account)],
4195            rent,
4196            0,
4197            0,
4198            1,
4199        );
4200        transaction_context
4201            .configure_top_level_instruction_for_tests(
4202                0,
4203                vec![InstructionAccount::new(1, false, true)],
4204                vec![],
4205            )
4206            .unwrap();
4207        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4208        let mut borrowed_account = instruction_context
4209            .try_borrow_instruction_account(0)
4210            .unwrap();
4211
4212        let new_node_pubkey = solana_pubkey::new_rand();
4213        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4214            .into_iter()
4215            .collect();
4216
4217        update_validator_identity(
4218            &mut borrowed_account,
4219            VoteStateTargetVersion::V4,
4220            &new_node_pubkey,
4221            &signers,
4222            custom_commission_collector_enabled,
4223        )
4224        .unwrap();
4225
4226        // node_pubkey updated, but block_revenue_collector preserved.
4227        let vote_state =
4228            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4229        assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4230        assert_eq!(vote_state.block_revenue_collector, custom_collector);
4231        assert_ne!(vote_state.block_revenue_collector, new_node_pubkey);
4232    }
4233
4234    #[test]
4235    fn test_get_and_update_authorized_voter_v4_with_bls() {
4236        let vote_account_pubkey = Pubkey::new_unique();
4237        let (bls_pubkey, bls_proof_of_possession) =
4238            create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey);
4239        let node_pubkey = Pubkey::new_unique();
4240        let authorized_voter = Pubkey::new_unique();
4241        let authorized_withdrawer = Pubkey::new_unique();
4242        let inflation_rewards_commission_bps = 10000;
4243        let rent = Rent::default();
4244        let lamports = rent.minimum_balance(VoteStateV4::size_of());
4245        // Create a VoteStateV4 account without BLS pubkey
4246        let vote_account = create_v4_account_with_authorized(
4247            &node_pubkey,
4248            &authorized_voter,
4249            [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
4250            &authorized_withdrawer,
4251            inflation_rewards_commission_bps,
4252            &authorized_withdrawer,
4253            0,
4254            &node_pubkey,
4255            lamports,
4256        );
4257        assert_eq!(vote_account.lamports(), lamports);
4258        assert_eq!(vote_account.owner(), &id());
4259        assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
4260
4261        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4262        let mut transaction_context = TransactionContext::new(
4263            vec![
4264                (id(), processor_account),
4265                (vote_account_pubkey, vote_account),
4266            ],
4267            rent,
4268            0,
4269            0,
4270            1,
4271        );
4272        transaction_context
4273            .configure_top_level_instruction_for_tests(
4274                0,
4275                vec![InstructionAccount::new(1, false, true)],
4276                vec![],
4277            )
4278            .unwrap();
4279        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4280        let mut borrowed_account = instruction_context
4281            .try_borrow_instruction_account(0)
4282            .unwrap();
4283
4284        let new_node_pubkey = solana_pubkey::new_rand();
4285        let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_node_pubkey]
4286            .into_iter()
4287            .collect();
4288        let clock = Clock::default();
4289        assert!(
4290            authorize(
4291                &mut borrowed_account,
4292                VoteStateTargetVersion::V4,
4293                &new_node_pubkey,
4294                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4295                    bls_pubkey,
4296                    bls_proof_of_possession
4297                }),
4298                &signers,
4299                &clock,
4300                true,
4301                || Ok(()),
4302            )
4303            .is_ok()
4304        );
4305        let vote_state = VoteStateHandler::new_v4(
4306            VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap(),
4307        );
4308        assert_eq!(
4309            vote_state.as_ref_v4().bls_pubkey_compressed,
4310            Some(bls_pubkey)
4311        );
4312        assert!(vote_state.has_bls_pubkey());
4313
4314        // Test replay attack, can't use someone else's BLS pubkey and PoP
4315        let clock = Clock {
4316            epoch: 3,
4317            ..Clock::default()
4318        };
4319        let (others_bls_pubkey, others_bls_proof_of_possession) =
4320            create_bls_pubkey_and_proof_of_possession(&Pubkey::new_unique());
4321        let new_node_pubkey = solana_pubkey::new_rand();
4322        let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_node_pubkey]
4323            .into_iter()
4324            .collect();
4325        assert_eq!(
4326            authorize(
4327                &mut borrowed_account,
4328                VoteStateTargetVersion::V4,
4329                &new_node_pubkey,
4330                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4331                    bls_pubkey: others_bls_pubkey,
4332                    bls_proof_of_possession: others_bls_proof_of_possession
4333                }),
4334                &signers,
4335                &clock,
4336                true,
4337                || Ok(()),
4338            ),
4339            Err(InstructionError::InvalidArgument),
4340        );
4341
4342        // Test updating to a new BLS pubkey, can only do it in next epoch.
4343        let clock = Clock {
4344            epoch: 5,
4345            ..Clock::default()
4346        };
4347        let (new_bls_pubkey, new_bls_proof_of_possession) =
4348            create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey);
4349        let new_authorized_voter = solana_pubkey::new_rand();
4350        let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_authorized_voter]
4351            .into_iter()
4352            .collect();
4353        assert_eq!(
4354            authorize(
4355                &mut borrowed_account,
4356                VoteStateTargetVersion::V4,
4357                &new_authorized_voter,
4358                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4359                    bls_pubkey: new_bls_pubkey,
4360                    bls_proof_of_possession: new_bls_proof_of_possession
4361                }),
4362                &signers,
4363                &clock,
4364                true,
4365                || Ok(()),
4366            ),
4367            Ok(())
4368        );
4369        let vote_state = VoteStateHandler::new_v4(
4370            VoteStateV4::deserialize(borrowed_account.get_data(), &new_authorized_voter).unwrap(),
4371        );
4372        assert_eq!(
4373            vote_state.as_ref_v4().bls_pubkey_compressed,
4374            Some(new_bls_pubkey)
4375        );
4376        assert!(vote_state.has_bls_pubkey());
4377    }
4378
4379    fn new_transaction_context(
4380        accounts: Vec<(Pubkey, AccountSharedData)>,
4381        instruction_accounts: Vec<InstructionAccount>,
4382        rent: &Rent,
4383    ) -> TransactionContext<'_> {
4384        let mut transaction_context = TransactionContext::new(accounts, rent.clone(), 0, 0, 1);
4385        transaction_context
4386            .configure_top_level_instruction_for_tests(0, instruction_accounts, vec![])
4387            .unwrap();
4388        transaction_context
4389    }
4390
4391    #[test]
4392    fn test_new_commission_collector_validate_and_resolve_key() {
4393        let rent = Rent::default();
4394        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4395        let vote_pubkey = solana_pubkey::new_rand();
4396        let vote_account = AccountSharedData::new(1, 0, &id());
4397        let collector_pubkey = solana_pubkey::new_rand();
4398        let valid_collector =
4399            || AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
4400
4401        // Success: VoteAccount variant returns the vote account's key.
4402        {
4403            let transaction_context = new_transaction_context(
4404                vec![
4405                    (id(), processor_account.clone()),
4406                    (vote_pubkey, vote_account.clone()),
4407                ],
4408                vec![InstructionAccount::new(1, false, true)],
4409                &rent,
4410            );
4411            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4412            let borrowed_vote = instruction_context
4413                .try_borrow_instruction_account(0)
4414                .unwrap();
4415            assert_eq!(
4416                NewCommissionCollector::VoteAccount.validate_and_resolve_key(&borrowed_vote, &rent),
4417                Ok(vote_pubkey),
4418            );
4419        }
4420
4421        // Success: NewAccount (happy) path returns the collector's key.
4422        {
4423            let transaction_context = new_transaction_context(
4424                vec![
4425                    (id(), processor_account.clone()),
4426                    (vote_pubkey, vote_account.clone()),
4427                    (collector_pubkey, valid_collector()),
4428                ],
4429                vec![
4430                    InstructionAccount::new(1, false, true),
4431                    InstructionAccount::new(2, false, true),
4432                ],
4433                &rent,
4434            );
4435            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4436            let borrowed_vote = instruction_context
4437                .try_borrow_instruction_account(0)
4438                .unwrap();
4439            let borrowed_collector = instruction_context
4440                .try_borrow_instruction_account(1)
4441                .unwrap();
4442            assert_eq!(
4443                NewCommissionCollector::NewAccount(borrowed_collector)
4444                    .validate_and_resolve_key(&borrowed_vote, &rent),
4445                Ok(collector_pubkey),
4446            );
4447        }
4448
4449        // Success: Incinerator is an accepted collector (SIMD-0232 explicitly
4450        // allows it — funds sent to the incinerator are burned at end-of-block).
4451        {
4452            // Imagine the incinerator is temporarily holding funds at the time
4453            // of the invocation.
4454            let incinerator_account =
4455                AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
4456            let transaction_context = new_transaction_context(
4457                vec![
4458                    (id(), processor_account.clone()),
4459                    (vote_pubkey, vote_account.clone()),
4460                    (solana_sdk_ids::incinerator::id(), incinerator_account),
4461                ],
4462                vec![
4463                    InstructionAccount::new(1, false, true),
4464                    InstructionAccount::new(2, false, true),
4465                ],
4466                &rent,
4467            );
4468            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4469            let borrowed_vote = instruction_context
4470                .try_borrow_instruction_account(0)
4471                .unwrap();
4472            let borrowed_collector = instruction_context
4473                .try_borrow_instruction_account(1)
4474                .unwrap();
4475            assert_eq!(
4476                NewCommissionCollector::NewAccount(borrowed_collector)
4477                    .validate_and_resolve_key(&borrowed_vote, &rent),
4478                Ok(solana_sdk_ids::incinerator::id()),
4479            );
4480        }
4481
4482        // Fail: Collector account not system-owned.
4483        {
4484            let bad_owner =
4485                AccountSharedData::new(rent.minimum_balance(0), 0, &solana_pubkey::new_rand());
4486            let transaction_context = new_transaction_context(
4487                vec![
4488                    (id(), processor_account.clone()),
4489                    (vote_pubkey, vote_account.clone()),
4490                    (collector_pubkey, bad_owner),
4491                ],
4492                vec![
4493                    InstructionAccount::new(1, false, true),
4494                    InstructionAccount::new(2, false, true),
4495                ],
4496                &rent,
4497            );
4498            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4499            let borrowed_vote = instruction_context
4500                .try_borrow_instruction_account(0)
4501                .unwrap();
4502            let borrowed_collector = instruction_context
4503                .try_borrow_instruction_account(1)
4504                .unwrap();
4505            assert_eq!(
4506                NewCommissionCollector::NewAccount(borrowed_collector)
4507                    .validate_and_resolve_key(&borrowed_vote, &rent),
4508                Err(InstructionError::InvalidAccountOwner),
4509            );
4510        }
4511
4512        // Fail: Collector account not rent-exempt.
4513        {
4514            let underfunded = AccountSharedData::new(0, 0, &system_program::id());
4515            let transaction_context = new_transaction_context(
4516                vec![
4517                    (id(), processor_account.clone()),
4518                    (vote_pubkey, vote_account.clone()),
4519                    (collector_pubkey, underfunded),
4520                ],
4521                vec![
4522                    InstructionAccount::new(1, false, true),
4523                    InstructionAccount::new(2, false, true),
4524                ],
4525                &rent,
4526            );
4527            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4528            let borrowed_vote = instruction_context
4529                .try_borrow_instruction_account(0)
4530                .unwrap();
4531            let borrowed_collector = instruction_context
4532                .try_borrow_instruction_account(1)
4533                .unwrap();
4534            assert_eq!(
4535                NewCommissionCollector::NewAccount(borrowed_collector)
4536                    .validate_and_resolve_key(&borrowed_vote, &rent),
4537                Err(InstructionError::InsufficientFunds),
4538            );
4539        }
4540
4541        // Fail: Collector account not writable (reserved account check).
4542        {
4543            let transaction_context = new_transaction_context(
4544                vec![
4545                    (id(), processor_account),
4546                    (vote_pubkey, vote_account),
4547                    (collector_pubkey, valid_collector()),
4548                ],
4549                vec![
4550                    InstructionAccount::new(1, false, true),
4551                    InstructionAccount::new(2, false, false), // <-- Not writable
4552                ],
4553                &rent,
4554            );
4555            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4556            let borrowed_vote = instruction_context
4557                .try_borrow_instruction_account(0)
4558                .unwrap();
4559            let borrowed_collector = instruction_context
4560                .try_borrow_instruction_account(1)
4561                .unwrap();
4562            assert_eq!(
4563                NewCommissionCollector::NewAccount(borrowed_collector)
4564                    .validate_and_resolve_key(&borrowed_vote, &rent),
4565                Err(InstructionError::InvalidArgument),
4566            );
4567        }
4568    }
4569
4570    /// Test update_commission_collector (SIMD-0232).
4571    ///
4572    /// This test only uses V4 since SIMD-0232 depends on SIMD-0185 (VoteStateV4).
4573    #[test]
4574    fn test_update_commission_collector() {
4575        let target_version = VoteStateTargetVersion::V4;
4576        let vote_pubkey = solana_pubkey::new_rand();
4577        let vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
4578        let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4579        let node_pubkey = *vote_state.node_pubkey();
4580
4581        let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
4582
4583        let serialized = vote_state.serialize();
4584        let serialized_len = serialized.len();
4585        let rent = Rent::default();
4586        let lamports = rent.minimum_balance(serialized_len);
4587        let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4588        vote_account.set_data_from_slice(&serialized);
4589
4590        let get_commission_collector =
4591            |vote_account: &BorrowedInstructionAccount, kind: CommissionKind| {
4592                let handler = get_vote_state_handler_checked(vote_account, target_version).unwrap();
4593                let vote_state = handler.as_ref_v4();
4594                match kind {
4595                    CommissionKind::InflationRewards => vote_state.inflation_rewards_collector,
4596                    CommissionKind::BlockRevenue => vote_state.block_revenue_collector,
4597                }
4598            };
4599
4600        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4601
4602        // Create a valid collector account (system-owned, rent-exempt).
4603        let new_collector = solana_pubkey::new_rand();
4604        let collector_lamports = rent.minimum_balance(0);
4605        let collector_account =
4606            AccountSharedData::new(collector_lamports, 0, &system_program::id());
4607
4608        let original_inflation_collector = vote_pubkey;
4609        let original_block_revenue_collector = node_pubkey;
4610
4611        // Should pass.
4612        {
4613            let transaction_context = new_transaction_context(
4614                vec![
4615                    (id(), processor_account.clone()),
4616                    (vote_pubkey, vote_account.clone()),
4617                    (new_collector, collector_account.clone()),
4618                ],
4619                vec![
4620                    InstructionAccount::new(1, false, true),
4621                    InstructionAccount::new(2, false, true),
4622                ],
4623                &rent,
4624            );
4625            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4626            let mut borrowed_vote_account = instruction_context
4627                .try_borrow_instruction_account(0)
4628                .unwrap();
4629
4630            // InflationRewards kind.
4631            update_commission_collector(
4632                &mut borrowed_vote_account,
4633                target_version,
4634                NewCommissionCollector::NewAccount(
4635                    instruction_context
4636                        .try_borrow_instruction_account(1)
4637                        .unwrap(),
4638                ),
4639                CommissionKind::InflationRewards,
4640                &signers,
4641                &rent,
4642            )
4643            .unwrap();
4644            assert_eq!(
4645                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4646                new_collector,
4647            );
4648            assert_eq!(
4649                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4650                original_block_revenue_collector, // Unchanged
4651            );
4652
4653            // BlockRevenue kind.
4654            update_commission_collector(
4655                &mut borrowed_vote_account,
4656                target_version,
4657                NewCommissionCollector::NewAccount(
4658                    instruction_context
4659                        .try_borrow_instruction_account(1)
4660                        .unwrap(),
4661                ),
4662                CommissionKind::BlockRevenue,
4663                &signers,
4664                &rent,
4665            )
4666            .unwrap();
4667            assert_eq!(
4668                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4669                new_collector,
4670            );
4671            assert_eq!(
4672                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4673                new_collector,
4674            );
4675        }
4676
4677        // Should pass - setting collector to vote account.
4678        {
4679            let transaction_context = new_transaction_context(
4680                vec![
4681                    (id(), processor_account.clone()),
4682                    (vote_pubkey, vote_account.clone()),
4683                ],
4684                vec![
4685                    InstructionAccount::new(1, false, true),
4686                    InstructionAccount::new(1, false, true), // collector = vote account (aliased)
4687                ],
4688                &rent,
4689            );
4690            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4691            let mut borrowed_vote_account = instruction_context
4692                .try_borrow_instruction_account(0)
4693                .unwrap();
4694
4695            // InflationRewards kind.
4696            update_commission_collector(
4697                &mut borrowed_vote_account,
4698                target_version,
4699                NewCommissionCollector::VoteAccount,
4700                CommissionKind::InflationRewards,
4701                &signers,
4702                &rent,
4703            )
4704            .unwrap();
4705            assert_eq!(
4706                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4707                vote_pubkey,
4708            );
4709            assert_eq!(
4710                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4711                original_block_revenue_collector, // Unchanged
4712            );
4713
4714            // BlockRevenue kind.
4715            update_commission_collector(
4716                &mut borrowed_vote_account,
4717                target_version,
4718                NewCommissionCollector::VoteAccount,
4719                CommissionKind::BlockRevenue,
4720                &signers,
4721                &rent,
4722            )
4723            .unwrap();
4724            assert_eq!(
4725                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4726                vote_pubkey,
4727            );
4728            assert_eq!(
4729                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4730                vote_pubkey,
4731            );
4732        }
4733
4734        // V3 -> V4 auto-conversion side-effect: updating one collector against a
4735        // V3-serialized account causes the "other" collector to be written as
4736        // its V4 default (inflation_rewards_collector = vote_pubkey,
4737        // block_revenue_collector = node_pubkey), per try_convert_to_vote_state_v4.
4738        {
4739            let v3 = get_max_sized_vote_state_v3();
4740            let v3_node_pubkey = v3.node_pubkey;
4741            let v3_withdrawer = v3.authorized_withdrawer;
4742            let v3_vote_pubkey = solana_pubkey::new_rand();
4743
4744            let v4_size = VoteStateV4::size_of();
4745            let mut account_data = vec![0u8; v4_size];
4746            bincode::serialize_into(&mut account_data[..], &VoteStateVersions::V3(Box::new(v3)))
4747                .unwrap();
4748            let mut v3_vote_account =
4749                AccountSharedData::new(rent.minimum_balance(v4_size), v4_size, &id());
4750            v3_vote_account.set_data_from_slice(&account_data);
4751
4752            let v3_signers: HashSet<Pubkey> = vec![v3_withdrawer].into_iter().collect();
4753
4754            let transaction_context = new_transaction_context(
4755                vec![
4756                    (id(), processor_account.clone()),
4757                    (v3_vote_pubkey, v3_vote_account),
4758                    (new_collector, collector_account.clone()),
4759                ],
4760                vec![
4761                    InstructionAccount::new(1, false, true),
4762                    InstructionAccount::new(2, false, true),
4763                ],
4764                &rent,
4765            );
4766            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4767            let mut borrowed_vote_account = instruction_context
4768                .try_borrow_instruction_account(0)
4769                .unwrap();
4770
4771            update_commission_collector(
4772                &mut borrowed_vote_account,
4773                target_version,
4774                NewCommissionCollector::NewAccount(
4775                    instruction_context
4776                        .try_borrow_instruction_account(1)
4777                        .unwrap(),
4778                ),
4779                CommissionKind::InflationRewards,
4780                &v3_signers,
4781                &rent,
4782            )
4783            .unwrap();
4784
4785            // The updated field reflects the caller's new collector.
4786            assert_eq!(
4787                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4788                new_collector,
4789            );
4790            // The *other* field was reset to its V4-conversion default
4791            // (block_revenue_collector = node_pubkey from the V3 source).
4792            assert_eq!(
4793                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4794                v3_node_pubkey,
4795            );
4796        }
4797
4798        // Should fail - deserialization error paths.
4799        //
4800        // All four variants produce `InvalidAccountData`:
4801        // * V0_23_5 is explicitly unsupported
4802        // * V1_14_11, V3, and V4 fail if state is bad
4803        let run_with_account_data = |bytes: Vec<u8>| -> Result<(), InstructionError> {
4804            let mut bad_vote_account =
4805                AccountSharedData::new(rent.minimum_balance(bytes.len()), bytes.len(), &id());
4806            bad_vote_account.set_data_from_slice(&bytes);
4807            let transaction_context = new_transaction_context(
4808                vec![
4809                    (id(), processor_account.clone()),
4810                    (vote_pubkey, bad_vote_account),
4811                    (new_collector, collector_account.clone()),
4812                ],
4813                vec![
4814                    InstructionAccount::new(1, false, true),
4815                    InstructionAccount::new(2, false, true),
4816                ],
4817                &rent,
4818            );
4819            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4820            let mut borrowed_vote_account = instruction_context
4821                .try_borrow_instruction_account(0)
4822                .unwrap();
4823            update_commission_collector(
4824                &mut borrowed_vote_account,
4825                target_version,
4826                NewCommissionCollector::NewAccount(
4827                    instruction_context
4828                        .try_borrow_instruction_account(1)
4829                        .unwrap(),
4830                ),
4831                CommissionKind::InflationRewards,
4832                &signers,
4833                &rent,
4834            )
4835        };
4836        let variant_with_short_body = |variant: u32| -> Vec<u8> {
4837            let mut bytes = vec![0u8; 8];
4838            bytes[..4].copy_from_slice(&variant.to_le_bytes());
4839            bytes
4840        };
4841
4842        // Should fail - V0_23_5 not supported.
4843        assert_eq!(
4844            run_with_account_data(variant_with_short_body(0)),
4845            Err(InstructionError::InvalidAccountData),
4846        );
4847
4848        // Should fail - Invalid V1_14_11 state.
4849        assert_eq!(
4850            run_with_account_data(variant_with_short_body(1)),
4851            Err(InstructionError::InvalidAccountData),
4852        );
4853
4854        // Should fail - Invalid V3 state.
4855        assert_eq!(
4856            run_with_account_data(variant_with_short_body(2)),
4857            Err(InstructionError::InvalidAccountData),
4858        );
4859
4860        // Should fail - Invalid V4 state.
4861        assert_eq!(
4862            run_with_account_data(variant_with_short_body(3)),
4863            Err(InstructionError::InvalidAccountData),
4864        );
4865
4866        // Should fail - authorized withdrawer didn't sign.
4867        {
4868            let non_signers: HashSet<Pubkey> = HashSet::new();
4869            let transaction_context = new_transaction_context(
4870                vec![
4871                    (id(), processor_account.clone()),
4872                    (vote_pubkey, vote_account.clone()),
4873                    (new_collector, collector_account.clone()),
4874                ],
4875                vec![
4876                    InstructionAccount::new(1, false, true),
4877                    InstructionAccount::new(2, false, true),
4878                ],
4879                &rent,
4880            );
4881            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4882            let mut borrowed_vote_account = instruction_context
4883                .try_borrow_instruction_account(0)
4884                .unwrap();
4885
4886            assert_eq!(
4887                update_commission_collector(
4888                    &mut borrowed_vote_account,
4889                    target_version,
4890                    NewCommissionCollector::NewAccount(
4891                        instruction_context
4892                            .try_borrow_instruction_account(1)
4893                            .unwrap()
4894                    ),
4895                    CommissionKind::InflationRewards,
4896                    &non_signers,
4897                    &rent,
4898                ),
4899                Err(InstructionError::MissingRequiredSignature)
4900            );
4901            assert_eq!(
4902                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4903                original_inflation_collector, // Unchanged
4904            );
4905            assert_eq!(
4906                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4907                original_block_revenue_collector, // Unchanged
4908            );
4909        }
4910
4911        // Should fail - wrong signer (not the authorized withdrawer).
4912        {
4913            let wrong_signers: HashSet<Pubkey> = vec![Pubkey::new_unique()].into_iter().collect();
4914            let transaction_context = new_transaction_context(
4915                vec![
4916                    (id(), processor_account.clone()),
4917                    (vote_pubkey, vote_account.clone()),
4918                    (new_collector, collector_account),
4919                ],
4920                vec![
4921                    InstructionAccount::new(1, false, true),
4922                    InstructionAccount::new(2, false, true),
4923                ],
4924                &rent,
4925            );
4926            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4927            let mut borrowed_vote_account = instruction_context
4928                .try_borrow_instruction_account(0)
4929                .unwrap();
4930
4931            assert_eq!(
4932                update_commission_collector(
4933                    &mut borrowed_vote_account,
4934                    target_version,
4935                    NewCommissionCollector::NewAccount(
4936                        instruction_context
4937                            .try_borrow_instruction_account(1)
4938                            .unwrap()
4939                    ),
4940                    CommissionKind::InflationRewards,
4941                    &wrong_signers,
4942                    &rent,
4943                ),
4944                Err(InstructionError::MissingRequiredSignature)
4945            );
4946            assert_eq!(
4947                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4948                original_inflation_collector, // Unchanged
4949            );
4950            assert_eq!(
4951                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4952                original_block_revenue_collector, // Unchanged
4953            );
4954        }
4955
4956        // Should fail - new collector not system program owned.
4957        {
4958            let bad_collector = solana_pubkey::new_rand();
4959            let non_system_owner = solana_pubkey::new_rand();
4960            let bad_collector_account =
4961                AccountSharedData::new(collector_lamports, 0, &non_system_owner);
4962            let transaction_context = new_transaction_context(
4963                vec![
4964                    (id(), processor_account.clone()),
4965                    (vote_pubkey, vote_account.clone()),
4966                    (bad_collector, bad_collector_account),
4967                ],
4968                vec![
4969                    InstructionAccount::new(1, false, true),
4970                    InstructionAccount::new(2, false, true),
4971                ],
4972                &rent,
4973            );
4974            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4975            let mut borrowed_vote_account = instruction_context
4976                .try_borrow_instruction_account(0)
4977                .unwrap();
4978
4979            assert_eq!(
4980                update_commission_collector(
4981                    &mut borrowed_vote_account,
4982                    target_version,
4983                    NewCommissionCollector::NewAccount(
4984                        instruction_context
4985                            .try_borrow_instruction_account(1)
4986                            .unwrap()
4987                    ),
4988                    CommissionKind::InflationRewards,
4989                    &signers,
4990                    &rent,
4991                ),
4992                Err(InstructionError::InvalidAccountOwner)
4993            );
4994            assert_eq!(
4995                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4996                original_inflation_collector, // Unchanged
4997            );
4998            assert_eq!(
4999                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5000                original_block_revenue_collector, // Unchanged
5001            );
5002        }
5003
5004        // Should fail - new collector not rent-exempt.
5005        {
5006            let bad_collector = solana_pubkey::new_rand();
5007            let bad_collector_account = AccountSharedData::new(0, 0, &system_program::id());
5008            let transaction_context = new_transaction_context(
5009                vec![
5010                    (id(), processor_account.clone()),
5011                    (vote_pubkey, vote_account.clone()),
5012                    (bad_collector, bad_collector_account),
5013                ],
5014                vec![
5015                    InstructionAccount::new(1, false, true),
5016                    InstructionAccount::new(2, false, true),
5017                ],
5018                &rent,
5019            );
5020            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5021            let mut borrowed_vote_account = instruction_context
5022                .try_borrow_instruction_account(0)
5023                .unwrap();
5024
5025            assert_eq!(
5026                update_commission_collector(
5027                    &mut borrowed_vote_account,
5028                    target_version,
5029                    NewCommissionCollector::NewAccount(
5030                        instruction_context
5031                            .try_borrow_instruction_account(1)
5032                            .unwrap()
5033                    ),
5034                    CommissionKind::InflationRewards,
5035                    &signers,
5036                    &rent,
5037                ),
5038                Err(InstructionError::InsufficientFunds)
5039            );
5040            assert_eq!(
5041                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
5042                original_inflation_collector, // Unchanged
5043            );
5044            assert_eq!(
5045                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5046                original_block_revenue_collector, // Unchanged
5047            );
5048        }
5049
5050        // Should fail - new collector not writable (reserved account check).
5051        {
5052            let bad_collector = solana_pubkey::new_rand();
5053            let bad_collector_account =
5054                AccountSharedData::new(collector_lamports, 0, &system_program::id());
5055            let transaction_context = new_transaction_context(
5056                vec![
5057                    (id(), processor_account),
5058                    (vote_pubkey, vote_account.clone()),
5059                    (bad_collector, bad_collector_account),
5060                ],
5061                vec![
5062                    InstructionAccount::new(1, false, true),
5063                    InstructionAccount::new(2, false, false), // not writable
5064                ],
5065                &rent,
5066            );
5067            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5068            let mut borrowed_vote_account = instruction_context
5069                .try_borrow_instruction_account(0)
5070                .unwrap();
5071
5072            assert_eq!(
5073                update_commission_collector(
5074                    &mut borrowed_vote_account,
5075                    target_version,
5076                    NewCommissionCollector::NewAccount(
5077                        instruction_context
5078                            .try_borrow_instruction_account(1)
5079                            .unwrap()
5080                    ),
5081                    CommissionKind::InflationRewards,
5082                    &signers,
5083                    &rent,
5084                ),
5085                Err(InstructionError::InvalidArgument)
5086            );
5087            assert_eq!(
5088                get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
5089                original_inflation_collector, // Unchanged
5090            );
5091            assert_eq!(
5092                get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5093                original_block_revenue_collector, // Unchanged
5094            );
5095        }
5096    }
5097
5098    #[test]
5099    fn test_initialize_account_v2() {
5100        let target_version = VoteStateTargetVersion::V4;
5101        let rent = Rent::default();
5102        let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
5103
5104        let vote_pubkey = solana_pubkey::new_rand();
5105        let node_pubkey = solana_pubkey::new_rand();
5106        let authorized_voter = solana_pubkey::new_rand();
5107        let authorized_withdrawer = solana_pubkey::new_rand();
5108        let inflation_collector_pubkey = solana_pubkey::new_rand();
5109        let block_revenue_collector_pubkey = solana_pubkey::new_rand();
5110
5111        let (bls_pubkey, bls_proof_of_possession) =
5112            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
5113        let vote_init = VoteInitV2 {
5114            node_pubkey,
5115            authorized_voter,
5116            authorized_voter_bls_pubkey: bls_pubkey,
5117            authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
5118            authorized_withdrawer,
5119            inflation_rewards_commission_bps: 1_234,
5120            block_revenue_commission_bps: 5_678,
5121        };
5122
5123        let signers: HashSet<Pubkey> = vec![node_pubkey].into_iter().collect();
5124        let clock = Clock::default();
5125
5126        let v4_size = VoteStateV4::size_of();
5127        let lamports = rent.minimum_balance(v4_size);
5128        let make_uninit_vote_account = || AccountSharedData::new(lamports, v4_size, &id());
5129        let valid_collector_account =
5130            || AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
5131
5132        let assert_v4_fields =
5133            |vote_account: &BorrowedInstructionAccount,
5134             expected_inflation_rewards_collector: Pubkey,
5135             expected_block_revenue_collector: Pubkey| {
5136                let VoteStateVersions::V4(v4) =
5137                    vote_account.get_state::<VoteStateVersions>().unwrap()
5138                else {
5139                    panic!("expected v4");
5140                };
5141                assert_eq!(v4.node_pubkey, node_pubkey);
5142                assert_eq!(
5143                    v4.authorized_voters.get_authorized_voter(clock.epoch),
5144                    Some(authorized_voter),
5145                );
5146                assert_eq!(v4.authorized_withdrawer, authorized_withdrawer);
5147                assert_eq!(v4.bls_pubkey_compressed, Some(bls_pubkey));
5148                assert_eq!(v4.inflation_rewards_commission_bps, 1_234);
5149                assert_eq!(v4.block_revenue_commission_bps, 5_678);
5150                assert_eq!(
5151                    v4.inflation_rewards_collector,
5152                    expected_inflation_rewards_collector
5153                );
5154                assert_eq!(v4.block_revenue_collector, expected_block_revenue_collector);
5155                assert_eq!(v4.pending_delegator_rewards, 0);
5156                assert!(v4.votes.is_empty());
5157                assert_eq!(v4.root_slot, None);
5158                assert!(v4.epoch_credits.is_empty());
5159            };
5160
5161        let assert_still_uninitialized = |vote_account: &BorrowedInstructionAccount| {
5162            assert!(
5163                vote_account
5164                    .get_state::<VoteStateVersions>()
5165                    .unwrap()
5166                    .is_uninitialized()
5167            );
5168        };
5169
5170        // Should pass - both collectors are separate accounts.
5171        {
5172            let transaction_context = new_transaction_context(
5173                vec![
5174                    (id(), processor_account.clone()),
5175                    (vote_pubkey, make_uninit_vote_account()),
5176                    (inflation_collector_pubkey, valid_collector_account()),
5177                    (block_revenue_collector_pubkey, valid_collector_account()),
5178                ],
5179                vec![
5180                    InstructionAccount::new(1, false, true),
5181                    InstructionAccount::new(2, false, true),
5182                    InstructionAccount::new(3, false, true),
5183                ],
5184                &rent,
5185            );
5186            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5187            let mut borrowed_vote_account = instruction_context
5188                .try_borrow_instruction_account(0)
5189                .unwrap();
5190
5191            initialize_account_v2(
5192                &mut borrowed_vote_account,
5193                target_version,
5194                &vote_init,
5195                NewCommissionCollector::NewAccount(
5196                    instruction_context
5197                        .try_borrow_instruction_account(1)
5198                        .unwrap(),
5199                ),
5200                NewCommissionCollector::NewAccount(
5201                    instruction_context
5202                        .try_borrow_instruction_account(2)
5203                        .unwrap(),
5204                ),
5205                &signers,
5206                &clock,
5207                &rent,
5208                || Ok(()),
5209            )
5210            .unwrap();
5211
5212            assert_v4_fields(
5213                &borrowed_vote_account,
5214                inflation_collector_pubkey,
5215                block_revenue_collector_pubkey,
5216            );
5217        }
5218
5219        // Should pass - inflation collector aliased to vote account.
5220        {
5221            let transaction_context = new_transaction_context(
5222                vec![
5223                    (id(), processor_account.clone()),
5224                    (vote_pubkey, make_uninit_vote_account()),
5225                    (block_revenue_collector_pubkey, valid_collector_account()),
5226                ],
5227                vec![
5228                    InstructionAccount::new(1, false, true),
5229                    InstructionAccount::new(2, false, true),
5230                ],
5231                &rent,
5232            );
5233            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5234            let mut borrowed_vote_account = instruction_context
5235                .try_borrow_instruction_account(0)
5236                .unwrap();
5237
5238            initialize_account_v2(
5239                &mut borrowed_vote_account,
5240                target_version,
5241                &vote_init,
5242                NewCommissionCollector::VoteAccount,
5243                NewCommissionCollector::NewAccount(
5244                    instruction_context
5245                        .try_borrow_instruction_account(1)
5246                        .unwrap(),
5247                ),
5248                &signers,
5249                &clock,
5250                &rent,
5251                || Ok(()),
5252            )
5253            .unwrap();
5254
5255            assert_v4_fields(
5256                &borrowed_vote_account,
5257                vote_pubkey,
5258                block_revenue_collector_pubkey,
5259            );
5260        }
5261
5262        // Should pass - block revenue collector aliased to vote account.
5263        {
5264            let transaction_context = new_transaction_context(
5265                vec![
5266                    (id(), processor_account.clone()),
5267                    (vote_pubkey, make_uninit_vote_account()),
5268                    (inflation_collector_pubkey, valid_collector_account()),
5269                ],
5270                vec![
5271                    InstructionAccount::new(1, false, true),
5272                    InstructionAccount::new(2, false, true),
5273                ],
5274                &rent,
5275            );
5276            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5277            let mut borrowed_vote_account = instruction_context
5278                .try_borrow_instruction_account(0)
5279                .unwrap();
5280
5281            initialize_account_v2(
5282                &mut borrowed_vote_account,
5283                target_version,
5284                &vote_init,
5285                NewCommissionCollector::NewAccount(
5286                    instruction_context
5287                        .try_borrow_instruction_account(1)
5288                        .unwrap(),
5289                ),
5290                NewCommissionCollector::VoteAccount,
5291                &signers,
5292                &clock,
5293                &rent,
5294                || Ok(()),
5295            )
5296            .unwrap();
5297
5298            assert_v4_fields(
5299                &borrowed_vote_account,
5300                inflation_collector_pubkey,
5301                vote_pubkey,
5302            );
5303        }
5304
5305        // Should pass - both collectors aliased to vote account.
5306        {
5307            let transaction_context = new_transaction_context(
5308                vec![
5309                    (id(), processor_account.clone()),
5310                    (vote_pubkey, make_uninit_vote_account()),
5311                ],
5312                vec![InstructionAccount::new(1, false, true)],
5313                &rent,
5314            );
5315            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5316            let mut borrowed_vote_account = instruction_context
5317                .try_borrow_instruction_account(0)
5318                .unwrap();
5319
5320            initialize_account_v2(
5321                &mut borrowed_vote_account,
5322                target_version,
5323                &vote_init,
5324                NewCommissionCollector::VoteAccount,
5325                NewCommissionCollector::VoteAccount,
5326                &signers,
5327                &clock,
5328                &rent,
5329                || Ok(()),
5330            )
5331            .unwrap();
5332
5333            assert_v4_fields(&borrowed_vote_account, vote_pubkey, vote_pubkey);
5334        }
5335
5336        // Should fail - vote account is the wrong size.
5337        {
5338            let oversized_vote_account =
5339                AccountSharedData::new(rent.minimum_balance(2 * v4_size), 2 * v4_size, &id());
5340            let transaction_context = new_transaction_context(
5341                vec![
5342                    (id(), processor_account.clone()),
5343                    (vote_pubkey, oversized_vote_account),
5344                    (inflation_collector_pubkey, valid_collector_account()),
5345                    (block_revenue_collector_pubkey, valid_collector_account()),
5346                ],
5347                vec![
5348                    InstructionAccount::new(1, false, true),
5349                    InstructionAccount::new(2, false, true),
5350                    InstructionAccount::new(3, false, true),
5351                ],
5352                &rent,
5353            );
5354            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5355            let mut borrowed_vote_account = instruction_context
5356                .try_borrow_instruction_account(0)
5357                .unwrap();
5358
5359            assert_eq!(
5360                initialize_account_v2(
5361                    &mut borrowed_vote_account,
5362                    target_version,
5363                    &vote_init,
5364                    NewCommissionCollector::NewAccount(
5365                        instruction_context
5366                            .try_borrow_instruction_account(1)
5367                            .unwrap(),
5368                    ),
5369                    NewCommissionCollector::NewAccount(
5370                        instruction_context
5371                            .try_borrow_instruction_account(2)
5372                            .unwrap(),
5373                    ),
5374                    &signers,
5375                    &clock,
5376                    &rent,
5377                    || Ok(()),
5378                ),
5379                Err(InstructionError::InvalidAccountData),
5380            );
5381        }
5382
5383        // Should fail - not a valid vote state.
5384        {
5385            let mut invalid_vote_account = AccountSharedData::new(lamports, v4_size, &id());
5386            invalid_vote_account.set_data_from_slice(&vec![0xFFu8; v4_size]);
5387
5388            let transaction_context = new_transaction_context(
5389                vec![
5390                    (id(), processor_account.clone()),
5391                    (vote_pubkey, invalid_vote_account),
5392                    (inflation_collector_pubkey, valid_collector_account()),
5393                    (block_revenue_collector_pubkey, valid_collector_account()),
5394                ],
5395                vec![
5396                    InstructionAccount::new(1, false, true),
5397                    InstructionAccount::new(2, false, true),
5398                    InstructionAccount::new(3, false, true),
5399                ],
5400                &rent,
5401            );
5402            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5403            let mut borrowed_vote_account = instruction_context
5404                .try_borrow_instruction_account(0)
5405                .unwrap();
5406
5407            assert_eq!(
5408                initialize_account_v2(
5409                    &mut borrowed_vote_account,
5410                    target_version,
5411                    &vote_init,
5412                    NewCommissionCollector::NewAccount(
5413                        instruction_context
5414                            .try_borrow_instruction_account(1)
5415                            .unwrap(),
5416                    ),
5417                    NewCommissionCollector::NewAccount(
5418                        instruction_context
5419                            .try_borrow_instruction_account(2)
5420                            .unwrap(),
5421                    ),
5422                    &signers,
5423                    &clock,
5424                    &rent,
5425                    || Ok(()),
5426                ),
5427                Err(InstructionError::InvalidAccountData),
5428            );
5429        }
5430
5431        // Should fail - vote account already initialized.
5432        {
5433            let preexisting_handler = vote_state_new_for_test(&vote_pubkey, target_version);
5434            let preexisting_state = preexisting_handler.as_ref_v4().clone();
5435            let serialized = preexisting_handler.serialize();
5436            let serialized_len = serialized.len();
5437            let mut initialized_vote_account =
5438                AccountSharedData::new(rent.minimum_balance(serialized_len), serialized_len, &id());
5439            initialized_vote_account.set_data_from_slice(&serialized);
5440
5441            let transaction_context = new_transaction_context(
5442                vec![
5443                    (id(), processor_account.clone()),
5444                    (vote_pubkey, initialized_vote_account),
5445                    (inflation_collector_pubkey, valid_collector_account()),
5446                    (block_revenue_collector_pubkey, valid_collector_account()),
5447                ],
5448                vec![
5449                    InstructionAccount::new(1, false, true),
5450                    InstructionAccount::new(2, false, true),
5451                    InstructionAccount::new(3, false, true),
5452                ],
5453                &rent,
5454            );
5455            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5456            let mut borrowed_vote_account = instruction_context
5457                .try_borrow_instruction_account(0)
5458                .unwrap();
5459
5460            assert_eq!(
5461                initialize_account_v2(
5462                    &mut borrowed_vote_account,
5463                    target_version,
5464                    &vote_init,
5465                    NewCommissionCollector::NewAccount(
5466                        instruction_context
5467                            .try_borrow_instruction_account(1)
5468                            .unwrap(),
5469                    ),
5470                    NewCommissionCollector::NewAccount(
5471                        instruction_context
5472                            .try_borrow_instruction_account(2)
5473                            .unwrap(),
5474                    ),
5475                    &signers,
5476                    &clock,
5477                    &rent,
5478                    || Ok(()),
5479                ),
5480                Err(InstructionError::AccountAlreadyInitialized),
5481            );
5482
5483            // Pre-existing state must be untouched - the new init payload must
5484            // not have been written.
5485            let handler =
5486                get_vote_state_handler_checked(&borrowed_vote_account, target_version).unwrap();
5487            assert_eq!(*handler.as_ref_v4(), preexisting_state);
5488        }
5489
5490        // Should fail - node_pubkey didn't sign.
5491        {
5492            let non_signers: HashSet<Pubkey> = HashSet::new();
5493            let transaction_context = new_transaction_context(
5494                vec![
5495                    (id(), processor_account.clone()),
5496                    (vote_pubkey, make_uninit_vote_account()),
5497                    (inflation_collector_pubkey, valid_collector_account()),
5498                    (block_revenue_collector_pubkey, valid_collector_account()),
5499                ],
5500                vec![
5501                    InstructionAccount::new(1, false, true),
5502                    InstructionAccount::new(2, false, true),
5503                    InstructionAccount::new(3, false, true),
5504                ],
5505                &rent,
5506            );
5507            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5508            let mut borrowed_vote_account = instruction_context
5509                .try_borrow_instruction_account(0)
5510                .unwrap();
5511
5512            assert_eq!(
5513                initialize_account_v2(
5514                    &mut borrowed_vote_account,
5515                    target_version,
5516                    &vote_init,
5517                    NewCommissionCollector::NewAccount(
5518                        instruction_context
5519                            .try_borrow_instruction_account(1)
5520                            .unwrap(),
5521                    ),
5522                    NewCommissionCollector::NewAccount(
5523                        instruction_context
5524                            .try_borrow_instruction_account(2)
5525                            .unwrap(),
5526                    ),
5527                    &non_signers,
5528                    &clock,
5529                    &rent,
5530                    || Ok(()),
5531                ),
5532                Err(InstructionError::MissingRequiredSignature),
5533            );
5534            assert_still_uninitialized(&borrowed_vote_account);
5535        }
5536
5537        // Should fail - SIMD-0232 collector account checks, applied to both
5538        // account indices.
5539        {
5540            #[derive(Clone, Copy)]
5541            enum CollectorSlot {
5542                Inflation,
5543                BlockRevenue,
5544            }
5545
5546            let test_bad_collector =
5547                |slot: CollectorSlot,
5548                 bad_collector: AccountSharedData,
5549                 bad_collector_is_writable: bool,
5550                 expected_error: InstructionError| {
5551                    let (
5552                        inflation_account,
5553                        inflation_writable,
5554                        block_revenue_account,
5555                        block_revenue_writable,
5556                    ) = match slot {
5557                        CollectorSlot::Inflation => (
5558                            bad_collector,
5559                            bad_collector_is_writable,
5560                            valid_collector_account(),
5561                            true,
5562                        ),
5563                        CollectorSlot::BlockRevenue => (
5564                            valid_collector_account(),
5565                            true,
5566                            bad_collector,
5567                            bad_collector_is_writable,
5568                        ),
5569                    };
5570
5571                    let transaction_context = new_transaction_context(
5572                        vec![
5573                            (id(), processor_account.clone()),
5574                            (vote_pubkey, make_uninit_vote_account()),
5575                            (inflation_collector_pubkey, inflation_account),
5576                            (block_revenue_collector_pubkey, block_revenue_account),
5577                        ],
5578                        vec![
5579                            InstructionAccount::new(1, false, true),
5580                            InstructionAccount::new(2, false, inflation_writable),
5581                            InstructionAccount::new(3, false, block_revenue_writable),
5582                        ],
5583                        &rent,
5584                    );
5585                    let instruction_context =
5586                        transaction_context.get_next_instruction_context().unwrap();
5587                    let mut borrowed_vote_account = instruction_context
5588                        .try_borrow_instruction_account(0)
5589                        .unwrap();
5590
5591                    assert_eq!(
5592                        initialize_account_v2(
5593                            &mut borrowed_vote_account,
5594                            target_version,
5595                            &vote_init,
5596                            NewCommissionCollector::NewAccount(
5597                                instruction_context
5598                                    .try_borrow_instruction_account(1)
5599                                    .unwrap(),
5600                            ),
5601                            NewCommissionCollector::NewAccount(
5602                                instruction_context
5603                                    .try_borrow_instruction_account(2)
5604                                    .unwrap(),
5605                            ),
5606                            &signers,
5607                            &clock,
5608                            &rent,
5609                            || Ok(()),
5610                        ),
5611                        Err(expected_error),
5612                    );
5613                    assert_still_uninitialized(&borrowed_vote_account);
5614                };
5615
5616            for slot in [CollectorSlot::Inflation, CollectorSlot::BlockRevenue] {
5617                // 1. Not system-owned.
5618                test_bad_collector(
5619                    slot,
5620                    AccountSharedData::new(rent.minimum_balance(0), 0, &solana_pubkey::new_rand()),
5621                    true,
5622                    InstructionError::InvalidAccountOwner,
5623                );
5624
5625                // 2. Not rent-exempt.
5626                test_bad_collector(
5627                    slot,
5628                    AccountSharedData::new(0, 0, &system_program::id()),
5629                    true,
5630                    InstructionError::InsufficientFunds,
5631                );
5632
5633                // 3. Not writable.
5634                test_bad_collector(
5635                    slot,
5636                    valid_collector_account(),
5637                    false,
5638                    InstructionError::InvalidArgument,
5639                );
5640            }
5641        }
5642
5643        // Should fail - BLS proof of possession does not verify.
5644        {
5645            let bad_vote_init = VoteInitV2 {
5646                authorized_voter_bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
5647                authorized_voter_bls_proof_of_possession: [2u8;
5648                    BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
5649                ..vote_init
5650            };
5651            let transaction_context = new_transaction_context(
5652                vec![
5653                    (id(), processor_account),
5654                    (vote_pubkey, make_uninit_vote_account()),
5655                    (inflation_collector_pubkey, valid_collector_account()),
5656                    (block_revenue_collector_pubkey, valid_collector_account()),
5657                ],
5658                vec![
5659                    InstructionAccount::new(1, false, true),
5660                    InstructionAccount::new(2, false, true),
5661                    InstructionAccount::new(3, false, true),
5662                ],
5663                &rent,
5664            );
5665            let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5666            let mut borrowed_vote_account = instruction_context
5667                .try_borrow_instruction_account(0)
5668                .unwrap();
5669
5670            assert_eq!(
5671                initialize_account_v2(
5672                    &mut borrowed_vote_account,
5673                    target_version,
5674                    &bad_vote_init,
5675                    NewCommissionCollector::NewAccount(
5676                        instruction_context
5677                            .try_borrow_instruction_account(1)
5678                            .unwrap(),
5679                    ),
5680                    NewCommissionCollector::NewAccount(
5681                        instruction_context
5682                            .try_borrow_instruction_account(2)
5683                            .unwrap(),
5684                    ),
5685                    &signers,
5686                    &clock,
5687                    &rent,
5688                    || Ok(()),
5689                ),
5690                Err(InstructionError::InvalidArgument),
5691            );
5692            assert_still_uninitialized(&borrowed_vote_account);
5693        }
5694    }
5695
5696    /// recipient at index 2.
5697    fn setup_withdraw_context(
5698        vote_pubkey: Pubkey,
5699        vote_account: AccountSharedData,
5700    ) -> TransactionContext<'static> {
5701        let rent = Rent::default();
5702        let recipient = solana_pubkey::new_rand();
5703        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
5704        let mut transaction_context = TransactionContext::new(
5705            vec![
5706                (id(), program_account),
5707                (vote_pubkey, vote_account),
5708                (recipient, AccountSharedData::default()),
5709            ],
5710            rent,
5711            0,
5712            0,
5713            1,
5714        );
5715        transaction_context
5716            .configure_top_level_instruction_for_tests(
5717                0,
5718                vec![
5719                    InstructionAccount::new(1, false, true),
5720                    InstructionAccount::new(2, false, true),
5721                ],
5722                vec![],
5723            )
5724            .unwrap();
5725        transaction_context
5726    }
5727
5728    #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
5729    fn test_withdraw(target_version: VoteStateTargetVersion) {
5730        // Verify withdraw boundary conditions around the rent-exempt
5731        // minimum: partial withdraw, full deinit, and over-withdraw.
5732        let vote_pubkey = solana_pubkey::new_rand();
5733        let vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
5734        let withdrawer = *vote_state.authorized_withdrawer();
5735        let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5736        let rent = Rent::default();
5737        let serialized = vote_state.clone().serialize();
5738        let serialized_len = serialized.len();
5739        let min_balance = rent.minimum_balance(serialized_len);
5740        let clock = Clock {
5741            epoch: 100,
5742            ..Clock::default()
5743        };
5744
5745        // Account at exact rent-exempt minimum: withdraw 1 fails.
5746        {
5747            let mut acct = AccountSharedData::new(min_balance, serialized_len, &id());
5748            acct.set_data_from_slice(&serialized);
5749            let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5750            let ix = transaction_context.get_next_instruction_context().unwrap();
5751            assert_eq!(
5752                withdraw(&ix, 0, target_version, 1, 1, &signers, &rent, &clock),
5753                Err(InstructionError::InsufficientFunds)
5754            );
5755        }
5756
5757        // Account at exact rent-exempt minimum: withdraw all succeeds (deinit).
5758        {
5759            let mut acct = AccountSharedData::new(min_balance, serialized_len, &id());
5760            acct.set_data_from_slice(&serialized);
5761            let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5762            let ix = transaction_context.get_next_instruction_context().unwrap();
5763            withdraw(
5764                &ix,
5765                0,
5766                target_version,
5767                min_balance,
5768                1,
5769                &signers,
5770                &rent,
5771                &clock,
5772            )
5773            .unwrap();
5774        }
5775
5776        // Account at rent_exempt + 100: withdraw 100 succeeds.
5777        {
5778            let mut acct = AccountSharedData::new(min_balance + 100, serialized_len, &id());
5779            acct.set_data_from_slice(&serialized);
5780            let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5781            let ix = transaction_context.get_next_instruction_context().unwrap();
5782            withdraw(&ix, 0, target_version, 100, 1, &signers, &rent, &clock).unwrap();
5783        }
5784
5785        // Account at rent_exempt + 100: withdraw 101 fails.
5786        {
5787            let mut acct = AccountSharedData::new(min_balance + 100, serialized_len, &id());
5788            acct.set_data_from_slice(&serialized);
5789            let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5790            let ix = transaction_context.get_next_instruction_context().unwrap();
5791            assert_eq!(
5792                withdraw(&ix, 0, target_version, 101, 1, &signers, &rent, &clock),
5793                Err(InstructionError::InsufficientFunds)
5794            );
5795        }
5796    }
5797
5798    /// Helper to create a V4 vote account with a specific
5799    /// `pending_delegator_rewards` value.
5800    fn make_v4_account_with_pending(
5801        vote_pubkey: &Pubkey,
5802        pending: u64,
5803        extra_lamports: u64,
5804    ) -> (VoteStateHandler, AccountSharedData) {
5805        let vote_state = vote_state_new_for_test(vote_pubkey, VoteStateTargetVersion::V4);
5806        let mut v4 = vote_state.as_ref_v4().clone();
5807        v4.pending_delegator_rewards = pending;
5808        let handler = VoteStateHandler::new_v4(v4);
5809        let serialized = handler.clone().serialize();
5810        let rent = Rent::default();
5811        let lamports = rent.minimum_balance(serialized.len()) + extra_lamports;
5812        let mut account = AccountSharedData::new(lamports, serialized.len(), &id());
5813        account.set_data_from_slice(&serialized);
5814        (handler, account)
5815    }
5816
5817    #[test]
5818    fn test_withdraw_with_pending_delegator_rewards() {
5819        // Verify withdraw protects pending_delegator_rewards: partial
5820        // withdrawals respect the pending reserve, and full close is
5821        // blocked when pending > 0.
5822        let vote_pubkey = solana_pubkey::new_rand();
5823        let rent = Rent::default();
5824        let clock = Clock {
5825            epoch: 100,
5826            ..Clock::default()
5827        };
5828
5829        // pending = 1000, extra = 1000. withdrawable = 0.
5830        {
5831            let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1000);
5832            let withdrawer = *handler.authorized_withdrawer();
5833            let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5834            let tx = setup_withdraw_context(vote_pubkey, account);
5835            let ix = tx.get_next_instruction_context().unwrap();
5836
5837            // Withdraw 1 fails (withdrawable = lamports - rent - pending = 0).
5838            assert_eq!(
5839                withdraw(
5840                    &ix,
5841                    0,
5842                    VoteStateTargetVersion::V4,
5843                    1,
5844                    1,
5845                    &signers,
5846                    &rent,
5847                    &clock
5848                ),
5849                Err(InstructionError::InsufficientFunds)
5850            );
5851        }
5852
5853        // pending = 1000, extra = 1001. withdrawable = 1.
5854        {
5855            let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1001);
5856            let withdrawer = *handler.authorized_withdrawer();
5857            let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5858            let tx = setup_withdraw_context(vote_pubkey, account);
5859            let ix = tx.get_next_instruction_context().unwrap();
5860
5861            // Withdraw 1 succeeds.
5862            withdraw(
5863                &ix,
5864                0,
5865                VoteStateTargetVersion::V4,
5866                1,
5867                1,
5868                &signers,
5869                &rent,
5870                &clock,
5871            )
5872            .unwrap();
5873        }
5874
5875        // pending = 1000, extra = 1001. Withdraw 2 fails.
5876        {
5877            let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1001);
5878            let withdrawer = *handler.authorized_withdrawer();
5879            let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5880            let tx = setup_withdraw_context(vote_pubkey, account);
5881            let ix = tx.get_next_instruction_context().unwrap();
5882
5883            assert_eq!(
5884                withdraw(
5885                    &ix,
5886                    0,
5887                    VoteStateTargetVersion::V4,
5888                    2,
5889                    1,
5890                    &signers,
5891                    &rent,
5892                    &clock
5893                ),
5894                Err(InstructionError::InsufficientFunds)
5895            );
5896        }
5897
5898        // Full close blocked when pending > 0.
5899        {
5900            let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1, 1_000_000);
5901            let withdrawer = *handler.authorized_withdrawer();
5902            let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5903            let lamports = rent.minimum_balance(VoteStateV4::size_of()) + 1_000_000;
5904            let tx = setup_withdraw_context(vote_pubkey, account);
5905            let ix = tx.get_next_instruction_context().unwrap();
5906
5907            assert_eq!(
5908                withdraw(
5909                    &ix,
5910                    0,
5911                    VoteStateTargetVersion::V4,
5912                    lamports,
5913                    1,
5914                    &signers,
5915                    &rent,
5916                    &clock
5917                ),
5918                Err(InstructionError::InsufficientFunds)
5919            );
5920        }
5921
5922        // Full close succeeds when pending = 0.
5923        {
5924            let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 0, 100);
5925            let withdrawer = *handler.authorized_withdrawer();
5926            let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5927            let lamports = rent.minimum_balance(VoteStateV4::size_of()) + 100;
5928            let tx = setup_withdraw_context(vote_pubkey, account);
5929            let ix = tx.get_next_instruction_context().unwrap();
5930
5931            withdraw(
5932                &ix,
5933                0,
5934                VoteStateTargetVersion::V4,
5935                lamports,
5936                1,
5937                &signers,
5938                &rent,
5939                &clock,
5940            )
5941            .unwrap();
5942        }
5943    }
5944
5945    /// Build a maximum-size V3 vote state with all variable-length
5946    /// collections at capacity (votes, epoch_credits, authorized_voters).
5947    fn get_max_sized_vote_state_v3() -> VoteStateV3 {
5948        let root_slot = 42u64;
5949        let votes: VecDeque<LandedVote> = (0..MAX_LOCKOUT_HISTORY)
5950            .map(|i| LandedVote {
5951                latency: i as u8,
5952                lockout: Lockout::new_with_confirmation_count(
5953                    root_slot + i as u64 + 1,
5954                    (MAX_LOCKOUT_HISTORY - i) as u32,
5955                ),
5956            })
5957            .collect();
5958        let epoch_credits: Vec<(u64, u64, u64)> = (0..MAX_EPOCH_CREDITS_HISTORY)
5959            .map(|i| (i as u64, (i as u64 + 1) * 100, i as u64 * 100))
5960            .collect();
5961        let mut authorized_voters = AuthorizedVoters::default();
5962        for i in 0..=solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
5963            authorized_voters.insert(i, solana_pubkey::new_rand());
5964        }
5965
5966        VoteStateV3 {
5967            node_pubkey: solana_pubkey::new_rand(),
5968            authorized_withdrawer: solana_pubkey::new_rand(),
5969            commission: 42,
5970            votes,
5971            root_slot: Some(root_slot),
5972            epoch_credits,
5973            authorized_voters,
5974            ..Default::default()
5975        }
5976    }
5977
5978    #[test]
5979    fn test_v3_to_v4_stale_trailing_bytes() {
5980        // V4 deserializer must ignore trailing bytes left over from a
5981        // V3 to V4 conversion in a fixed-size account buffer.
5982        //
5983        // The conversion and serialization is driven through the handler, ie.
5984        // `get_vote_state_handler_checked`/`try_convert_to_vote_state_v4`.
5985        //
5986        // We conduct this test through `get_vote_state_handler_checked` to
5987        // ensure we're testing program code.
5988        let vote_pubkey = solana_pubkey::new_rand();
5989        let v3 = get_max_sized_vote_state_v3();
5990        let node_pubkey = v3.node_pubkey;
5991        let authorized_withdrawer = v3.authorized_withdrawer;
5992        let commission = v3.commission;
5993        let root_slot = v3.root_slot;
5994        let votes = v3.votes.clone();
5995        let epoch_credits = v3.epoch_credits.clone();
5996        let authorized_voters = v3.authorized_voters.clone();
5997        let last_timestamp = v3.last_timestamp.clone();
5998
5999        // Serialize V3 into a fixed-size account buffer.
6000        let buf_size = VoteStateV3::size_of();
6001        let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6002        let v3_serialized_len = bincode::serialized_size(&v3_versioned).unwrap() as usize;
6003        let mut vote_account_data = vec![0u8; buf_size];
6004        bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6005
6006        // Drive V3 to V4 conversion through the program handler.
6007        let rent = Rent::default();
6008        let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6009        let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6010        vote_account.set_data_from_slice(&vote_account_data);
6011        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6012        let transaction_context = new_transaction_context(
6013            vec![(id(), program_account), (vote_pubkey, vote_account)],
6014            vec![InstructionAccount::new(1, false, true)],
6015            &rent,
6016        );
6017        let ix = transaction_context.get_next_instruction_context().unwrap();
6018        let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6019
6020        // `get_vote_state_handler_checked` with V4 target triggers the full
6021        // deser -> conversion path; `set_vote_account_state` writes it back.
6022        let vote_state =
6023            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6024        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6025
6026        // Inspect raw account data written by the handler.
6027        let account_data = borrowed.get_data();
6028        let v4_serialized_len = {
6029            let v4 = VoteStateV4::deserialize(account_data, &vote_pubkey).unwrap();
6030            bincode::serialized_size(&VoteStateVersions::new_v4(v4)).unwrap() as usize
6031        };
6032        assert!(
6033            v4_serialized_len < v3_serialized_len,
6034            "v4 ({v4_serialized_len}) should be smaller than v3 ({v3_serialized_len})",
6035        );
6036
6037        // The V4 deserializer must produce the correct state despite
6038        // trailing bytes left from the larger V3 serialization.
6039        let deserialized = VoteStateV4::deserialize(account_data, &vote_pubkey).unwrap();
6040        assert_eq!(deserialized.node_pubkey, node_pubkey);
6041        assert_eq!(deserialized.authorized_withdrawer, authorized_withdrawer);
6042        assert_eq!(deserialized.root_slot, root_slot);
6043        assert_eq!(deserialized.votes, votes);
6044        assert_eq!(deserialized.epoch_credits, epoch_credits);
6045        assert_eq!(deserialized.authorized_voters, authorized_voters);
6046        assert_eq!(
6047            deserialized.inflation_rewards_commission_bps,
6048            commission as u16 * 100
6049        );
6050        assert_eq!(deserialized.last_timestamp, last_timestamp);
6051
6052        // Fill the trailing region with non-zero garbage, then round-trip
6053        // through the handler again to verify the program handles it.
6054        borrowed.get_data_mut().unwrap()[v4_serialized_len..].fill(0xDE);
6055
6056        let vote_state =
6057            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6058        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6059
6060        let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6061        assert_eq!(deserialized.node_pubkey, node_pubkey);
6062        assert_eq!(deserialized.authorized_withdrawer, authorized_withdrawer);
6063        assert_eq!(deserialized.root_slot, root_slot);
6064        assert_eq!(deserialized.votes, votes);
6065        assert_eq!(deserialized.epoch_credits, epoch_credits);
6066        assert_eq!(deserialized.authorized_voters, authorized_voters);
6067        assert_eq!(
6068            deserialized.inflation_rewards_commission_bps,
6069            commission as u16 * 100
6070        );
6071        assert_eq!(deserialized.last_timestamp, last_timestamp);
6072    }
6073
6074    #[test]
6075    fn test_v3_to_v4_trailing_bytes_shrink_and_regrow() {
6076        // Exercises the full lifecycle of trailing-byte behavior in a
6077        // fixed-size account buffer, driven through the program handlers
6078        // `get_vote_state_handler_checked` and `set_vote_account_state`:
6079        // * Step 1: Start with a max-size V3 state (all collections full).
6080        // * Step 2: Convert V3 -> V4 via the handler. V4's serialized form
6081        //           is smaller, leaving trailing garbage.
6082        // * Step 3: Clear all votes, simulating the extreme case of a
6083        //           validator whose entire tower has expired, which shrinks
6084        //           the v4 vote state.
6085        // * Step 4: Re-add votes incrementally, round-tripping through the
6086        //           handler each time to verify the program handles the
6087        //           growing serialized region over stale trailing bytes.
6088
6089        let vote_pubkey = solana_pubkey::new_rand();
6090        let v3 = get_max_sized_vote_state_v3();
6091        let node_pubkey = v3.node_pubkey;
6092        let authorized_withdrawer = v3.authorized_withdrawer;
6093        let commission = v3.commission;
6094        let root_slot = v3.root_slot;
6095        let votes = v3.votes.clone();
6096        let epoch_credits = v3.epoch_credits.clone();
6097        let authorized_voters = v3.authorized_voters.clone();
6098        let last_timestamp = v3.last_timestamp.clone();
6099
6100        // Step 1: Populate V3 account and record V3 serialized size.
6101        let buf_size = VoteStateV3::size_of();
6102        let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6103        let v3_serialized_len = bincode::serialized_size(&v3_versioned).unwrap() as usize;
6104        let mut vote_account_data = vec![0u8; buf_size];
6105        bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6106
6107        let rent = Rent::default();
6108        let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6109        let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6110        vote_account.set_data_from_slice(&vote_account_data);
6111        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6112        let transaction_context = new_transaction_context(
6113            vec![(id(), program_account), (vote_pubkey, vote_account)],
6114            vec![InstructionAccount::new(1, false, true)],
6115            &rent,
6116        );
6117        let ix = transaction_context.get_next_instruction_context().unwrap();
6118        let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6119
6120        // Step 2: V3 -> V4 conversion via the handler.
6121        let vote_state =
6122            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6123        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6124
6125        let v4_after_convert = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6126        let v4_serialized_len =
6127            bincode::serialized_size(&VoteStateVersions::new_v4(v4_after_convert.clone())).unwrap()
6128                as usize;
6129
6130        assert!(
6131            v4_serialized_len < v3_serialized_len,
6132            "v4 ({v4_serialized_len}) should be smaller than v3 ({v3_serialized_len})",
6133        );
6134        let trailing_len_after_convert = buf_size - v4_serialized_len;
6135        assert!(
6136            trailing_len_after_convert > 0,
6137            "expected trailing bytes after v3 -> v4 conversion"
6138        );
6139
6140        // Verify field-level correctness of the converted state.
6141        assert_eq!(v4_after_convert.node_pubkey, node_pubkey);
6142        assert_eq!(
6143            v4_after_convert.authorized_withdrawer,
6144            authorized_withdrawer
6145        );
6146        assert_eq!(v4_after_convert.root_slot, root_slot);
6147        assert_eq!(v4_after_convert.votes, votes);
6148        assert_eq!(v4_after_convert.epoch_credits, epoch_credits);
6149        assert_eq!(v4_after_convert.authorized_voters, authorized_voters);
6150        assert_eq!(
6151            v4_after_convert.inflation_rewards_commission_bps,
6152            commission as u16 * 100,
6153        );
6154        assert_eq!(v4_after_convert.last_timestamp, last_timestamp);
6155
6156        // Step 3a: Clear all votes, round-trip with resulting stale bytes.
6157        let mut v4_empty_votes = v4_after_convert.clone();
6158        v4_empty_votes.votes.clear();
6159        let v4_empty_serialized_len =
6160            bincode::serialized_size(&VoteStateVersions::new_v4(v4_empty_votes.clone())).unwrap()
6161                as usize;
6162        assert!(
6163            v4_empty_serialized_len < v4_serialized_len,
6164            "empty-votes v4 ({v4_empty_serialized_len}) should be smaller than full v4 \
6165             ({v4_serialized_len})",
6166        );
6167
6168        // Write the vote-cleared state. The trailing region now contains
6169        // stale bytes from the previous (larger) V4 serialization.
6170        borrowed
6171            .set_state(&VoteStateVersions::new_v4(v4_empty_votes.clone()))
6172            .unwrap();
6173        let trailing_len_after_clear = buf_size - v4_empty_serialized_len;
6174        assert!(
6175            trailing_len_after_clear > trailing_len_after_convert,
6176            "trailing region should grow after clearing votes: {trailing_len_after_clear} vs \
6177             {trailing_len_after_convert}",
6178        );
6179
6180        // Round-trip through the handler with the stale bytes.
6181        let vote_state =
6182            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6183        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6184
6185        let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6186        assert!(deserialized.votes.is_empty(),);
6187        assert_eq!(deserialized.epoch_credits.len(), MAX_EPOCH_CREDITS_HISTORY,);
6188        assert_eq!(deserialized.authorized_voters, authorized_voters,);
6189        assert_eq!(deserialized.last_timestamp, last_timestamp);
6190
6191        // Step 3b: Fill trailing with explicit garbage and round-trip again.
6192        //
6193        // Overwrite the trailing region with a non-zero pattern to verify
6194        // the handler is not sensitive to arbitrary trailing content.
6195        borrowed.get_data_mut().unwrap()[v4_empty_serialized_len..].fill(0xCD);
6196
6197        let vote_state =
6198            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6199        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6200
6201        let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6202        assert!(deserialized.votes.is_empty());
6203        assert_eq!(deserialized.epoch_credits.len(), MAX_EPOCH_CREDITS_HISTORY);
6204        assert_eq!(deserialized.authorized_voters, authorized_voters);
6205        assert_eq!(deserialized.last_timestamp, last_timestamp);
6206
6207        // Step 4: Re-add votes, growing the serialized region.
6208        //
6209        // Incrementally add votes back, writing each state and filling
6210        // trailing with garbage, then round-tripping through the handler
6211        // to verify it handles the growing data region correctly.
6212        let mut v4_regrowing = v4_empty_votes;
6213        for i in 0..MAX_LOCKOUT_HISTORY {
6214            v4_regrowing.votes.push_back(LandedVote {
6215                latency: (i % 256) as u8,
6216                lockout: Lockout::new_with_confirmation_count(
6217                    root_slot.unwrap() + 1000 + i as u64,
6218                    (MAX_LOCKOUT_HISTORY - i) as u32,
6219                ),
6220            });
6221
6222            // Write the updated state, fill trailing with garbage.
6223            borrowed
6224                .set_state(&VoteStateVersions::new_v4(v4_regrowing.clone()))
6225                .unwrap();
6226            let current_serialized_len =
6227                bincode::serialized_size(&VoteStateVersions::new_v4(v4_regrowing.clone())).unwrap()
6228                    as usize;
6229            let current_trailing = buf_size - current_serialized_len;
6230            assert!(
6231                current_trailing < trailing_len_after_clear,
6232                "trailing region should shrink as votes are added"
6233            );
6234            if current_serialized_len < buf_size {
6235                borrowed.get_data_mut().unwrap()[current_serialized_len..].fill(0xEF);
6236            }
6237
6238            // Round-trip through the handler to verify.
6239            let vote_state =
6240                get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6241            vote_state.set_vote_account_state(&mut borrowed).unwrap();
6242
6243            let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6244            assert_eq!(
6245                deserialized.votes.len(),
6246                i + 1,
6247                "expected {} votes after re-adding",
6248                i + 1,
6249            );
6250            assert_eq!(deserialized, v4_regrowing);
6251        }
6252
6253        // Final consistency check: all votes are back and all fields correct.
6254        let final_deserialized =
6255            VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6256        assert_eq!(final_deserialized.votes.len(), MAX_LOCKOUT_HISTORY);
6257        assert_eq!(final_deserialized.epoch_credits, epoch_credits);
6258        assert_eq!(final_deserialized.authorized_voters, authorized_voters);
6259        assert_eq!(final_deserialized.last_timestamp, last_timestamp);
6260    }
6261
6262    #[test]
6263    fn test_bls_absent_after_v3_to_v4_migration() {
6264        // V3 to V4 migration via get_vote_state_handler_checked must
6265        // produce bls_pubkey_compressed = None.
6266        let vote_pubkey = solana_pubkey::new_rand();
6267        let v3 = VoteStateV3::new(
6268            &VoteInit {
6269                node_pubkey: solana_pubkey::new_rand(),
6270                authorized_voter: solana_pubkey::new_rand(),
6271                authorized_withdrawer: solana_pubkey::new_rand(),
6272                commission: 10,
6273            },
6274            &Clock::default(),
6275        );
6276        let last_timestamp = v3.last_timestamp.clone();
6277
6278        // Serialize V3 into an account.
6279        let buf_size = VoteStateV3::size_of();
6280        let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6281        let mut vote_account_data = vec![0u8; buf_size];
6282        bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6283
6284        let rent = Rent::default();
6285        let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6286        let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6287        vote_account.set_data_from_slice(&vote_account_data);
6288        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6289        let transaction_context = new_transaction_context(
6290            vec![(id(), program_account), (vote_pubkey, vote_account)],
6291            vec![InstructionAccount::new(1, false, true)],
6292            &rent,
6293        );
6294        let ix = transaction_context.get_next_instruction_context().unwrap();
6295        let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6296
6297        // Drive conversion through the handler.
6298        let vote_state =
6299            get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6300        vote_state.set_vote_account_state(&mut borrowed).unwrap();
6301
6302        let v4 = VoteStateHandler::new_v4(
6303            VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap(),
6304        );
6305        assert_eq!(v4.as_ref_v4().bls_pubkey_compressed, None);
6306        assert!(!v4.has_bls_pubkey());
6307        assert_eq!(v4.as_ref_v4().last_timestamp, last_timestamp);
6308    }
6309
6310    #[test]
6311    fn test_bls_overwrite_via_voter_with_bls() {
6312        // BLS pubkey set to A, then overwritten to B via VoterWithBLS.
6313        let vote_pubkey = Pubkey::new_unique();
6314        let (bls_a, pop_a) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
6315        let (bls_b, pop_b) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
6316
6317        let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6318        let withdrawer = *vote_state.authorized_withdrawer();
6319        let node_pubkey = *vote_state.node_pubkey();
6320        let serialized = vote_state.serialize();
6321        let rent = Rent::default();
6322        let lamports = rent.minimum_balance(serialized.len());
6323        let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6324        vote_account.set_data_from_slice(&serialized);
6325        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6326        let transaction_context = new_transaction_context(
6327            vec![(id(), program_account), (vote_pubkey, vote_account)],
6328            vec![InstructionAccount::new(1, false, true)],
6329            &rent,
6330        );
6331        let ix = transaction_context.get_next_instruction_context().unwrap();
6332        let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6333
6334        let signers: HashSet<Pubkey> = [withdrawer, Pubkey::new_unique()].into_iter().collect();
6335
6336        // Set BLS A.
6337        authorize(
6338            &mut borrowed,
6339            VoteStateTargetVersion::V4,
6340            &Pubkey::new_unique(),
6341            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6342                bls_pubkey: bls_a,
6343                bls_proof_of_possession: pop_a,
6344            }),
6345            &signers,
6346            &Clock::default(),
6347            true,
6348            || Ok(()),
6349        )
6350        .unwrap();
6351
6352        let v4 = VoteStateV4::deserialize(borrowed.get_data(), &node_pubkey).unwrap();
6353        assert_eq!(v4.bls_pubkey_compressed, Some(bls_a));
6354
6355        // Overwrite with BLS B.
6356        let clock = Clock {
6357            epoch: 3,
6358            ..Clock::default()
6359        };
6360        authorize(
6361            &mut borrowed,
6362            VoteStateTargetVersion::V4,
6363            &Pubkey::new_unique(),
6364            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6365                bls_pubkey: bls_b,
6366                bls_proof_of_possession: pop_b,
6367            }),
6368            &signers,
6369            &clock,
6370            true,
6371            || Ok(()),
6372        )
6373        .unwrap();
6374
6375        let v4 = VoteStateV4::deserialize(borrowed.get_data(), &node_pubkey).unwrap();
6376        assert_eq!(v4.bls_pubkey_compressed, Some(bls_b));
6377    }
6378
6379    #[test]
6380    fn test_bls_pop_cryptographic_failures() {
6381        // Invalid PoP scenarios: zero bytes, garbage bytes, and
6382        // PoP bound to the wrong vote account.
6383        let vote_pubkey = Pubkey::new_unique();
6384        let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6385        let withdrawer = *vote_state.authorized_withdrawer();
6386        let serialized = vote_state.serialize();
6387        let rent = Rent::default();
6388        let lamports = rent.minimum_balance(serialized.len());
6389        let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6390        vote_account.set_data_from_slice(&serialized);
6391        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6392        let transaction_context = new_transaction_context(
6393            vec![(id(), program_account), (vote_pubkey, vote_account)],
6394            vec![InstructionAccount::new(1, false, true)],
6395            &rent,
6396        );
6397        let ix = transaction_context.get_next_instruction_context().unwrap();
6398        let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6399        let signers: HashSet<Pubkey> = [withdrawer, Pubkey::new_unique()].into_iter().collect();
6400        let clock = Clock::default();
6401
6402        // All-zero BLS pubkey + PoP.
6403        assert_eq!(
6404            authorize(
6405                &mut borrowed,
6406                VoteStateTargetVersion::V4,
6407                &Pubkey::new_unique(),
6408                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6409                    bls_pubkey: [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
6410                    bls_proof_of_possession: [0u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
6411                }),
6412                &signers,
6413                &clock,
6414                true,
6415                || Ok(()),
6416            ),
6417            Err(InstructionError::InvalidArgument)
6418        );
6419
6420        // Random garbage BLS pubkey + PoP.
6421        assert_eq!(
6422            authorize(
6423                &mut borrowed,
6424                VoteStateTargetVersion::V4,
6425                &Pubkey::new_unique(),
6426                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6427                    bls_pubkey: [0xAB; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
6428                    bls_proof_of_possession: [0xCD; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
6429                }),
6430                &signers,
6431                &clock,
6432                true,
6433                || Ok(()),
6434            ),
6435            Err(InstructionError::InvalidArgument)
6436        );
6437
6438        // Valid BLS pubkey but PoP for wrong vote account.
6439        let other_vote = Pubkey::new_unique();
6440        let (bls_for_other, pop_for_other) = create_bls_pubkey_and_proof_of_possession(&other_vote);
6441        assert_eq!(
6442            authorize(
6443                &mut borrowed,
6444                VoteStateTargetVersion::V4,
6445                &Pubkey::new_unique(),
6446                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6447                    bls_pubkey: bls_for_other,
6448                    bls_proof_of_possession: pop_for_other,
6449                }),
6450                &signers,
6451                &clock,
6452                true,
6453                || Ok(()),
6454            ),
6455            Err(InstructionError::InvalidArgument)
6456        );
6457    }
6458
6459    #[test]
6460    fn test_collector_fields_immutable_in_v4_only_scope() {
6461        // Verify `inflation_rewards_collector` and `block_revenue_collector`
6462        // are not modified by `Authorize` or `UpdateCommission` post-v4.
6463        let vote_pubkey = solana_pubkey::new_rand();
6464        let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6465        let withdrawer = *vote_state.authorized_withdrawer();
6466        let original_inflation_collector = vote_state.as_ref_v4().inflation_rewards_collector;
6467        let original_block_revenue_collector = vote_state.as_ref_v4().block_revenue_collector;
6468
6469        let serialized = vote_state.clone().serialize();
6470        let rent = Rent::default();
6471        let lamports = rent.minimum_balance(serialized.len());
6472        let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6473        vote_account.set_data_from_slice(&serialized);
6474        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6475        let transaction_context = new_transaction_context(
6476            vec![(id(), program_account), (vote_pubkey, vote_account)],
6477            vec![InstructionAccount::new(1, false, true)],
6478            &rent,
6479        );
6480        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
6481        let mut borrowed = instruction_context
6482            .try_borrow_instruction_account(0)
6483            .unwrap();
6484
6485        let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
6486        let clock = Clock::default();
6487
6488        // Authorize: should not change collectors.
6489        authorize(
6490            &mut borrowed,
6491            VoteStateTargetVersion::V4,
6492            &solana_pubkey::new_rand(),
6493            VoteAuthorize::Voter,
6494            &signers,
6495            &clock,
6496            false,
6497            || Ok(()),
6498        )
6499        .unwrap();
6500
6501        let v4 = VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6502            .unwrap();
6503        assert_eq!(v4.inflation_rewards_collector, original_inflation_collector);
6504        assert_eq!(v4.block_revenue_collector, original_block_revenue_collector);
6505
6506        // UpdateCommission: should not change collectors.
6507        update_commission(
6508            &mut borrowed,
6509            VoteStateTargetVersion::V4,
6510            50,
6511            &signers,
6512            &solana_epoch_schedule::EpochSchedule::without_warmup(),
6513            &clock,
6514            false,
6515        )
6516        .unwrap();
6517
6518        let v4 = VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6519            .unwrap();
6520        assert_eq!(v4.inflation_rewards_collector, original_inflation_collector);
6521        assert_eq!(v4.block_revenue_collector, original_block_revenue_collector);
6522    }
6523
6524    #[test]
6525    fn test_pending_delegator_rewards_zero_in_v4_only_scope() {
6526        // Verify `pending_delegator_rewards` stays 0 through post-v4
6527        // instructions (SIMD-0123 not active).
6528        let vote_pubkey = solana_pubkey::new_rand();
6529        let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6530        let withdrawer = *vote_state.authorized_withdrawer();
6531
6532        let serialized = vote_state.clone().serialize();
6533        let rent = Rent::default();
6534        let lamports = rent.minimum_balance(serialized.len());
6535        let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6536        vote_account.set_data_from_slice(&serialized);
6537        let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6538        let transaction_context = new_transaction_context(
6539            vec![(id(), program_account), (vote_pubkey, vote_account)],
6540            vec![InstructionAccount::new(1, false, true)],
6541            &rent,
6542        );
6543        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
6544        let mut borrowed = instruction_context
6545            .try_borrow_instruction_account(0)
6546            .unwrap();
6547
6548        assert_eq!(
6549            VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6550                .unwrap()
6551                .pending_delegator_rewards,
6552            0
6553        );
6554
6555        // Authorize: pending should stay 0.
6556        let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
6557        authorize(
6558            &mut borrowed,
6559            VoteStateTargetVersion::V4,
6560            &solana_pubkey::new_rand(),
6561            VoteAuthorize::Voter,
6562            &signers,
6563            &Clock::default(),
6564            false,
6565            || Ok(()),
6566        )
6567        .unwrap();
6568
6569        assert_eq!(
6570            VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6571                .unwrap()
6572                .pending_delegator_rewards,
6573            0
6574        );
6575    }
6576}