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