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