Skip to main content

solana_vote_program/vote_state/
handler.rs

1//! Vote state handler API.
2//!
3//! Wraps the vote state behind a "handler" API to support converting from an
4//! existing vote state version to whichever version is the target (or
5//! "current") vote state version.
6//!
7//! The program must be generic over whichever vote state version is the
8//! target, since at compile time the target version is not known (can be
9//! changed with a feature gate). For this reason, the handler offers a
10//! getter and setter API around vote state, for all operations required by the
11//! vote program.
12
13#[cfg(feature = "dev-context-only-utils")]
14use qualifier_attr::qualifiers;
15#[cfg(test)]
16use solana_vote_interface::authorized_voters::AuthorizedVoters;
17use {
18    solana_clock::{Clock, Epoch, Slot, UnixTimestamp},
19    solana_instruction::error::InstructionError,
20    solana_pubkey::Pubkey,
21    solana_transaction_context::instruction_accounts::BorrowedInstructionAccount,
22    solana_vote_interface::{
23        error::VoteError,
24        state::{
25            BLS_PUBLIC_KEY_COMPRESSED_SIZE, BlockTimestamp, LandedVote, Lockout,
26            MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, VOTE_CREDITS_GRACE_SLOTS,
27            VOTE_CREDITS_MAXIMUM_PER_SLOT, VoteInit, VoteInitV2, VoteState1_14_11, VoteStateV3,
28            VoteStateV4,
29        },
30    },
31    std::collections::VecDeque,
32    wincode::{SchemaRead, SchemaWrite},
33};
34
35/// Copy of `VoteStateVersions` except that state is without a `Box`.
36/// Eliminates the cost of the `Box` when deserializing a `VoteAccount`.
37#[derive(Debug, PartialEq, Eq, SchemaRead)]
38pub enum VoteStateVersionsInline {
39    Uninitialized,
40    V1_14_11(VoteState1_14_11),
41    V3(VoteStateV3),
42    V4(VoteStateV4),
43}
44
45impl VoteStateVersionsInline {
46    pub(crate) fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
47        let versioned =
48            wincode::deserialize(input).map_err(|_| InstructionError::InvalidAccountData)?;
49        match versioned {
50            // Preserve VoteStateVersions::deserialize's rejection of discriminant 0.
51            Self::Uninitialized => Err(InstructionError::InvalidAccountData),
52            _ => Ok(versioned),
53        }
54    }
55
56    pub fn is_uninitialized(&self) -> bool {
57        match self {
58            Self::Uninitialized => true,
59            Self::V1_14_11(vote_state) => vote_state.is_uninitialized(),
60            Self::V3(vote_state) => vote_state.is_uninitialized(),
61            // As per SIMD-0185, v4 is always initialized.
62            Self::V4(_) => false,
63        }
64    }
65}
66
67/// Copy of `VoteStateVersions` except that references to state are stored.
68/// Allows for faster serialization as data doesn't have to be moved around.
69#[derive(SchemaWrite)]
70enum VoteStateVersionsRef<'a> {
71    #[wincode(tag = 3)]
72    V4(&'a VoteStateV4),
73}
74
75/// The target version to convert all deserialized vote state into.
76#[derive(Clone, Copy, Debug, PartialEq)]
77pub enum VoteStateTargetVersion {
78    V4,
79    // New vote state versions will be added here...
80}
81
82#[allow(clippy::large_enum_variant)]
83#[derive(Clone, Debug, PartialEq)]
84enum TargetVoteState {
85    V4(VoteStateV4),
86    // New vote state versions will be added here...
87}
88
89/// Vote state handler for
90/// * Deserializing vote state
91/// * Converting vote state in-memory to target version
92/// * Operating on the vote state data agnostically
93/// * Serializing the resulting state to the vote account
94#[derive(Clone, Debug, PartialEq)]
95pub struct VoteStateHandler {
96    target_state: TargetVoteState,
97}
98
99impl VoteStateHandler {
100    pub(crate) fn authorized_withdrawer(&self) -> &Pubkey {
101        match &self.target_state {
102            TargetVoteState::V4(v4) => &v4.authorized_withdrawer,
103        }
104    }
105
106    pub(crate) fn set_authorized_withdrawer(&mut self, authorized_withdrawer: Pubkey) {
107        match &mut self.target_state {
108            TargetVoteState::V4(v4) => v4.authorized_withdrawer = authorized_withdrawer,
109        }
110    }
111
112    #[cfg(test)]
113    pub(crate) fn authorized_voters(&self) -> &AuthorizedVoters {
114        match &self.target_state {
115            TargetVoteState::V4(v4) => &v4.authorized_voters,
116        }
117    }
118
119    pub(crate) fn set_new_authorized_voter<F>(
120        &mut self,
121        authorized_pubkey: &Pubkey,
122        current_epoch: Epoch,
123        target_epoch: Epoch,
124        bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
125        verify: F,
126    ) -> Result<(), InstructionError>
127    where
128        F: Fn(Pubkey) -> Result<(), InstructionError>,
129    {
130        let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
131        verify(epoch_authorized_voter)?;
132
133        match &mut self.target_state {
134            TargetVoteState::V4(v4) => {
135                // The offset in slots `n` on which the target_epoch
136                // (default value `DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET`) is
137                // calculated is the number of slots available from the
138                // first slot `S` of an epoch in which to set a new voter for
139                // the epoch at `S` + `n`
140                if v4.authorized_voters.contains(target_epoch) {
141                    return Err(VoteError::TooSoonToReauthorize.into());
142                }
143
144                v4.authorized_voters
145                    .insert(target_epoch, *authorized_pubkey);
146
147                if bls_pubkey.is_some() {
148                    v4.bls_pubkey_compressed = bls_pubkey.copied();
149                }
150
151                Ok(())
152            }
153        }
154    }
155
156    pub(crate) fn get_and_update_authorized_voter(
157        &mut self,
158        current_epoch: Epoch,
159    ) -> Result<Pubkey, InstructionError> {
160        match &mut self.target_state {
161            TargetVoteState::V4(v4) => {
162                let pubkey = v4
163                    .authorized_voters
164                    .get_and_cache_authorized_voter_for_epoch(current_epoch)
165                    .ok_or(InstructionError::InvalidAccountData)?;
166                // Per SIMD-0185, v4 retains voters for `current_epoch - 1` through
167                // `current_epoch + 2`. Only purge entries for epochs less than
168                // `current_epoch - 1`.
169                v4.authorized_voters
170                    .purge_authorized_voters(current_epoch.saturating_sub(1));
171                Ok(pubkey)
172            }
173        }
174    }
175
176    pub(crate) fn commission(&self) -> u8 {
177        match &self.target_state {
178            TargetVoteState::V4(v4) => {
179                (v4.inflation_rewards_commission_bps / 100).min(u8::MAX as u16) as u8
180            }
181        }
182    }
183
184    #[allow(clippy::arithmetic_side_effects)]
185    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
186    pub(crate) fn set_commission(&mut self, commission: u8) {
187        match &mut self.target_state {
188            TargetVoteState::V4(v4) => {
189                // Safety: u16::MAX > u8::MAX * 100
190                v4.inflation_rewards_commission_bps = (commission as u16) * 100;
191            }
192        }
193    }
194
195    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
196    pub(crate) fn set_inflation_rewards_commission_bps(&mut self, commission_bps: u16) {
197        match &mut self.target_state {
198            TargetVoteState::V4(v4) => v4.inflation_rewards_commission_bps = commission_bps,
199        }
200    }
201
202    pub(crate) fn set_block_revenue_commission_bps(&mut self, commission_bps: u16) {
203        match &mut self.target_state {
204            TargetVoteState::V4(v4) => v4.block_revenue_commission_bps = commission_bps,
205        }
206    }
207
208    pub fn node_pubkey(&self) -> &Pubkey {
209        match &self.target_state {
210            TargetVoteState::V4(v4) => &v4.node_pubkey,
211        }
212    }
213
214    pub(crate) fn set_node_pubkey(&mut self, node_pubkey: Pubkey) {
215        match &mut self.target_state {
216            TargetVoteState::V4(v4) => v4.node_pubkey = node_pubkey,
217        }
218    }
219
220    pub(crate) fn set_inflation_rewards_collector(&mut self, collector: Pubkey) {
221        match &mut self.target_state {
222            TargetVoteState::V4(v4) => v4.inflation_rewards_collector = collector,
223        }
224    }
225
226    pub(crate) fn set_block_revenue_collector(&mut self, collector: Pubkey) {
227        match &mut self.target_state {
228            TargetVoteState::V4(v4) => v4.block_revenue_collector = collector,
229        }
230    }
231
232    pub(crate) fn pending_delegator_rewards(&self) -> u64 {
233        match &self.target_state {
234            TargetVoteState::V4(v4) => v4.pending_delegator_rewards,
235        }
236    }
237
238    pub(crate) fn add_pending_delegator_rewards(
239        &mut self,
240        amount: u64,
241    ) -> Result<(), InstructionError> {
242        match &mut self.target_state {
243            TargetVoteState::V4(v4) => {
244                v4.pending_delegator_rewards = v4
245                    .pending_delegator_rewards
246                    .checked_add(amount)
247                    .ok_or(InstructionError::ArithmeticOverflow)?;
248                Ok(())
249            }
250        }
251    }
252
253    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
254    pub(crate) fn votes(&self) -> &VecDeque<LandedVote> {
255        match &self.target_state {
256            TargetVoteState::V4(v4) => &v4.votes,
257        }
258    }
259
260    pub(crate) fn votes_mut(&mut self) -> &mut VecDeque<LandedVote> {
261        match &mut self.target_state {
262            TargetVoteState::V4(v4) => &mut v4.votes,
263        }
264    }
265
266    pub fn set_votes(&mut self, votes: VecDeque<LandedVote>) {
267        match &mut self.target_state {
268            TargetVoteState::V4(v4) => v4.votes = votes,
269        }
270    }
271
272    pub(crate) fn contains_slot(&self, candidate_slot: Slot) -> bool {
273        match &self.target_state {
274            TargetVoteState::V4(v4) => v4
275                .votes
276                .binary_search_by(|vote| vote.slot().cmp(&candidate_slot))
277                .is_ok(),
278        }
279    }
280
281    pub(crate) fn last_lockout(&self) -> Option<&Lockout> {
282        match &self.target_state {
283            TargetVoteState::V4(v4) => v4.votes.back().map(|vote| &vote.lockout),
284        }
285    }
286
287    pub fn last_voted_slot(&self) -> Option<Slot> {
288        self.last_lockout().map(|v| v.slot())
289    }
290
291    pub fn root_slot(&self) -> Option<Slot> {
292        match &self.target_state {
293            TargetVoteState::V4(v4) => v4.root_slot,
294        }
295    }
296
297    pub fn set_root_slot(&mut self, root_slot: Option<Slot>) {
298        match &mut self.target_state {
299            TargetVoteState::V4(v4) => v4.root_slot = root_slot,
300        }
301    }
302
303    pub(crate) fn current_epoch(&self) -> Epoch {
304        match &self.target_state {
305            TargetVoteState::V4(v4) => {
306                if v4.epoch_credits.is_empty() {
307                    0
308                } else {
309                    v4.epoch_credits.last().unwrap().0
310                }
311            }
312        }
313    }
314
315    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
316    pub(crate) fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
317        match &self.target_state {
318            TargetVoteState::V4(v4) => &v4.epoch_credits,
319        }
320    }
321
322    pub fn epoch_credits_mut(&mut self) -> &mut Vec<(Epoch, u64, u64)> {
323        match &mut self.target_state {
324            TargetVoteState::V4(v4) => &mut v4.epoch_credits,
325        }
326    }
327
328    pub fn last_timestamp(&self) -> &BlockTimestamp {
329        match &self.target_state {
330            TargetVoteState::V4(v4) => &v4.last_timestamp,
331        }
332    }
333
334    pub fn set_last_timestamp(&mut self, timestamp: BlockTimestamp) {
335        match &mut self.target_state {
336            TargetVoteState::V4(v4) => v4.last_timestamp = timestamp,
337        }
338    }
339
340    pub(crate) fn set_vote_account_state(
341        &self,
342        vote_account: &mut BorrowedInstructionAccount,
343    ) -> Result<(), InstructionError> {
344        match &self.target_state {
345            TargetVoteState::V4(v4) => {
346                // If the account is not large enough to store the vote state, then attempt a realloc to make it large enough.
347                // The realloc can only proceed if the vote account has balance sufficient for rent exemption at the new size.
348                if (vote_account.get_data().len() < VoteStateV4::size_of())
349                    && (!vote_account.is_rent_exempt_at_data_length(VoteStateV4::size_of())
350                        || vote_account
351                            .set_data_length(VoteStateV4::size_of())
352                            .is_err())
353                {
354                    // Unlike with conversions to v3, we will not gracefully default to
355                    // storing a v1_14_11. Instead, throw an error, as per SIMD-0185.
356                    return Err(InstructionError::AccountNotRentExempt);
357                }
358                // Vote account is large enough to store the newest version of vote state
359                let state = VoteStateVersionsRef::V4(v4);
360                let data = vote_account.get_data_mut()?;
361                let serialized_size =
362                    wincode::serialized_size(&state).map_err(|_| InstructionError::GenericError)?;
363                if serialized_size > data.len() as u64 {
364                    return Err(InstructionError::AccountDataTooSmall);
365                }
366                wincode::serialize_into(data, &state).map_err(|_| InstructionError::GenericError)
367            }
368        }
369    }
370
371    pub(crate) fn has_bls_pubkey(&self) -> bool {
372        match &self.target_state {
373            TargetVoteState::V4(v4) => v4.bls_pubkey_compressed.is_some(),
374        }
375    }
376
377    pub fn init_vote_account_state(
378        vote_account: &mut BorrowedInstructionAccount,
379        vote_init: &VoteInit,
380        clock: &Clock,
381        target_version: VoteStateTargetVersion,
382    ) -> Result<(), InstructionError> {
383        let handler = match target_version {
384            VoteStateTargetVersion::V4 => {
385                let vote_state =
386                    VoteStateV4::new_with_defaults(vote_account.get_key(), vote_init, clock);
387                Self::new_v4(vote_state)
388            }
389        };
390        handler.set_vote_account_state(vote_account)
391    }
392
393    pub fn init_vote_account_state_v2(
394        vote_account: &mut BorrowedInstructionAccount,
395        vote_init: &VoteInitV2,
396        inflation_rewards_collector: &Pubkey,
397        block_revenue_collector: &Pubkey,
398        clock: &Clock,
399        target_version: VoteStateTargetVersion,
400    ) -> Result<(), InstructionError> {
401        let handler = match target_version {
402            VoteStateTargetVersion::V4 => {
403                let vote_state = VoteStateV4::new(
404                    vote_init,
405                    inflation_rewards_collector,
406                    block_revenue_collector,
407                    clock,
408                );
409                Self::new_v4(vote_state)
410            }
411        };
412        handler.set_vote_account_state(vote_account)
413    }
414
415    pub fn deinitialize_vote_account_state(
416        vote_account: &mut BorrowedInstructionAccount,
417        target_version: VoteStateTargetVersion,
418    ) -> Result<(), InstructionError> {
419        match target_version {
420            VoteStateTargetVersion::V4 => {
421                // As per SIMD-0185, clear the entire account.
422                vote_account.get_data_mut()?.fill(0);
423                Ok(())
424            }
425        }
426    }
427
428    pub fn check_vote_account_length(
429        vote_account: &mut BorrowedInstructionAccount,
430        target_version: VoteStateTargetVersion,
431    ) -> Result<(), InstructionError> {
432        let length = vote_account.get_data().len();
433        let expected = match target_version {
434            VoteStateTargetVersion::V4 => VoteStateV4::size_of(),
435        };
436        if length != expected {
437            Err(InstructionError::InvalidAccountData)
438        } else {
439            Ok(())
440        }
441    }
442
443    pub(crate) fn credits_for_vote_at_index(&self, index: usize) -> u64 {
444        let latency = self
445            .votes()
446            .get(index)
447            .map_or(0, |landed_vote| landed_vote.latency);
448
449        // If latency is 0, this means that the Lockout was created and stored from a software version that did not
450        // store vote latencies; in this case, 1 credit is awarded
451        if latency == 0 {
452            1
453        } else {
454            match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) {
455                None | Some(0) => {
456                    // latency was <= VOTE_CREDITS_GRACE_SLOTS, so maximum credits are awarded
457                    VOTE_CREDITS_MAXIMUM_PER_SLOT as u64
458                }
459
460                Some(diff) => {
461                    // diff = latency - VOTE_CREDITS_GRACE_SLOTS, and diff > 0
462                    // Subtract diff from VOTE_CREDITS_MAXIMUM_PER_SLOT which is the number of credits to award
463                    match VOTE_CREDITS_MAXIMUM_PER_SLOT.checked_sub(diff) {
464                        // If diff >= VOTE_CREDITS_MAXIMUM_PER_SLOT, 1 credit is awarded
465                        None | Some(0) => 1,
466
467                        Some(credits) => credits as u64,
468                    }
469                }
470            }
471        }
472    }
473
474    pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
475        // increment credits, record by epoch
476
477        // never seen a credit
478        if self.epoch_credits().is_empty() {
479            self.epoch_credits_mut().push((epoch, 0, 0));
480        } else if epoch != self.epoch_credits().last().unwrap().0 {
481            let (_, credits, prev_credits) = *self.epoch_credits().last().unwrap();
482
483            if credits != prev_credits {
484                // if credits were earned previous epoch
485                // append entry at end of list for the new epoch
486                self.epoch_credits_mut().push((epoch, credits, credits));
487            } else {
488                // else just move the current epoch
489                self.epoch_credits_mut().last_mut().unwrap().0 = epoch;
490            }
491
492            // Remove too old epoch_credits
493            if self.epoch_credits().len() > MAX_EPOCH_CREDITS_HISTORY {
494                self.epoch_credits_mut().remove(0);
495            }
496        }
497
498        self.epoch_credits_mut().last_mut().unwrap().1 = self
499            .epoch_credits()
500            .last()
501            .unwrap()
502            .1
503            .saturating_add(credits);
504    }
505
506    pub(crate) fn process_timestamp(
507        &mut self,
508        slot: Slot,
509        timestamp: UnixTimestamp,
510    ) -> Result<(), VoteError> {
511        let last_timestamp = self.last_timestamp();
512        if (slot < last_timestamp.slot || timestamp < last_timestamp.timestamp)
513            || (slot == last_timestamp.slot
514                && &BlockTimestamp { slot, timestamp } != last_timestamp
515                && last_timestamp.slot != 0)
516        {
517            return Err(VoteError::TimestampTooOld);
518        }
519        self.set_last_timestamp(BlockTimestamp { slot, timestamp });
520        Ok(())
521    }
522
523    fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
524        while let Some(vote) = self.last_lockout() {
525            if !vote.is_locked_out_at_slot(next_vote_slot) {
526                self.votes_mut().pop_back();
527            } else {
528                break;
529            }
530        }
531    }
532
533    fn double_lockouts(&mut self) {
534        let stack_depth = self.votes().len();
535        for (i, v) in self.votes_mut().iter_mut().enumerate() {
536            // Don't increase the lockout for this vote until we get more confirmations
537            // than the max number of confirmations this vote has seen
538            if stack_depth
539                > i.checked_add(v.confirmation_count() as usize).expect(
540                    "`confirmation_count` and tower_size should be bounded by \
541                     `MAX_LOCKOUT_HISTORY`",
542                )
543            {
544                v.lockout.increase_confirmation_count(1);
545            }
546        }
547    }
548
549    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
550    pub(crate) fn process_next_vote_slot(
551        &mut self,
552        next_vote_slot: Slot,
553        epoch: Epoch,
554        current_slot: Slot,
555    ) {
556        // Ignore votes for slots earlier than we already have votes for
557        if self
558            .last_voted_slot()
559            .is_some_and(|last_voted_slot| next_vote_slot <= last_voted_slot)
560        {
561            return;
562        }
563
564        self.pop_expired_votes(next_vote_slot);
565
566        let landed_vote = LandedVote {
567            latency: compute_vote_latency(next_vote_slot, current_slot),
568            lockout: Lockout::new(next_vote_slot),
569        };
570
571        // Once the stack is full, pop the oldest lockout and distribute rewards
572        if self.votes().len() == MAX_LOCKOUT_HISTORY {
573            let credits = self.credits_for_vote_at_index(0);
574            let landed_vote = self.votes_mut().pop_front().unwrap();
575            self.set_root_slot(Some(landed_vote.slot()));
576
577            self.increment_credits(epoch, credits);
578        }
579        self.votes_mut().push_back(landed_vote);
580        self.double_lockouts();
581    }
582
583    #[cfg(test)]
584    pub(crate) fn credits(&self) -> u64 {
585        if self.epoch_credits().is_empty() {
586            0
587        } else {
588            self.epoch_credits().last().unwrap().1
589        }
590    }
591
592    #[cfg(test)]
593    pub(crate) fn nth_recent_lockout(&self, position: usize) -> Option<&Lockout> {
594        if position < self.votes().len() {
595            let pos = self
596                .votes()
597                .len()
598                .checked_sub(position)
599                .and_then(|pos| pos.checked_sub(1))?;
600            self.votes().get(pos).map(|vote| &vote.lockout)
601        } else {
602            None
603        }
604    }
605
606    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
607    pub(crate) fn new_v4(vote_state: VoteStateV4) -> Self {
608        Self {
609            target_state: TargetVoteState::V4(vote_state),
610        }
611    }
612
613    /// Constructs a `VoteStateHandler` of the same version as the input `VoteStateVersions`.
614    ///
615    /// Returns `Err(InstructionError::InvalidAccountData)` if the version of the input
616    /// `VoteStateVersions` is not supported by `VoteStateHandler`.
617    pub fn try_new_from_vote_state_versions(
618        versions: VoteStateVersionsInline,
619    ) -> Result<Self, InstructionError> {
620        match versions {
621            VoteStateVersionsInline::V4(state) => Ok(Self::new_v4(state)),
622            VoteStateVersionsInline::V3(_)
623            | VoteStateVersionsInline::V1_14_11(_)
624            | VoteStateVersionsInline::Uninitialized => Err(InstructionError::InvalidAccountData),
625        }
626    }
627
628    #[cfg(any(test, feature = "dev-context-only-utils"))]
629    pub fn default_v4() -> Self {
630        Self::new_v4(VoteStateV4::default())
631    }
632
633    #[cfg(any(test, feature = "dev-context-only-utils"))]
634    pub fn as_ref_v4(&self) -> &VoteStateV4 {
635        match &self.target_state {
636            TargetVoteState::V4(v4) => v4,
637        }
638    }
639
640    #[cfg(any(test, feature = "dev-context-only-utils"))]
641    pub fn unwrap_v4(self) -> VoteStateV4 {
642        match self.target_state {
643            TargetVoteState::V4(v4) => v4,
644        }
645    }
646
647    /// Serializes `self` into the provided `data` buffer.
648    ///
649    /// Returns `Err(InstructionError::InvalidAccountData)` if serialization fails.
650    pub fn serialize_into(&self, data: &mut [u8]) -> Result<(), InstructionError> {
651        match &self.target_state {
652            TargetVoteState::V4(v4) => {
653                let versioned = VoteStateVersionsRef::V4(v4);
654                wincode::serialize_into(data, &versioned)
655                    .map_err(|_e| InstructionError::InvalidAccountData)
656            }
657        }
658    }
659
660    #[cfg(test)]
661    pub fn serialize(self) -> Vec<u8> {
662        let mut data = vec![0; VoteStateV4::size_of()];
663        self.serialize_into(&mut data).unwrap();
664        data
665    }
666}
667
668// Computes the vote latency for vote on voted_for_slot where the vote itself landed in current_slot
669pub(crate) fn compute_vote_latency(voted_for_slot: Slot, current_slot: Slot) -> u8 {
670    std::cmp::min(current_slot.saturating_sub(voted_for_slot), u8::MAX as u64) as u8
671}
672
673pub(crate) fn try_convert_to_vote_state_v4(
674    versioned: VoteStateVersionsInline,
675    vote_pubkey: &Pubkey,
676) -> Result<VoteStateV4, InstructionError> {
677    match versioned {
678        VoteStateVersionsInline::Uninitialized => Err(InstructionError::UninitializedAccount),
679        VoteStateVersionsInline::V1_14_11(state) => Ok(VoteStateV4 {
680            node_pubkey: state.node_pubkey,
681            authorized_withdrawer: state.authorized_withdrawer,
682            inflation_rewards_collector: *vote_pubkey,
683            block_revenue_collector: state.node_pubkey,
684            inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
685            block_revenue_commission_bps: 10_000u16,
686            pending_delegator_rewards: 0,
687            bls_pubkey_compressed: None,
688            votes: landed_votes_from_lockouts(state.votes),
689            root_slot: state.root_slot,
690            authorized_voters: state.authorized_voters.clone(),
691            epoch_credits: state.epoch_credits,
692            last_timestamp: state.last_timestamp,
693        }),
694        VoteStateVersionsInline::V3(state) => Ok(VoteStateV4 {
695            node_pubkey: state.node_pubkey,
696            authorized_withdrawer: state.authorized_withdrawer,
697            inflation_rewards_collector: *vote_pubkey,
698            block_revenue_collector: state.node_pubkey,
699            inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
700            block_revenue_commission_bps: 10_000u16,
701            pending_delegator_rewards: 0,
702            bls_pubkey_compressed: None,
703            votes: state.votes,
704            root_slot: state.root_slot,
705            authorized_voters: state.authorized_voters,
706            epoch_credits: state.epoch_credits,
707            last_timestamp: state.last_timestamp,
708        }),
709        VoteStateVersionsInline::V4(state) => Ok(state),
710    }
711}
712
713fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
714    lockouts.into_iter().map(|lockout| lockout.into()).collect()
715}
716
717#[allow(clippy::arithmetic_side_effects)]
718#[allow(clippy::type_complexity)]
719#[cfg(test)]
720mod tests {
721    use {
722        super::*,
723        crate::id,
724        solana_account::AccountSharedData,
725        solana_clock::Clock,
726        solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
727        solana_pubkey::Pubkey,
728        solana_rent::Rent,
729        solana_sdk_ids::native_loader,
730        solana_transaction_context::{
731            instruction_accounts::InstructionAccount, transaction::TransactionContext,
732        },
733        solana_vote_interface::{
734            authorized_voters::AuthorizedVoters,
735            state::{
736                BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BlockTimestamp, MAX_EPOCH_CREDITS_HISTORY,
737                MAX_LOCKOUT_HISTORY, VoteInit, VoteState1_14_11, VoteStateV3, VoteStateVersions,
738            },
739        },
740        std::collections::VecDeque,
741        test_case::test_case,
742    };
743
744    /// Default block revenue commission rate in basis points (100%) per SIMD-0185.
745    const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000;
746
747    #[test]
748    fn test_borrowed_vote_state_versions_compatibility() {
749        let v1 = VoteState1_14_11::default();
750        let v3 = VoteStateV3::default();
751        let v4 = get_max_sized_vote_state_v4();
752        let versions = [
753            (
754                None,
755                VoteStateVersions::V1_14_11(Box::new(v1.clone())),
756                VoteStateVersionsInline::V1_14_11(v1.clone()),
757            ),
758            (
759                None,
760                VoteStateVersions::V3(Box::new(v3.clone())),
761                VoteStateVersionsInline::V3(v3.clone()),
762            ),
763            (
764                Some(VoteStateVersionsRef::V4(&v4)),
765                VoteStateVersions::V4(Box::new(v4.clone())),
766                VoteStateVersionsInline::V4(v4.clone()),
767            ),
768        ];
769        for (borrowed, orig, inline) in versions {
770            let bytes = wincode::serialize(&orig).unwrap();
771            assert_eq!(
772                VoteStateVersionsInline::deserialize(&bytes).unwrap(),
773                inline
774            );
775            if let Some(borrowed) = borrowed {
776                let borrowed_bytes = wincode::serialize(&borrowed).unwrap();
777                assert_eq!(borrowed_bytes, bytes);
778                assert_eq!(
779                    VoteStateVersionsInline::deserialize(&borrowed_bytes).unwrap(),
780                    inline
781                );
782            }
783        }
784    }
785
786    #[test]
787    fn test_inline_vote_state_rejects_uninitialized() {
788        let bytes = wincode::serialize(&VoteStateVersions::Uninitialized).unwrap();
789        assert_eq!(
790            VoteStateVersionsInline::deserialize(&bytes),
791            Err(InstructionError::InvalidAccountData),
792        );
793    }
794
795    #[test]
796    fn test_vote_state_handler_compatibility() {
797        let v4 = get_max_sized_vote_state_v4();
798        let expected = wincode::serialize(&VoteStateVersions::V4(Box::new(v4.clone()))).unwrap();
799        let handler = VoteStateHandler::new_v4(v4);
800        let mut data = vec![0; VoteStateV4::size_of()];
801        handler.serialize_into(&mut data).unwrap();
802        assert_eq!(&data[..expected.len()], expected);
803        let decoded = VoteStateVersionsInline::deserialize(&data).unwrap();
804        assert_eq!(
805            VoteStateHandler::try_new_from_vote_state_versions(decoded).unwrap(),
806            handler,
807        );
808
809        let mut undersized_data = vec![0; expected.len() - 1];
810        assert_eq!(
811            handler.serialize_into(&mut undersized_data),
812            Err(InstructionError::InvalidAccountData),
813        );
814    }
815
816    fn mock_transaction_context(
817        vote_pubkey: Pubkey,
818        vote_account: AccountSharedData,
819        rent: Rent,
820    ) -> TransactionContext<'static> {
821        let program_account = AccountSharedData::new(0, 0, &native_loader::id());
822        let mut transaction_context = TransactionContext::new(
823            vec![(id(), program_account), (vote_pubkey, vote_account)],
824            rent,
825            0,
826            0,
827            1,
828        );
829        transaction_context
830            .configure_top_level_instruction_for_tests(
831                0,
832                vec![InstructionAccount::new(1, false, true)],
833                vec![],
834            )
835            .unwrap();
836        transaction_context
837    }
838
839    fn get_max_sized_vote_state_v4() -> VoteStateV4 {
840        let mut authorized_voters = AuthorizedVoters::default();
841        for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
842            authorized_voters.insert(i, Pubkey::new_unique());
843        }
844
845        VoteStateV4 {
846            votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
847            root_slot: Some(u64::MAX),
848            epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
849            authorized_voters,
850            bls_pubkey_compressed: Some([255; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
851            ..Default::default()
852        }
853    }
854
855    fn set_new_authorized_voter_and_assert(
856        vote_state: &mut VoteStateHandler,
857        original_voter: Pubkey,
858        epoch_offset: Epoch,
859    ) {
860        let new_voter = Pubkey::new_unique();
861        // Set a new authorized voter
862        vote_state
863            .set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(()))
864            .unwrap();
865
866        // Trying to set authorized voter for same epoch again should fail
867        assert_eq!(
868            vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())),
869            Err(VoteError::TooSoonToReauthorize.into())
870        );
871
872        // Setting the same authorized voter again should succeed
873        vote_state
874            .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, None, |_| Ok(()))
875            .unwrap();
876
877        // Set a third and fourth authorized voter
878        let new_voter2 = Pubkey::new_unique();
879        vote_state
880            .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, None, |_| Ok(()))
881            .unwrap();
882
883        let new_voter3 = Pubkey::new_unique();
884        vote_state
885            .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, None, |_| Ok(()))
886            .unwrap();
887
888        // Check can set back to original voter
889        vote_state
890            .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, None, |_| Ok(()))
891            .unwrap();
892
893        // Run with these voters for a while, check the ranges of authorized
894        // voters is correct
895        for i in 9..epoch_offset {
896            assert_eq!(
897                vote_state.get_and_update_authorized_voter(i).unwrap(),
898                original_voter
899            );
900        }
901        for i in epoch_offset..3 + epoch_offset {
902            assert_eq!(
903                vote_state.get_and_update_authorized_voter(i).unwrap(),
904                new_voter
905            );
906        }
907        for i in 3 + epoch_offset..6 + epoch_offset {
908            assert_eq!(
909                vote_state.get_and_update_authorized_voter(i).unwrap(),
910                new_voter2
911            );
912        }
913        for i in 6 + epoch_offset..9 + epoch_offset {
914            assert_eq!(
915                vote_state.get_and_update_authorized_voter(i).unwrap(),
916                new_voter3
917            );
918        }
919        for i in 9 + epoch_offset..=10 + epoch_offset {
920            assert_eq!(
921                vote_state.get_and_update_authorized_voter(i).unwrap(),
922                original_voter
923            );
924        }
925    }
926
927    #[test]
928    fn test_set_new_authorized_voter() {
929        let vote_pubkey = Pubkey::new_unique();
930        let original_voter = Pubkey::new_unique();
931        let epoch_offset = 15;
932
933        let vote_init = VoteInit {
934            node_pubkey: original_voter,
935            authorized_voter: original_voter,
936            authorized_withdrawer: original_voter,
937            commission: 0,
938        };
939        let clock = Clock::default();
940
941        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
942            &vote_pubkey,
943            &vote_init,
944            &clock,
945        ));
946
947        set_new_authorized_voter_and_assert(&mut vote_state, original_voter, epoch_offset);
948
949        {
950            let voter_a = Pubkey::new_unique();
951            let voter_b = Pubkey::new_unique();
952
953            let v4 = VoteStateV4 {
954                authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
955                ..Default::default()
956            };
957            let mut handler = VoteStateHandler::new_v4(v4);
958            handler
959                .set_new_authorized_voter(&voter_a, 0, 10, None, |_| Ok(()))
960                .unwrap();
961            handler
962                .set_new_authorized_voter(&voter_b, 0, 5, None, |_| Ok(()))
963                .unwrap();
964        }
965    }
966
967    fn assert_authorized_voter_is_locked_within_epoch(
968        vote_state: &mut VoteStateHandler,
969        original_voter: &Pubkey,
970    ) {
971        // Test that it's not possible to set a new authorized
972        // voter within the same epoch, even if none has been
973        // explicitly set before
974        let new_voter = Pubkey::new_unique();
975        assert_eq!(
976            vote_state.set_new_authorized_voter(&new_voter, 1, 1, None, |_| Ok(())),
977            Err(VoteError::TooSoonToReauthorize.into())
978        );
979        assert_eq!(
980            vote_state.authorized_voters().get_authorized_voter(1),
981            Some(*original_voter)
982        );
983        // Set a new authorized voter for a future epoch
984        assert_eq!(
985            vote_state.set_new_authorized_voter(&new_voter, 1, 2, None, |_| Ok(())),
986            Ok(())
987        );
988        // Test that it's not possible to set a new authorized
989        // voter within the same epoch, even if none has been
990        // explicitly set before
991        assert_eq!(
992            vote_state.set_new_authorized_voter(original_voter, 3, 3, None, |_| Ok(())),
993            Err(VoteError::TooSoonToReauthorize.into())
994        );
995        assert_eq!(
996            vote_state.authorized_voters().get_authorized_voter(3),
997            Some(new_voter)
998        );
999    }
1000
1001    #[test]
1002    fn test_authorized_voter_is_locked_within_epoch() {
1003        let vote_pubkey = Pubkey::new_unique();
1004        let original_voter = Pubkey::new_unique();
1005
1006        let vote_init = VoteInit {
1007            node_pubkey: original_voter,
1008            authorized_voter: original_voter,
1009            authorized_withdrawer: original_voter,
1010            commission: 0,
1011        };
1012        let clock = Clock::default();
1013
1014        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1015            &vote_pubkey,
1016            &vote_init,
1017            &clock,
1018        ));
1019        assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter);
1020    }
1021
1022    #[test]
1023    fn test_get_and_update_authorized_voter() {
1024        let vote_pubkey = Pubkey::new_unique();
1025        let original_voter = Pubkey::new_unique();
1026        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1027            &vote_pubkey,
1028            &VoteInit {
1029                node_pubkey: original_voter,
1030                authorized_voter: original_voter,
1031                authorized_withdrawer: original_voter,
1032                commission: 0,
1033            },
1034            &Clock::default(),
1035        ));
1036
1037        // Run the same exercise as the v3 test to start.
1038
1039        assert_eq!(vote_state.authorized_voters().len(), 1);
1040        assert_eq!(
1041            *vote_state.authorized_voters().first().unwrap().1,
1042            original_voter
1043        );
1044
1045        // If no new authorized voter was set, the same authorized voter
1046        // is locked into the next epoch
1047        assert_eq!(
1048            vote_state.get_and_update_authorized_voter(1).unwrap(),
1049            original_voter
1050        );
1051
1052        // Try to get the authorized voter for epoch 5, implies
1053        // the authorized voter for epochs 1-4 were unchanged
1054        assert_eq!(
1055            vote_state.get_and_update_authorized_voter(5).unwrap(),
1056            original_voter
1057        );
1058
1059        // Just like with the v3 tests, authorized voters for epochs 0..5 should
1060        // be purged, but only because we didn't cache an entry for current - 1.
1061        assert_eq!(vote_state.authorized_voters().len(), 1);
1062        for i in 0..5 {
1063            assert!(
1064                vote_state
1065                    .authorized_voters()
1066                    .get_authorized_voter(i)
1067                    .is_none()
1068            );
1069        }
1070
1071        // Say we're in epoch 7. Cache entries for both epochs 6 and 7.
1072        assert_eq!(
1073            vote_state.get_and_update_authorized_voter(6).unwrap(),
1074            original_voter
1075        );
1076        assert_eq!(
1077            vote_state.get_and_update_authorized_voter(7).unwrap(),
1078            original_voter
1079        );
1080
1081        // Now we should have length 2.
1082        assert_eq!(vote_state.authorized_voters().len(), 2);
1083
1084        // 0..=5 should still be purged.
1085        for i in 0..=5 {
1086            assert!(
1087                vote_state
1088                    .authorized_voters()
1089                    .get_authorized_voter(i)
1090                    .is_none()
1091            );
1092        }
1093
1094        // Set an authorized voter change at epoch 9.
1095        let new_authorized_voter = Pubkey::new_unique();
1096        vote_state
1097            .set_new_authorized_voter(&new_authorized_voter, 7, 9, None, |_| Ok(()))
1098            .unwrap();
1099
1100        // Try to get the authorized voter for epoch 8, unchanged
1101        assert_eq!(
1102            vote_state.get_and_update_authorized_voter(8).unwrap(),
1103            original_voter
1104        );
1105
1106        // Try to get the authorized voter for epoch 9 and onwards, should
1107        // be the new authorized voter
1108        for i in 9..12 {
1109            assert_eq!(
1110                vote_state.get_and_update_authorized_voter(i).unwrap(),
1111                new_authorized_voter
1112            );
1113        }
1114        assert_eq!(vote_state.authorized_voters().len(), 2);
1115
1116        // If we skip a few epochs ahead, only the current epoch is retained.
1117        assert_eq!(
1118            vote_state.get_and_update_authorized_voter(15).unwrap(),
1119            new_authorized_voter
1120        );
1121        assert_eq!(vote_state.authorized_voters().len(), 1);
1122
1123        // V4 purge boundary: at epoch 11 with voter set at epoch 10,
1124        // V4 retains epoch 10 (purge range 0..10) while V3 would not.
1125        {
1126            let voter_10 = Pubkey::new_unique();
1127            let voter_15 = Pubkey::new_unique();
1128            let mut v4 = VoteStateV4 {
1129                authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
1130                ..Default::default()
1131            };
1132            v4.authorized_voters.insert(10, voter_10);
1133            v4.authorized_voters.insert(15, voter_15);
1134            let mut handler = VoteStateHandler::new_v4(v4);
1135            let v4_voter = handler.get_and_update_authorized_voter(11).unwrap();
1136            assert_eq!(v4_voter, voter_10);
1137            // V4 purge range 0..10: epoch 10 retained.
1138            assert!(
1139                handler
1140                    .authorized_voters()
1141                    .get_authorized_voter(10)
1142                    .is_some()
1143            );
1144        }
1145
1146        // Epoch 0: saturating_sub(1) = 0, purge range 0..0 is empty.
1147        {
1148            let voter = Pubkey::new_unique();
1149            let v4 = VoteStateV4 {
1150                authorized_voters: AuthorizedVoters::new(0, voter),
1151                ..Default::default()
1152            };
1153            let mut handler = VoteStateHandler::new_v4(v4);
1154            let result = handler.get_and_update_authorized_voter(0).unwrap();
1155            assert_eq!(result, voter);
1156            assert!(!handler.authorized_voters().is_empty());
1157        }
1158    }
1159
1160    #[test_case(
1161        VoteStateV4::size_of(),
1162        VoteStateHandler::new_v4(get_max_sized_vote_state_v4()),
1163        |vote_state, data| {
1164            let versioned = VoteStateVersions::new_v4(vote_state.unwrap_v4());
1165            VoteStateV4::serialize(&versioned, data).unwrap();
1166        };
1167        "VoteStateV4"
1168    )]
1169    fn test_vote_state_max_size(
1170        max_size: usize,
1171        mut vote_state: VoteStateHandler,
1172        verify_serialize: fn(VoteStateHandler, &mut [u8]),
1173    ) {
1174        let mut max_sized_data = vec![0; max_size];
1175        let (start_leader_schedule_epoch, _) = vote_state.authorized_voters().last().unwrap();
1176        let start_current_epoch =
1177            start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1;
1178
1179        for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
1180            vote_state
1181                .set_new_authorized_voter(
1182                    &Pubkey::new_unique(),
1183                    i,
1184                    i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
1185                    None,
1186                    |_| Ok(()),
1187                )
1188                .unwrap();
1189
1190            verify_serialize(vote_state.clone(), &mut max_sized_data);
1191        }
1192    }
1193
1194    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1195    fn test_vote_state_epoch_credits(mut vote_state: VoteStateHandler) {
1196        assert_eq!(vote_state.credits(), 0);
1197        assert_eq!(vote_state.epoch_credits().clone(), vec![]);
1198
1199        let mut expected = vec![];
1200        let mut credits = 0;
1201        let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1202        for epoch in 0..epochs {
1203            for _j in 0..epoch {
1204                vote_state.increment_credits(epoch, 1);
1205                credits += 1;
1206            }
1207            expected.push((epoch, credits, credits - epoch));
1208        }
1209
1210        while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
1211            expected.remove(0);
1212        }
1213
1214        assert_eq!(vote_state.credits(), credits);
1215        assert_eq!(vote_state.epoch_credits().clone(), expected);
1216    }
1217
1218    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1219    fn test_vote_state_epoch0_no_credits(mut vote_state: VoteStateHandler) {
1220        assert_eq!(vote_state.epoch_credits().len(), 0);
1221        vote_state.increment_credits(1, 1);
1222        assert_eq!(vote_state.epoch_credits().len(), 1);
1223
1224        vote_state.increment_credits(2, 1);
1225        assert_eq!(vote_state.epoch_credits().len(), 2);
1226    }
1227
1228    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1229    fn test_vote_state_increment_credits(mut vote_state: VoteStateHandler) {
1230        let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1231        for i in 0..credits {
1232            vote_state.increment_credits(i, 1);
1233        }
1234        assert_eq!(vote_state.credits(), credits);
1235        assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
1236    }
1237
1238    #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1239    fn test_vote_process_timestamp(mut vote_state: VoteStateHandler) {
1240        let (slot, timestamp) = (15, 1_575_412_285);
1241        vote_state.set_last_timestamp(BlockTimestamp { slot, timestamp });
1242
1243        assert_eq!(
1244            vote_state.process_timestamp(slot - 1, timestamp + 1),
1245            Err(VoteError::TimestampTooOld)
1246        );
1247        assert_eq!(
1248            vote_state.last_timestamp(),
1249            &BlockTimestamp { slot, timestamp }
1250        );
1251        assert_eq!(
1252            vote_state.process_timestamp(slot + 1, timestamp - 1),
1253            Err(VoteError::TimestampTooOld)
1254        );
1255        assert_eq!(
1256            vote_state.process_timestamp(slot, timestamp + 1),
1257            Err(VoteError::TimestampTooOld)
1258        );
1259        assert_eq!(vote_state.process_timestamp(slot, timestamp), Ok(()));
1260        assert_eq!(
1261            vote_state.last_timestamp(),
1262            &BlockTimestamp { slot, timestamp }
1263        );
1264        assert_eq!(vote_state.process_timestamp(slot + 1, timestamp), Ok(()));
1265        assert_eq!(
1266            vote_state.last_timestamp(),
1267            &BlockTimestamp {
1268                slot: slot + 1,
1269                timestamp
1270            }
1271        );
1272        assert_eq!(
1273            vote_state.process_timestamp(slot + 2, timestamp + 1),
1274            Ok(())
1275        );
1276        assert_eq!(
1277            vote_state.last_timestamp(),
1278            &BlockTimestamp {
1279                slot: slot + 2,
1280                timestamp: timestamp + 1
1281            }
1282        );
1283
1284        // Test initial vote
1285        vote_state.set_last_timestamp(BlockTimestamp::default());
1286        assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
1287    }
1288
1289    fn init_vote_account_state_v4_and_assert(
1290        vote_pubkey: Pubkey,
1291        vote_account: AccountSharedData,
1292        vote_init: &VoteInit,
1293        clock: &Clock,
1294        rent: Rent,
1295        expected_result: Result<(), InstructionError>,
1296    ) {
1297        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1298        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1299        let mut vote_account = instruction_context
1300            .try_borrow_instruction_account(0)
1301            .unwrap();
1302
1303        // Initialize.
1304        let result = VoteStateHandler::init_vote_account_state(
1305            &mut vote_account,
1306            vote_init,
1307            clock,
1308            VoteStateTargetVersion::V4,
1309        );
1310        assert_eq!(result, expected_result);
1311
1312        if result.is_ok() {
1313            let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1314            assert!(matches!(vote_state_versions, VoteStateVersions::V4(_)));
1315            assert!(!vote_state_versions.is_uninitialized());
1316
1317            // Verify fields.
1318            if let VoteStateVersions::V4(v4) = vote_state_versions {
1319                assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1320                assert_eq!(
1321                    v4.authorized_voters.get_authorized_voter(clock.epoch),
1322                    Some(vote_init.authorized_voter)
1323                );
1324                assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1325                assert_eq!(
1326                    v4.inflation_rewards_commission_bps,
1327                    (vote_init.commission as u16) * 100
1328                );
1329
1330                // SIMD-0185 fields
1331                assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
1332                assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
1333                assert_eq!(
1334                    v4.block_revenue_commission_bps,
1335                    DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1336                );
1337
1338                // Fields that should be default
1339                assert_eq!(v4.pending_delegator_rewards, 0);
1340                assert_eq!(v4.bls_pubkey_compressed, None);
1341                assert!(v4.votes.is_empty());
1342                assert_eq!(v4.root_slot, None);
1343                assert!(v4.epoch_credits.is_empty());
1344                assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1345            } else {
1346                panic!("should be v4");
1347            }
1348        }
1349    }
1350
1351    fn deinit_vote_account_state_v4_and_assert(
1352        vote_pubkey: Pubkey,
1353        vote_account: AccountSharedData,
1354        rent: Rent,
1355    ) {
1356        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1357        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1358        let mut vote_account = instruction_context
1359            .try_borrow_instruction_account(0)
1360            .unwrap();
1361
1362        // Deinitialize.
1363        VoteStateHandler::deinitialize_vote_account_state(
1364            &mut vote_account,
1365            VoteStateTargetVersion::V4,
1366        )
1367        .unwrap();
1368
1369        // Per SIMD-0185, V4 should completely zero out the account data.
1370        let account_data = vote_account.get_data();
1371        assert!(account_data.iter().all(|&b| b == 0),);
1372
1373        // Vote account was completely zeroed, so this should deserialize as
1374        // uninitialized.
1375        let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1376        assert!(matches!(
1377            vote_state_versions,
1378            VoteStateVersions::Uninitialized
1379        ));
1380        assert!(vote_state_versions.is_uninitialized());
1381    }
1382
1383    #[test]
1384    fn test_init_vote_account_state_v4() {
1385        let vote_pubkey = Pubkey::new_unique();
1386        let vote_init = VoteInit {
1387            node_pubkey: Pubkey::new_unique(),
1388            authorized_voter: Pubkey::new_unique(),
1389            authorized_withdrawer: Pubkey::new_unique(),
1390            commission: 5,
1391        };
1392        let clock = Clock::default();
1393        let rent = Rent::default();
1394
1395        // First create a vote account that's too small for v4.
1396        let v1_14_11_size = VoteState1_14_11::size_of();
1397        let lamports = rent.minimum_balance(v1_14_11_size);
1398        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1399
1400        // Initialize - should fail.
1401        init_vote_account_state_v4_and_assert(
1402            vote_pubkey,
1403            vote_account,
1404            &vote_init,
1405            &clock,
1406            rent.clone(),
1407            Err(InstructionError::AccountNotRentExempt),
1408        );
1409
1410        // Create a vote account that's too small for v4, but has enough
1411        // lamports for resize.
1412        let v4_size = VoteStateV4::size_of();
1413        let lamports = rent.minimum_balance(v4_size);
1414        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1415
1416        // Initialize - should resize and create v4.
1417        init_vote_account_state_v4_and_assert(
1418            vote_pubkey,
1419            vote_account,
1420            &vote_init,
1421            &clock,
1422            rent.clone(),
1423            Ok(()),
1424        );
1425
1426        // Now create a vote account that's large enough for v4.
1427        let lamports = rent.minimum_balance(v4_size);
1428        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1429
1430        // Initialize - should create v4.
1431        init_vote_account_state_v4_and_assert(
1432            vote_pubkey,
1433            vote_account,
1434            &vote_init,
1435            &clock,
1436            rent,
1437            Ok(()),
1438        );
1439    }
1440
1441    #[test]
1442    fn test_deinitialize_vote_account_state_v4() {
1443        let vote_pubkey = Pubkey::new_unique();
1444        let rent = Rent::default();
1445
1446        // First create a vote account that's too small for v4.
1447        let v1_14_11_size = VoteState1_14_11::size_of();
1448        let lamports = rent.minimum_balance(v1_14_11_size);
1449        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1450
1451        // Deinitialize - should fail.
1452        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1453
1454        // Create a vote account that's too small for v4, but has enough
1455        // lamports for resize.
1456        let v4_size = VoteStateV4::size_of();
1457        let lamports = rent.minimum_balance(v4_size);
1458        let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1459
1460        // Deinitialize - should resize and create v4.
1461        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1462
1463        // Now create a vote account that's large enough for v4.
1464        let lamports = rent.minimum_balance(v4_size);
1465        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1466
1467        // Deinitialize - should create v4.
1468        deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent);
1469    }
1470
1471    #[test]
1472    fn test_v4_commission_basis_points() {
1473        // Test that commission is properly converted to basis points
1474        // (multiplied by 100), as per SIMD-0185.
1475        let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1476
1477        for (input, expected) in [
1478            (0, 0),
1479            (1, 100),
1480            (5, 500),
1481            (10, 1_000),
1482            (25, 2_500),
1483            (50, 5_000),
1484            (75, 7_500),
1485            (100, 10_000),
1486            (255, 25_500),
1487        ] {
1488            handler.set_commission(input);
1489            assert_eq!(handler.commission(), input);
1490
1491            // Verify the internal V4 state has the correct basis points.
1492            let vote_state_v4 = handler.as_ref_v4();
1493            assert_eq!(vote_state_v4.inflation_rewards_commission_bps, expected);
1494        }
1495    }
1496
1497    #[test]
1498    fn test_v4_commission_getter_clamps_extreme_bps() {
1499        // Verify commission() clamps to u8::MAX for extreme bps values
1500        // instead of wrapping around. SIMD-0291 allows storing any u16 as
1501        // inflation_rewards_commission_bps — the legacy getter must not
1502        // produce misleading values.
1503        for (bps, expected) in [
1504            (10_000, 100),   // normal
1505            (25_500, 255),   // exact boundary, no clamping needed
1506            (25_501, 255),   // just past boundary, clamped
1507            (25_600, 255),   // would wrap to 0 without clamping
1508            (u16::MAX, 255), // would wrap to 143 without clamping
1509        ] {
1510            let vote_state = VoteStateV4 {
1511                inflation_rewards_commission_bps: bps,
1512                ..Default::default()
1513            };
1514            let handler = VoteStateHandler::new_v4(vote_state);
1515            assert_eq!(handler.commission(), expected);
1516        }
1517    }
1518
1519    #[test]
1520    fn test_commission_v3_v4_preserved() {
1521        // Verify commission() returns the same value through V3→V4 migration.
1522        let vote_pubkey = Pubkey::new_unique();
1523        for n in [0u8, 1, 50, 100, 101, 255] {
1524            let v3 = VoteStateV3 {
1525                commission: n,
1526                ..Default::default()
1527            };
1528
1529            let versioned = VoteStateVersionsInline::V3(v3);
1530            let migrated = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1531            let migrated_handler = VoteStateHandler::new_v4(migrated);
1532            assert_eq!(migrated_handler.commission(), n);
1533            assert_eq!(
1534                migrated_handler
1535                    .as_ref_v4()
1536                    .inflation_rewards_commission_bps,
1537                n as u16 * 100
1538            );
1539        }
1540    }
1541
1542    #[test]
1543    fn test_v4_conversion_from_all_versions() {
1544        // Test conversion from all vote state versions to v4 per SIMD-0185.
1545        let vote_pubkey = Pubkey::new_unique();
1546        let node_pubkey = Pubkey::new_unique();
1547        let authorized_voter = Pubkey::new_unique();
1548        let authorized_withdrawer = Pubkey::new_unique();
1549        let commission = 42u8;
1550        let votes = vec![Lockout::new(100)];
1551        let votes_deque: VecDeque<Lockout> = votes.iter().cloned().collect();
1552        let epoch_credits = vec![(5, 200, 100)];
1553        let root_slot = Some(50);
1554
1555        // Helper to verify v4 defaults per SIMD-0185.
1556        let verify_v4_defaults = |v4_state: &VoteStateV4, expected_commission_bps: u16| {
1557            assert_eq!(
1558                v4_state.inflation_rewards_commission_bps,
1559                expected_commission_bps
1560            );
1561            assert_eq!(v4_state.inflation_rewards_collector, vote_pubkey);
1562            assert_eq!(v4_state.block_revenue_collector, node_pubkey);
1563            assert_eq!(
1564                v4_state.block_revenue_commission_bps,
1565                DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1566            );
1567            assert_eq!(v4_state.pending_delegator_rewards, 0);
1568            assert_eq!(v4_state.bls_pubkey_compressed, None);
1569        };
1570
1571        // V0_23_5
1572        // NOTE: On target_os = "solana", VoteState0_23_5::deserialize should
1573        // fail, but we can't replicate that in these unit tests. This should
1574        // be tested in the program's processor tests.
1575
1576        // V1_14_11
1577        {
1578            let vote_state_v2 = VoteState1_14_11 {
1579                node_pubkey,
1580                authorized_withdrawer,
1581                commission,
1582                authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1583                votes: votes_deque,
1584                epoch_credits: epoch_credits.clone(),
1585                root_slot,
1586                ..VoteState1_14_11::default()
1587            };
1588
1589            let versioned = VoteStateVersionsInline::V1_14_11(vote_state_v2.clone());
1590            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1591
1592            // Compare fields that were already present in V1_14_11.
1593            assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1594            assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1595            assert_eq!(
1596                vote_state_v4.authorized_voters,
1597                vote_state_v2.authorized_voters
1598            );
1599            assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1600            assert_eq!(vote_state_v4.root_slot, root_slot);
1601            assert_eq!(vote_state_v4.votes.len(), vote_state_v2.votes.len());
1602            for (v4_vote, v2_vote) in vote_state_v4.votes.iter().zip(vote_state_v2.votes.iter()) {
1603                assert_eq!(v4_vote.lockout, *v2_vote);
1604            }
1605
1606            // Verify SIMD-0185 defaults.
1607            verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1608        }
1609
1610        // V3
1611        {
1612            let vote_init = VoteInit {
1613                node_pubkey,
1614                authorized_voter,
1615                authorized_withdrawer,
1616                commission,
1617            };
1618            let mut vote_state_v3 = VoteStateV3::new(&vote_init, &Clock::default());
1619            for lockout in &votes {
1620                vote_state_v3.votes.push_back(LandedVote {
1621                    latency: 1,
1622                    lockout: *lockout,
1623                });
1624            }
1625            vote_state_v3.epoch_credits = epoch_credits.clone();
1626            vote_state_v3.root_slot = root_slot;
1627
1628            let versioned = VoteStateVersionsInline::V3(vote_state_v3.clone());
1629            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1630
1631            // Compare fields that were already present in V3.
1632            assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1633            assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1634            assert_eq!(
1635                vote_state_v4.authorized_voters,
1636                vote_state_v3.authorized_voters
1637            );
1638            assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1639            assert_eq!(vote_state_v4.root_slot, root_slot);
1640            assert_eq!(vote_state_v4.last_timestamp, vote_state_v3.last_timestamp);
1641            assert_eq!(vote_state_v4.votes, vote_state_v3.votes);
1642
1643            // Verify SIMD-0185 defaults.
1644            verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1645        }
1646
1647        // V4
1648        {
1649            let mut initial_vote_state_v4 = VoteStateV4 {
1650                node_pubkey,
1651                authorized_withdrawer,
1652                inflation_rewards_commission_bps: 1234,
1653                block_revenue_commission_bps: 5678,
1654                inflation_rewards_collector: Pubkey::new_unique(),
1655                block_revenue_collector: Pubkey::new_unique(),
1656                pending_delegator_rewards: 999,
1657                bls_pubkey_compressed: Some([42; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
1658                authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1659                epoch_credits,
1660                root_slot,
1661                ..VoteStateV4::default()
1662            };
1663            for lockout in &votes {
1664                initial_vote_state_v4.votes.push_back(LandedVote {
1665                    latency: 1,
1666                    lockout: *lockout,
1667                });
1668            }
1669
1670            let versioned = VoteStateVersionsInline::V4(initial_vote_state_v4.clone());
1671            let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1672
1673            // Should be an exact copy.
1674            assert_eq!(vote_state_v4, initial_vote_state_v4);
1675        }
1676    }
1677
1678    #[test]
1679    fn test_v4_migration_with_pre_existing_voters() {
1680        // Verify V4 purge rules apply to the migrated voter map from
1681        // both V1_14_11 and V3.
1682        let vote_pubkey = Pubkey::new_unique();
1683        let voter_5 = Pubkey::new_unique();
1684        let voter_7 = Pubkey::new_unique();
1685        let voter_9 = Pubkey::new_unique();
1686
1687        let build_voters = || {
1688            let mut voters = AuthorizedVoters::new(0, Pubkey::new_unique());
1689            voters.insert(5, voter_5);
1690            voters.insert(7, voter_7);
1691            voters.insert(9, voter_9);
1692            voters
1693        };
1694
1695        let assert_purge = |handler: &mut VoteStateHandler| {
1696            // Advance to epoch 10. V4 purge range 0..9.
1697            let voter = handler.get_and_update_authorized_voter(10).unwrap();
1698            assert_eq!(voter, voter_9);
1699            assert!(
1700                handler
1701                    .authorized_voters()
1702                    .get_authorized_voter(9)
1703                    .is_some()
1704            );
1705            assert!(
1706                handler
1707                    .authorized_voters()
1708                    .get_authorized_voter(7)
1709                    .is_none()
1710            );
1711            assert!(
1712                handler
1713                    .authorized_voters()
1714                    .get_authorized_voter(5)
1715                    .is_none()
1716            );
1717        };
1718
1719        // V1_14_11 → V4
1720        {
1721            let v1 = VoteState1_14_11 {
1722                authorized_voters: build_voters(),
1723                ..Default::default()
1724            };
1725            let versioned = VoteStateVersionsInline::V1_14_11(v1);
1726            let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1727            let mut handler = VoteStateHandler::new_v4(v4);
1728            assert_purge(&mut handler);
1729        }
1730
1731        // V3 → V4
1732        {
1733            let v3 = VoteStateV3 {
1734                authorized_voters: build_voters(),
1735                ..Default::default()
1736            };
1737            let versioned = VoteStateVersionsInline::V3(v3);
1738            let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1739            let mut handler = VoteStateHandler::new_v4(v4);
1740            assert_purge(&mut handler);
1741        }
1742    }
1743
1744    #[test]
1745    fn test_v3_v4_size_equality() {
1746        let v3_size = VoteStateV3::size_of();
1747        let v4_size = VoteStateV4::size_of();
1748        assert_eq!(v3_size, v4_size, "V3 and V4 should have the same size");
1749    }
1750
1751    #[test_case(
1752        VoteState1_14_11::size_of(),
1753        false,
1754        Err(InstructionError::AccountNotRentExempt);
1755        "v1_14_11 size insufficient lamports - no fallback"
1756    )]
1757    #[test_case(
1758        VoteState1_14_11::size_of(),
1759        true,
1760        Ok(());
1761        "v1_14_11 size sufficient lamports - resize and succeed"
1762    )]
1763    #[test_case(
1764        VoteStateV4::size_of(),
1765        true,
1766        Ok(());
1767        "v4 properly sized with rent-exempt lamports - succeed"
1768    )]
1769    fn test_v4_resize_behavior(
1770        account_size: usize,
1771        sufficient_lamports: bool,
1772        expected_result: Result<(), InstructionError>,
1773    ) {
1774        let vote_pubkey = Pubkey::new_unique();
1775        let node_pubkey = Pubkey::new_unique();
1776        let authorized_voter = Pubkey::new_unique();
1777        let authorized_withdrawer = Pubkey::new_unique();
1778        let rent = Rent::default();
1779
1780        let vote_init = VoteInit {
1781            node_pubkey,
1782            authorized_voter,
1783            authorized_withdrawer,
1784            commission: 42,
1785        };
1786
1787        let lamports = if sufficient_lamports {
1788            rent.minimum_balance(VoteStateV4::size_of())
1789        } else {
1790            rent.minimum_balance(account_size)
1791        };
1792
1793        let mut vote_account = AccountSharedData::new(lamports, account_size, &id());
1794        vote_account.set_data_from_slice(&vec![0; account_size]);
1795
1796        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1797        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1798        let mut vote_account_borrowed = instruction_context
1799            .try_borrow_instruction_account(0)
1800            .unwrap();
1801
1802        let result = VoteStateHandler::init_vote_account_state(
1803            &mut vote_account_borrowed,
1804            &vote_init,
1805            &Clock::default(),
1806            VoteStateTargetVersion::V4,
1807        );
1808
1809        assert_eq!(result, expected_result);
1810    }
1811
1812    #[test]
1813    fn test_get_and_update_authorized_voter_v4_with_bls() {
1814        let vote_pubkey = Pubkey::new_unique();
1815        let original_voter = Pubkey::new_unique();
1816        let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1817            &vote_pubkey,
1818            &VoteInit {
1819                node_pubkey: original_voter,
1820                authorized_voter: original_voter,
1821                authorized_withdrawer: original_voter,
1822                commission: 0,
1823            },
1824            &Clock::default(),
1825        ));
1826
1827        // It has some initial authorized voter but no BLS pubkey.
1828        assert_eq!(vote_state.authorized_voters().len(), 1);
1829        assert_eq!(
1830            *vote_state.authorized_voters().first().unwrap().1,
1831            original_voter
1832        );
1833        assert!(!vote_state.has_bls_pubkey());
1834
1835        // Update authorized voter with BLS pubkey.
1836        let new_voter = Pubkey::new_unique();
1837        let bls_pubkey_compressed = [3u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1838        vote_state
1839            .set_new_authorized_voter(&new_voter, 0, 1, Some(&bls_pubkey_compressed), |_| Ok(()))
1840            .unwrap();
1841        assert_eq!(vote_state.authorized_voters().len(), 2);
1842        assert_eq!(*vote_state.authorized_voters().last().unwrap().1, new_voter);
1843        assert_eq!(
1844            vote_state.as_ref_v4().bls_pubkey_compressed,
1845            Some(bls_pubkey_compressed)
1846        );
1847        assert!(vote_state.has_bls_pubkey());
1848
1849        // Now update authorized voter again with another BLS pubkey.
1850        let newer_voter = Pubkey::new_unique();
1851        let newer_bls_pubkey_compressed = [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1852        vote_state
1853            .set_new_authorized_voter(
1854                &newer_voter,
1855                1,
1856                2,
1857                Some(&newer_bls_pubkey_compressed),
1858                |_| Ok(()),
1859            )
1860            .unwrap();
1861        assert_eq!(vote_state.authorized_voters().len(), 3);
1862        assert_eq!(
1863            *vote_state.authorized_voters().last().unwrap().1,
1864            newer_voter
1865        );
1866        assert_eq!(
1867            vote_state.as_ref_v4().bls_pubkey_compressed,
1868            Some(newer_bls_pubkey_compressed)
1869        );
1870        assert!(vote_state.has_bls_pubkey());
1871    }
1872
1873    #[test]
1874    fn test_set_inflation_rewards_commission_bps() {
1875        let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1876
1877        // First test some "normal" values.
1878        for bps in [0, 100, 500, 1_000, 5_000, 10_000] {
1879            handler.set_inflation_rewards_commission_bps(bps);
1880            let v4 = handler.as_ref_v4();
1881            assert_eq!(v4.inflation_rewards_commission_bps, bps);
1882            // commission() should return bps / 100
1883            assert_eq!(handler.commission(), (bps / 100) as u8);
1884        }
1885
1886        // Now test values > 10,000 are allowed at program level.
1887        // Capping happens during reward calculation, not storage.
1888        for bps in [10_001, 15_000, u16::MAX] {
1889            handler.set_inflation_rewards_commission_bps(bps);
1890            let v4 = handler.as_ref_v4();
1891            assert_eq!(v4.inflation_rewards_commission_bps, bps);
1892        }
1893    }
1894
1895    #[test]
1896    fn test_compute_vote_latency() {
1897        for (voted_for_slot, current_slot, expected) in [
1898            (0, 0, 0),                   // zero latency
1899            (0, 1, 1),                   // normal
1900            (0, 255, 255),               // max u8
1901            (0, 256, 255),               // clamped to u8::MAX
1902            (0, 1_000, 255),             // large gap, clamped
1903            (u64::MAX - 1, u64::MAX, 1), // near-max slots
1904            (5, 5, 0),                   // same slot
1905            (10, 5, 0),                  // current < voted, saturating_sub → 0
1906        ] {
1907            assert_eq!(compute_vote_latency(voted_for_slot, current_slot), expected);
1908        }
1909    }
1910
1911    #[test]
1912    fn test_init_vote_account_state_v2() {
1913        let vote_pubkey = Pubkey::new_unique();
1914        let inflation_rewards_collector = Pubkey::new_unique();
1915        let block_revenue_collector = Pubkey::new_unique();
1916        let vote_init = VoteInitV2 {
1917            node_pubkey: Pubkey::new_unique(),
1918            authorized_voter: Pubkey::new_unique(),
1919            authorized_voter_bls_pubkey: [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1920            authorized_voter_bls_proof_of_possession: [8u8;
1921                BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1922            authorized_withdrawer: Pubkey::new_unique(),
1923            inflation_rewards_commission_bps: 1_234,
1924            block_revenue_commission_bps: 5_678,
1925        };
1926        let clock = Clock::default();
1927        let rent = Rent::default();
1928
1929        let v4_size = VoteStateV4::size_of();
1930        let lamports = rent.minimum_balance(v4_size);
1931        let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1932
1933        let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1934        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1935        let mut vote_account = instruction_context
1936            .try_borrow_instruction_account(0)
1937            .unwrap();
1938
1939        VoteStateHandler::init_vote_account_state_v2(
1940            &mut vote_account,
1941            &vote_init,
1942            &inflation_rewards_collector,
1943            &block_revenue_collector,
1944            &clock,
1945            VoteStateTargetVersion::V4,
1946        )
1947        .unwrap();
1948
1949        let VoteStateVersions::V4(v4) = vote_account.get_state::<VoteStateVersions>().unwrap()
1950        else {
1951            panic!("should be v4");
1952        };
1953
1954        assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1955        assert_eq!(
1956            v4.authorized_voters.get_authorized_voter(clock.epoch),
1957            Some(vote_init.authorized_voter),
1958        );
1959        assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1960        assert_eq!(
1961            v4.bls_pubkey_compressed,
1962            Some(vote_init.authorized_voter_bls_pubkey),
1963        );
1964        assert_eq!(
1965            v4.inflation_rewards_commission_bps,
1966            vote_init.inflation_rewards_commission_bps,
1967        );
1968        assert_eq!(
1969            v4.block_revenue_commission_bps,
1970            vote_init.block_revenue_commission_bps,
1971        );
1972        assert_eq!(v4.inflation_rewards_collector, inflation_rewards_collector);
1973        assert_eq!(v4.block_revenue_collector, block_revenue_collector);
1974        assert_eq!(v4.pending_delegator_rewards, 0);
1975        assert!(v4.votes.is_empty());
1976        assert_eq!(v4.root_slot, None);
1977        assert!(v4.epoch_credits.is_empty());
1978        assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1979    }
1980}