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