cbe_program/vote/state/
mod.rs

1//! Vote state
2
3#[cfg(test)]
4use crate::epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET;
5use {
6    crate::{
7        clock::{Epoch, Slot, UnixTimestamp},
8        hash::Hash,
9        instruction::InstructionError,
10        pubkey::Pubkey,
11        rent::Rent,
12        sysvar::clock::Clock,
13        vote::{authorized_voters::AuthorizedVoters, error::VoteError},
14    },
15    bincode::{deserialize, serialize_into, ErrorKind},
16    serde_derive::{Deserialize, Serialize},
17    std::{collections::VecDeque, fmt::Debug},
18};
19
20mod vote_state_0_23_5;
21pub mod vote_state_versions;
22pub use vote_state_versions::*;
23
24// Maximum number of votes to keep around, tightly coupled with epoch_schedule::MINIMUM_SLOTS_PER_EPOCH
25pub const MAX_LOCKOUT_HISTORY: usize = 31;
26pub const INITIAL_LOCKOUT: usize = 2;
27
28// Maximum number of credits history to keep around
29pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
30
31// Offset of VoteState::prior_voters, for determining initialization status without deserialization
32const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82;
33
34#[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")]
35#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
36pub struct Vote {
37    /// A stack of votes starting with the oldest vote
38    pub slots: Vec<Slot>,
39    /// signature of the bank's state at the last slot
40    pub hash: Hash,
41    /// processing timestamp of last slot
42    pub timestamp: Option<UnixTimestamp>,
43}
44
45impl Vote {
46    pub fn new(slots: Vec<Slot>, hash: Hash) -> Self {
47        Self {
48            slots,
49            hash,
50            timestamp: None,
51        }
52    }
53
54    pub fn last_voted_slot(&self) -> Option<Slot> {
55        self.slots.last().copied()
56    }
57}
58
59#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Copy, Clone, AbiExample)]
60pub struct Lockout {
61    pub slot: Slot,
62    pub confirmation_count: u32,
63}
64
65impl Lockout {
66    pub fn new(slot: Slot) -> Self {
67        Self {
68            slot,
69            confirmation_count: 1,
70        }
71    }
72
73    // The number of slots for which this vote is locked
74    pub fn lockout(&self) -> u64 {
75        (INITIAL_LOCKOUT as u64).pow(self.confirmation_count)
76    }
77
78    // The last slot at which a vote is still locked out. Validators should not
79    // vote on a slot in another fork which is less than or equal to this slot
80    // to avoid having their stake slashed.
81    #[allow(clippy::integer_arithmetic)]
82    pub fn last_locked_out_slot(&self) -> Slot {
83        self.slot + self.lockout()
84    }
85
86    pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
87        self.last_locked_out_slot() >= slot
88    }
89}
90
91#[frozen_abi(digest = "GwJfVFsATSj7nvKwtUkHYzqPRaPY6SLxPGXApuCya3x5")]
92#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
93pub struct VoteStateUpdate {
94    /// The proposed tower
95    pub lockouts: VecDeque<Lockout>,
96    /// The proposed root
97    pub root: Option<Slot>,
98    /// signature of the bank's state at the last slot
99    pub hash: Hash,
100    /// processing timestamp of last slot
101    pub timestamp: Option<UnixTimestamp>,
102}
103
104impl From<Vec<(Slot, u32)>> for VoteStateUpdate {
105    fn from(recent_slots: Vec<(Slot, u32)>) -> Self {
106        let lockouts: VecDeque<Lockout> = recent_slots
107            .into_iter()
108            .map(|(slot, confirmation_count)| Lockout {
109                slot,
110                confirmation_count,
111            })
112            .collect();
113        Self {
114            lockouts,
115            root: None,
116            hash: Hash::default(),
117            timestamp: None,
118        }
119    }
120}
121
122impl VoteStateUpdate {
123    pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
124        Self {
125            lockouts,
126            root,
127            hash,
128            timestamp: None,
129        }
130    }
131
132    pub fn slots(&self) -> Vec<Slot> {
133        self.lockouts.iter().map(|lockout| lockout.slot).collect()
134    }
135
136    pub fn last_voted_slot(&self) -> Option<Slot> {
137        self.lockouts.back().map(|l| l.slot)
138    }
139}
140
141#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
142pub struct VoteInit {
143    pub node_pubkey: Pubkey,
144    pub authorized_voter: Pubkey,
145    pub authorized_withdrawer: Pubkey,
146    pub commission: u8,
147}
148
149#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
150pub enum VoteAuthorize {
151    Voter,
152    Withdrawer,
153}
154
155#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
156pub struct VoteAuthorizeWithSeedArgs {
157    pub authorization_type: VoteAuthorize,
158    pub current_authority_derived_key_owner: Pubkey,
159    pub current_authority_derived_key_seed: String,
160    pub new_authority: Pubkey,
161}
162
163#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
164pub struct VoteAuthorizeCheckedWithSeedArgs {
165    pub authorization_type: VoteAuthorize,
166    pub current_authority_derived_key_owner: Pubkey,
167    pub current_authority_derived_key_seed: String,
168}
169
170#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
171pub struct BlockTimestamp {
172    pub slot: Slot,
173    pub timestamp: UnixTimestamp,
174}
175
176// this is how many epochs a voter can be remembered for slashing
177const MAX_ITEMS: usize = 32;
178
179#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
180pub struct CircBuf<I> {
181    buf: [I; MAX_ITEMS],
182    /// next pointer
183    idx: usize,
184    is_empty: bool,
185}
186
187impl<I: Default + Copy> Default for CircBuf<I> {
188    #[allow(clippy::integer_arithmetic)]
189    fn default() -> Self {
190        Self {
191            buf: [I::default(); MAX_ITEMS],
192            idx: MAX_ITEMS - 1,
193            is_empty: true,
194        }
195    }
196}
197
198impl<I> CircBuf<I> {
199    #[allow(clippy::integer_arithmetic)]
200    pub fn append(&mut self, item: I) {
201        // remember prior delegate and when we switched, to support later slashing
202        self.idx += 1;
203        self.idx %= MAX_ITEMS;
204
205        self.buf[self.idx] = item;
206        self.is_empty = false;
207    }
208
209    pub fn buf(&self) -> &[I; MAX_ITEMS] {
210        &self.buf
211    }
212
213    pub fn last(&self) -> Option<&I> {
214        if !self.is_empty {
215            Some(&self.buf[self.idx])
216        } else {
217            None
218        }
219    }
220}
221
222#[frozen_abi(digest = "4oxo6mBc8zrZFA89RgKsNyMqqM52iVrCphsWfaHjaAAY")]
223#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
224pub struct VoteState {
225    /// the node that votes in this account
226    pub node_pubkey: Pubkey,
227
228    /// the signer for withdrawals
229    pub authorized_withdrawer: Pubkey,
230    /// percentage (0-100) that represents what part of a rewards
231    ///  payout should be given to this VoteAccount
232    pub commission: u8,
233
234    pub votes: VecDeque<Lockout>,
235
236    // This usually the last Lockout which was popped from self.votes.
237    // However, it can be arbitrary slot, when being used inside Tower
238    pub root_slot: Option<Slot>,
239
240    /// the signer for vote transactions
241    authorized_voters: AuthorizedVoters,
242
243    /// history of prior authorized voters and the epochs for which
244    /// they were set, the bottom end of the range is inclusive,
245    /// the top of the range is exclusive
246    prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
247
248    /// history of how many credits earned by the end of each epoch
249    ///  each tuple is (Epoch, credits, prev_credits)
250    pub epoch_credits: Vec<(Epoch, u64, u64)>,
251
252    /// most recent timestamp submitted with a vote
253    pub last_timestamp: BlockTimestamp,
254}
255
256impl VoteState {
257    pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
258        Self {
259            node_pubkey: vote_init.node_pubkey,
260            authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
261            authorized_withdrawer: vote_init.authorized_withdrawer,
262            commission: vote_init.commission,
263            ..VoteState::default()
264        }
265    }
266
267    pub fn get_authorized_voter(&self, epoch: Epoch) -> Option<Pubkey> {
268        self.authorized_voters.get_authorized_voter(epoch)
269    }
270
271    pub fn authorized_voters(&self) -> &AuthorizedVoters {
272        &self.authorized_voters
273    }
274
275    pub fn prior_voters(&mut self) -> &CircBuf<(Pubkey, Epoch, Epoch)> {
276        &self.prior_voters
277    }
278
279    pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
280        rent.minimum_balance(VoteState::size_of())
281    }
282
283    /// Upper limit on the size of the Vote State
284    /// when votes.len() is MAX_LOCKOUT_HISTORY.
285    pub const fn size_of() -> usize {
286        3731 // see test_vote_state_size_of.
287    }
288
289    pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
290        deserialize::<VoteStateVersions>(input)
291            .map(|versioned| versioned.convert_to_current())
292            .map_err(|_| InstructionError::InvalidAccountData)
293    }
294
295    pub fn serialize(
296        versioned: &VoteStateVersions,
297        output: &mut [u8],
298    ) -> Result<(), InstructionError> {
299        serialize_into(output, versioned).map_err(|err| match *err {
300            ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
301            _ => InstructionError::GenericError,
302        })
303    }
304
305    /// returns commission split as (voter_portion, staker_portion, was_split) tuple
306    ///
307    ///  if commission calculation is 100% one way or other,
308    ///   indicate with false for was_split
309    #[allow(clippy::integer_arithmetic)]
310    pub fn commission_split(&self, on: u64) -> (u64, u64, bool) {
311        match self.commission.min(100) {
312            0 => (0, on, false),
313            100 => (on, 0, false),
314            split => {
315                let on = u128::from(on);
316                // Calculate mine and theirs independently and symmetrically instead of
317                // using the remainder of the other to treat them strictly equally.
318                // This is also to cancel the rewarding if either of the parties
319                // should receive only fractional scoobies, resulting in not being rewarded at all.
320                // Thus, note that we intentionally discard any residual fractional scoobies.
321                let mine = on * u128::from(split) / 100u128;
322                let theirs = on * u128::from(100 - split) / 100u128;
323
324                (mine as u64, theirs as u64, true)
325            }
326        }
327    }
328
329    /// Returns if the vote state contains a slot `candidate_slot`
330    pub fn contains_slot(&self, candidate_slot: Slot) -> bool {
331        self.votes
332            .binary_search_by(|lockout| lockout.slot.cmp(&candidate_slot))
333            .is_ok()
334    }
335
336    #[cfg(test)]
337    fn get_max_sized_vote_state() -> VoteState {
338        let mut authorized_voters = AuthorizedVoters::default();
339        for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
340            authorized_voters.insert(i, Pubkey::new_unique());
341        }
342
343        VoteState {
344            votes: VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]),
345            root_slot: Some(std::u64::MAX),
346            epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
347            authorized_voters,
348            ..Self::default()
349        }
350    }
351
352    pub fn process_next_vote_slot(&mut self, next_vote_slot: Slot, epoch: Epoch) {
353        // Ignore votes for slots earlier than we already have votes for
354        if self
355            .last_voted_slot()
356            .map_or(false, |last_voted_slot| next_vote_slot <= last_voted_slot)
357        {
358            return;
359        }
360
361        let vote = Lockout::new(next_vote_slot);
362
363        self.pop_expired_votes(next_vote_slot);
364
365        // Once the stack is full, pop the oldest lockout and distribute rewards
366        if self.votes.len() == MAX_LOCKOUT_HISTORY {
367            let vote = self.votes.pop_front().unwrap();
368            self.root_slot = Some(vote.slot);
369
370            self.increment_credits(epoch, 1);
371        }
372        self.votes.push_back(vote);
373        self.double_lockouts();
374    }
375
376    /// increment credits, record credits for last epoch if new epoch
377    #[allow(clippy::integer_arithmetic)]
378    pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
379        // increment credits, record by epoch
380
381        // never seen a credit
382        if self.epoch_credits.is_empty() {
383            self.epoch_credits.push((epoch, 0, 0));
384        } else if epoch != self.epoch_credits.last().unwrap().0 {
385            let (_, credits, prev_credits) = *self.epoch_credits.last().unwrap();
386
387            if credits != prev_credits {
388                // if credits were earned previous epoch
389                // append entry at end of list for the new epoch
390                self.epoch_credits.push((epoch, credits, credits));
391            } else {
392                // else just move the current epoch
393                self.epoch_credits.last_mut().unwrap().0 = epoch;
394            }
395
396            // Remove too old epoch_credits
397            if self.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
398                self.epoch_credits.remove(0);
399            }
400        }
401
402        self.epoch_credits.last_mut().unwrap().1 += credits;
403    }
404
405    #[allow(clippy::integer_arithmetic)]
406    pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
407        if position < self.votes.len() {
408            let pos = self.votes.len() - 1 - position;
409            self.votes.get(pos)
410        } else {
411            None
412        }
413    }
414
415    pub fn last_lockout(&self) -> Option<&Lockout> {
416        self.votes.back()
417    }
418
419    pub fn last_voted_slot(&self) -> Option<Slot> {
420        self.last_lockout().map(|v| v.slot)
421    }
422
423    // Upto MAX_LOCKOUT_HISTORY many recent unexpired
424    // vote slots pushed onto the stack.
425    pub fn tower(&self) -> Vec<Slot> {
426        self.votes.iter().map(|v| v.slot).collect()
427    }
428
429    pub fn current_epoch(&self) -> Epoch {
430        if self.epoch_credits.is_empty() {
431            0
432        } else {
433            self.epoch_credits.last().unwrap().0
434        }
435    }
436
437    /// Number of "credits" owed to this account from the mining pool. Submit this
438    /// VoteState to the Rewards program to trade credits for scoobies.
439    pub fn credits(&self) -> u64 {
440        if self.epoch_credits.is_empty() {
441            0
442        } else {
443            self.epoch_credits.last().unwrap().1
444        }
445    }
446
447    /// Number of "credits" owed to this account from the mining pool on a per-epoch basis,
448    ///  starting from credits observed.
449    /// Each tuple of (Epoch, u64, u64) is read as (epoch, credits, prev_credits), where
450    ///   credits for each epoch is credits - prev_credits; while redundant this makes
451    ///   calculating rewards over partial epochs nice and simple
452    pub fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
453        &self.epoch_credits
454    }
455
456    pub fn set_new_authorized_voter<F>(
457        &mut self,
458        authorized_pubkey: &Pubkey,
459        current_epoch: Epoch,
460        target_epoch: Epoch,
461        verify: F,
462    ) -> Result<(), InstructionError>
463    where
464        F: Fn(Pubkey) -> Result<(), InstructionError>,
465    {
466        let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
467        verify(epoch_authorized_voter)?;
468
469        // The offset in slots `n` on which the target_epoch
470        // (default value `DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET`) is
471        // calculated is the number of slots available from the
472        // first slot `S` of an epoch in which to set a new voter for
473        // the epoch at `S` + `n`
474        if self.authorized_voters.contains(target_epoch) {
475            return Err(VoteError::TooSoonToReauthorize.into());
476        }
477
478        // Get the latest authorized_voter
479        let (latest_epoch, latest_authorized_pubkey) = self
480            .authorized_voters
481            .last()
482            .ok_or(InstructionError::InvalidAccountData)?;
483
484        // If we're not setting the same pubkey as authorized pubkey again,
485        // then update the list of prior voters to mark the expiration
486        // of the old authorized pubkey
487        if latest_authorized_pubkey != authorized_pubkey {
488            // Update the epoch ranges of authorized pubkeys that will be expired
489            let epoch_of_last_authorized_switch =
490                self.prior_voters.last().map(|range| range.2).unwrap_or(0);
491
492            // target_epoch must:
493            // 1) Be monotonically increasing due to the clock always
494            //    moving forward
495            // 2) not be equal to latest epoch otherwise this
496            //    function would have returned TooSoonToReauthorize error
497            //    above
498            assert!(target_epoch > *latest_epoch);
499
500            // Commit the new state
501            self.prior_voters.append((
502                *latest_authorized_pubkey,
503                epoch_of_last_authorized_switch,
504                target_epoch,
505            ));
506        }
507
508        self.authorized_voters
509            .insert(target_epoch, *authorized_pubkey);
510
511        Ok(())
512    }
513
514    pub fn get_and_update_authorized_voter(
515        &mut self,
516        current_epoch: Epoch,
517    ) -> Result<Pubkey, InstructionError> {
518        let pubkey = self
519            .authorized_voters
520            .get_and_cache_authorized_voter_for_epoch(current_epoch)
521            .ok_or(InstructionError::InvalidAccountData)?;
522        self.authorized_voters
523            .purge_authorized_voters(current_epoch);
524        Ok(pubkey)
525    }
526
527    // Pop all recent votes that are not locked out at the next vote slot.  This
528    // allows validators to switch forks once their votes for another fork have
529    // expired. This also allows validators continue voting on recent blocks in
530    // the same fork without increasing lockouts.
531    pub fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
532        while let Some(vote) = self.last_lockout() {
533            if !vote.is_locked_out_at_slot(next_vote_slot) {
534                self.votes.pop_back();
535            } else {
536                break;
537            }
538        }
539    }
540
541    #[allow(clippy::integer_arithmetic)]
542    pub fn double_lockouts(&mut self) {
543        let stack_depth = self.votes.len();
544        for (i, v) in self.votes.iter_mut().enumerate() {
545            // Don't increase the lockout for this vote until we get more confirmations
546            // than the max number of confirmations this vote has seen
547            if stack_depth > i + v.confirmation_count as usize {
548                v.confirmation_count += 1;
549            }
550        }
551    }
552
553    pub fn process_timestamp(
554        &mut self,
555        slot: Slot,
556        timestamp: UnixTimestamp,
557    ) -> Result<(), VoteError> {
558        if (slot < self.last_timestamp.slot || timestamp < self.last_timestamp.timestamp)
559            || (slot == self.last_timestamp.slot
560                && BlockTimestamp { slot, timestamp } != self.last_timestamp
561                && self.last_timestamp.slot != 0)
562        {
563            return Err(VoteError::TimestampTooOld);
564        }
565        self.last_timestamp = BlockTimestamp { slot, timestamp };
566        Ok(())
567    }
568
569    #[allow(clippy::integer_arithmetic)]
570    pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
571        const VERSION_OFFSET: usize = 4;
572        data.len() == VoteState::size_of()
573            && data[VERSION_OFFSET..VERSION_OFFSET + DEFAULT_PRIOR_VOTERS_OFFSET]
574                != [0; DEFAULT_PRIOR_VOTERS_OFFSET]
575    }
576}
577
578pub mod serde_compact_vote_state_update {
579    use {
580        super::*,
581        crate::{
582            clock::{Slot, UnixTimestamp},
583            serde_varint, short_vec,
584            vote::state::Lockout,
585        },
586        serde::{Deserialize, Deserializer, Serialize, Serializer},
587    };
588
589    #[derive(Deserialize, Serialize, AbiExample)]
590    struct LockoutOffset {
591        #[serde(with = "serde_varint")]
592        offset: Slot,
593        confirmation_count: u8,
594    }
595
596    #[derive(Deserialize, Serialize)]
597    struct CompactVoteStateUpdate {
598        root: Slot,
599        #[serde(with = "short_vec")]
600        lockout_offsets: Vec<LockoutOffset>,
601        hash: Hash,
602        timestamp: Option<UnixTimestamp>,
603    }
604
605    pub fn serialize<S>(
606        vote_state_update: &VoteStateUpdate,
607        serializer: S,
608    ) -> Result<S::Ok, S::Error>
609    where
610        S: Serializer,
611    {
612        let lockout_offsets = vote_state_update.lockouts.iter().scan(
613            vote_state_update.root.unwrap_or_default(),
614            |slot, lockout| {
615                let offset = match lockout.slot.checked_sub(*slot) {
616                    None => return Some(Err(serde::ser::Error::custom("Invalid vote lockout"))),
617                    Some(offset) => offset,
618                };
619                let confirmation_count = match u8::try_from(lockout.confirmation_count) {
620                    Ok(confirmation_count) => confirmation_count,
621                    Err(_) => {
622                        return Some(Err(serde::ser::Error::custom("Invalid confirmation count")))
623                    }
624                };
625                let lockout_offset = LockoutOffset {
626                    offset,
627                    confirmation_count,
628                };
629                *slot = lockout.slot;
630                Some(Ok(lockout_offset))
631            },
632        );
633        let compact_vote_state_update = CompactVoteStateUpdate {
634            root: vote_state_update.root.unwrap_or(Slot::MAX),
635            lockout_offsets: lockout_offsets.collect::<Result<_, _>>()?,
636            hash: vote_state_update.hash,
637            timestamp: vote_state_update.timestamp,
638        };
639        compact_vote_state_update.serialize(serializer)
640    }
641
642    pub fn deserialize<'de, D>(deserializer: D) -> Result<VoteStateUpdate, D::Error>
643    where
644        D: Deserializer<'de>,
645    {
646        let CompactVoteStateUpdate {
647            root,
648            lockout_offsets,
649            hash,
650            timestamp,
651        } = CompactVoteStateUpdate::deserialize(deserializer)?;
652        let root = (root != Slot::MAX).then_some(root);
653        let lockouts =
654            lockout_offsets
655                .iter()
656                .scan(root.unwrap_or_default(), |slot, lockout_offset| {
657                    *slot = match slot.checked_add(lockout_offset.offset) {
658                        None => {
659                            return Some(Err(serde::de::Error::custom("Invalid lockout offset")))
660                        }
661                        Some(slot) => slot,
662                    };
663                    let lockout = Lockout {
664                        slot: *slot,
665                        confirmation_count: u32::from(lockout_offset.confirmation_count),
666                    };
667                    Some(Ok(lockout))
668                });
669        Ok(VoteStateUpdate {
670            root,
671            lockouts: lockouts.collect::<Result<_, _>>()?,
672            hash,
673            timestamp,
674        })
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use {super::*, itertools::Itertools, rand::Rng};
681
682    #[test]
683    fn test_vote_serialize() {
684        let mut buffer: Vec<u8> = vec![0; VoteState::size_of()];
685        let mut vote_state = VoteState::default();
686        vote_state
687            .votes
688            .resize(MAX_LOCKOUT_HISTORY, Lockout::default());
689        vote_state.root_slot = Some(1);
690        let versioned = VoteStateVersions::new_current(vote_state);
691        assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err());
692        VoteState::serialize(&versioned, &mut buffer).unwrap();
693        assert_eq!(
694            VoteState::deserialize(&buffer).unwrap(),
695            versioned.convert_to_current()
696        );
697    }
698
699    #[test]
700    fn test_vote_state_commission_split() {
701        let vote_state = VoteState::default();
702
703        assert_eq!(vote_state.commission_split(1), (0, 1, false));
704
705        let mut vote_state = VoteState {
706            commission: std::u8::MAX,
707            ..VoteState::default()
708        };
709        assert_eq!(vote_state.commission_split(1), (1, 0, false));
710
711        vote_state.commission = 99;
712        assert_eq!(vote_state.commission_split(10), (9, 0, true));
713
714        vote_state.commission = 1;
715        assert_eq!(vote_state.commission_split(10), (0, 9, true));
716
717        vote_state.commission = 50;
718        let (voter_portion, staker_portion, was_split) = vote_state.commission_split(10);
719
720        assert_eq!((voter_portion, staker_portion, was_split), (5, 5, true));
721    }
722
723    #[test]
724    fn test_vote_state_epoch_credits() {
725        let mut vote_state = VoteState::default();
726
727        assert_eq!(vote_state.credits(), 0);
728        assert_eq!(vote_state.epoch_credits().clone(), vec![]);
729
730        let mut expected = vec![];
731        let mut credits = 0;
732        let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
733        for epoch in 0..epochs {
734            for _j in 0..epoch {
735                vote_state.increment_credits(epoch, 1);
736                credits += 1;
737            }
738            expected.push((epoch, credits, credits - epoch));
739        }
740
741        while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
742            expected.remove(0);
743        }
744
745        assert_eq!(vote_state.credits(), credits);
746        assert_eq!(vote_state.epoch_credits().clone(), expected);
747    }
748
749    #[test]
750    fn test_vote_state_epoch0_no_credits() {
751        let mut vote_state = VoteState::default();
752
753        assert_eq!(vote_state.epoch_credits().len(), 0);
754        vote_state.increment_credits(1, 1);
755        assert_eq!(vote_state.epoch_credits().len(), 1);
756
757        vote_state.increment_credits(2, 1);
758        assert_eq!(vote_state.epoch_credits().len(), 2);
759    }
760
761    #[test]
762    fn test_vote_state_increment_credits() {
763        let mut vote_state = VoteState::default();
764
765        let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
766        for i in 0..credits {
767            vote_state.increment_credits(i, 1);
768        }
769        assert_eq!(vote_state.credits(), credits);
770        assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
771    }
772
773    #[test]
774    fn test_vote_process_timestamp() {
775        let (slot, timestamp) = (15, 1_575_412_285);
776        let mut vote_state = VoteState {
777            last_timestamp: BlockTimestamp { slot, timestamp },
778            ..VoteState::default()
779        };
780
781        assert_eq!(
782            vote_state.process_timestamp(slot - 1, timestamp + 1),
783            Err(VoteError::TimestampTooOld)
784        );
785        assert_eq!(
786            vote_state.last_timestamp,
787            BlockTimestamp { slot, timestamp }
788        );
789        assert_eq!(
790            vote_state.process_timestamp(slot + 1, timestamp - 1),
791            Err(VoteError::TimestampTooOld)
792        );
793        assert_eq!(
794            vote_state.process_timestamp(slot, timestamp + 1),
795            Err(VoteError::TimestampTooOld)
796        );
797        assert_eq!(vote_state.process_timestamp(slot, timestamp), Ok(()));
798        assert_eq!(
799            vote_state.last_timestamp,
800            BlockTimestamp { slot, timestamp }
801        );
802        assert_eq!(vote_state.process_timestamp(slot + 1, timestamp), Ok(()));
803        assert_eq!(
804            vote_state.last_timestamp,
805            BlockTimestamp {
806                slot: slot + 1,
807                timestamp
808            }
809        );
810        assert_eq!(
811            vote_state.process_timestamp(slot + 2, timestamp + 1),
812            Ok(())
813        );
814        assert_eq!(
815            vote_state.last_timestamp,
816            BlockTimestamp {
817                slot: slot + 2,
818                timestamp: timestamp + 1
819            }
820        );
821
822        // Test initial vote
823        vote_state.last_timestamp = BlockTimestamp::default();
824        assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
825    }
826
827    #[test]
828    fn test_get_and_update_authorized_voter() {
829        let original_voter = Pubkey::new_unique();
830        let mut vote_state = VoteState::new(
831            &VoteInit {
832                node_pubkey: original_voter,
833                authorized_voter: original_voter,
834                authorized_withdrawer: original_voter,
835                commission: 0,
836            },
837            &Clock::default(),
838        );
839
840        assert_eq!(vote_state.authorized_voters.len(), 1);
841        assert_eq!(
842            *vote_state.authorized_voters.first().unwrap().1,
843            original_voter
844        );
845
846        // If no new authorized voter was set, the same authorized voter
847        // is locked into the next epoch
848        assert_eq!(
849            vote_state.get_and_update_authorized_voter(1).unwrap(),
850            original_voter
851        );
852
853        // Try to get the authorized voter for epoch 5, implies
854        // the authorized voter for epochs 1-4 were unchanged
855        assert_eq!(
856            vote_state.get_and_update_authorized_voter(5).unwrap(),
857            original_voter
858        );
859
860        // Authorized voter for expired epoch 0..5 should have been
861        // purged and no longer queryable
862        assert_eq!(vote_state.authorized_voters.len(), 1);
863        for i in 0..5 {
864            assert!(vote_state
865                .authorized_voters
866                .get_authorized_voter(i)
867                .is_none());
868        }
869
870        // Set an authorized voter change at slot 7
871        let new_authorized_voter = Pubkey::new_unique();
872        vote_state
873            .set_new_authorized_voter(&new_authorized_voter, 5, 7, |_| Ok(()))
874            .unwrap();
875
876        // Try to get the authorized voter for epoch 6, unchanged
877        assert_eq!(
878            vote_state.get_and_update_authorized_voter(6).unwrap(),
879            original_voter
880        );
881
882        // Try to get the authorized voter for epoch 7 and onwards, should
883        // be the new authorized voter
884        for i in 7..10 {
885            assert_eq!(
886                vote_state.get_and_update_authorized_voter(i).unwrap(),
887                new_authorized_voter
888            );
889        }
890        assert_eq!(vote_state.authorized_voters.len(), 1);
891    }
892
893    #[test]
894    fn test_set_new_authorized_voter() {
895        let original_voter = Pubkey::new_unique();
896        let epoch_offset = 15;
897        let mut vote_state = VoteState::new(
898            &VoteInit {
899                node_pubkey: original_voter,
900                authorized_voter: original_voter,
901                authorized_withdrawer: original_voter,
902                commission: 0,
903            },
904            &Clock::default(),
905        );
906
907        assert!(vote_state.prior_voters.last().is_none());
908
909        let new_voter = Pubkey::new_unique();
910        // Set a new authorized voter
911        vote_state
912            .set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(()))
913            .unwrap();
914
915        assert_eq!(vote_state.prior_voters.idx, 0);
916        assert_eq!(
917            vote_state.prior_voters.last(),
918            Some(&(original_voter, 0, epoch_offset))
919        );
920
921        // Trying to set authorized voter for same epoch again should fail
922        assert_eq!(
923            vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(())),
924            Err(VoteError::TooSoonToReauthorize.into())
925        );
926
927        // Setting the same authorized voter again should succeed
928        vote_state
929            .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, |_| Ok(()))
930            .unwrap();
931
932        // Set a third and fourth authorized voter
933        let new_voter2 = Pubkey::new_unique();
934        vote_state
935            .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, |_| Ok(()))
936            .unwrap();
937        assert_eq!(vote_state.prior_voters.idx, 1);
938        assert_eq!(
939            vote_state.prior_voters.last(),
940            Some(&(new_voter, epoch_offset, 3 + epoch_offset))
941        );
942
943        let new_voter3 = Pubkey::new_unique();
944        vote_state
945            .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, |_| Ok(()))
946            .unwrap();
947        assert_eq!(vote_state.prior_voters.idx, 2);
948        assert_eq!(
949            vote_state.prior_voters.last(),
950            Some(&(new_voter2, 3 + epoch_offset, 6 + epoch_offset))
951        );
952
953        // Check can set back to original voter
954        vote_state
955            .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, |_| Ok(()))
956            .unwrap();
957
958        // Run with these voters for a while, check the ranges of authorized
959        // voters is correct
960        for i in 9..epoch_offset {
961            assert_eq!(
962                vote_state.get_and_update_authorized_voter(i).unwrap(),
963                original_voter
964            );
965        }
966        for i in epoch_offset..3 + epoch_offset {
967            assert_eq!(
968                vote_state.get_and_update_authorized_voter(i).unwrap(),
969                new_voter
970            );
971        }
972        for i in 3 + epoch_offset..6 + epoch_offset {
973            assert_eq!(
974                vote_state.get_and_update_authorized_voter(i).unwrap(),
975                new_voter2
976            );
977        }
978        for i in 6 + epoch_offset..9 + epoch_offset {
979            assert_eq!(
980                vote_state.get_and_update_authorized_voter(i).unwrap(),
981                new_voter3
982            );
983        }
984        for i in 9 + epoch_offset..=10 + epoch_offset {
985            assert_eq!(
986                vote_state.get_and_update_authorized_voter(i).unwrap(),
987                original_voter
988            );
989        }
990    }
991
992    #[test]
993    fn test_authorized_voter_is_locked_within_epoch() {
994        let original_voter = Pubkey::new_unique();
995        let mut vote_state = VoteState::new(
996            &VoteInit {
997                node_pubkey: original_voter,
998                authorized_voter: original_voter,
999                authorized_withdrawer: original_voter,
1000                commission: 0,
1001            },
1002            &Clock::default(),
1003        );
1004
1005        // Test that it's not possible to set a new authorized
1006        // voter within the same epoch, even if none has been
1007        // explicitly set before
1008        let new_voter = Pubkey::new_unique();
1009        assert_eq!(
1010            vote_state.set_new_authorized_voter(&new_voter, 1, 1, |_| Ok(())),
1011            Err(VoteError::TooSoonToReauthorize.into())
1012        );
1013
1014        assert_eq!(vote_state.get_authorized_voter(1), Some(original_voter));
1015
1016        // Set a new authorized voter for a future epoch
1017        assert_eq!(
1018            vote_state.set_new_authorized_voter(&new_voter, 1, 2, |_| Ok(())),
1019            Ok(())
1020        );
1021
1022        // Test that it's not possible to set a new authorized
1023        // voter within the same epoch, even if none has been
1024        // explicitly set before
1025        assert_eq!(
1026            vote_state.set_new_authorized_voter(&original_voter, 3, 3, |_| Ok(())),
1027            Err(VoteError::TooSoonToReauthorize.into())
1028        );
1029
1030        assert_eq!(vote_state.get_authorized_voter(3), Some(new_voter));
1031    }
1032
1033    #[test]
1034    fn test_vote_state_size_of() {
1035        let vote_state = VoteState::get_max_sized_vote_state();
1036        let vote_state = VoteStateVersions::new_current(vote_state);
1037        let size = bincode::serialized_size(&vote_state).unwrap();
1038        assert_eq!(VoteState::size_of() as u64, size);
1039    }
1040
1041    #[test]
1042    fn test_vote_state_max_size() {
1043        let mut max_sized_data = vec![0; VoteState::size_of()];
1044        let vote_state = VoteState::get_max_sized_vote_state();
1045        let (start_leader_schedule_epoch, _) = vote_state.authorized_voters.last().unwrap();
1046        let start_current_epoch =
1047            start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1;
1048
1049        let mut vote_state = Some(vote_state);
1050        for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
1051            vote_state.as_mut().map(|vote_state| {
1052                vote_state.set_new_authorized_voter(
1053                    &Pubkey::new_unique(),
1054                    i,
1055                    i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
1056                    |_| Ok(()),
1057                )
1058            });
1059
1060            let versioned = VoteStateVersions::new_current(vote_state.take().unwrap());
1061            VoteState::serialize(&versioned, &mut max_sized_data).unwrap();
1062            vote_state = Some(versioned.convert_to_current());
1063        }
1064    }
1065
1066    #[test]
1067    fn test_default_vote_state_is_uninitialized() {
1068        // The default `VoteState` is stored to de-initialize a zero-balance vote account,
1069        // so must remain such that `VoteStateVersions::is_uninitialized()` returns true
1070        // when called on a `VoteStateVersions` that stores it
1071        assert!(VoteStateVersions::new_current(VoteState::default()).is_uninitialized());
1072    }
1073
1074    #[test]
1075    fn test_is_correct_size_and_initialized() {
1076        // Check all zeroes
1077        let mut vote_account_data = vec![0; VoteState::size_of()];
1078        assert!(!VoteState::is_correct_size_and_initialized(
1079            &vote_account_data
1080        ));
1081
1082        // Check default VoteState
1083        let default_account_state = VoteStateVersions::new_current(VoteState::default());
1084        VoteState::serialize(&default_account_state, &mut vote_account_data).unwrap();
1085        assert!(!VoteState::is_correct_size_and_initialized(
1086            &vote_account_data
1087        ));
1088
1089        // Check non-zero data shorter than offset index used
1090        let short_data = vec![1; DEFAULT_PRIOR_VOTERS_OFFSET];
1091        assert!(!VoteState::is_correct_size_and_initialized(&short_data));
1092
1093        // Check non-zero large account
1094        let mut large_vote_data = vec![1; 2 * VoteState::size_of()];
1095        let default_account_state = VoteStateVersions::new_current(VoteState::default());
1096        VoteState::serialize(&default_account_state, &mut large_vote_data).unwrap();
1097        assert!(!VoteState::is_correct_size_and_initialized(
1098            &vote_account_data
1099        ));
1100
1101        // Check populated VoteState
1102        let account_state = VoteStateVersions::new_current(VoteState::new(
1103            &VoteInit {
1104                node_pubkey: Pubkey::new_unique(),
1105                authorized_voter: Pubkey::new_unique(),
1106                authorized_withdrawer: Pubkey::new_unique(),
1107                commission: 0,
1108            },
1109            &Clock::default(),
1110        ));
1111        VoteState::serialize(&account_state, &mut vote_account_data).unwrap();
1112        assert!(VoteState::is_correct_size_and_initialized(
1113            &vote_account_data
1114        ));
1115    }
1116
1117    #[test]
1118    fn test_minimum_balance() {
1119        let rent = cbe_program::rent::Rent::default();
1120        let minimum_balance = rent.minimum_balance(VoteState::size_of());
1121        // golden, may need updating when vote_state grows
1122        assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
1123    }
1124
1125    #[test]
1126    fn test_serde_compact_vote_state_update() {
1127        let mut rng = rand::thread_rng();
1128        for _ in 0..5000 {
1129            run_serde_compact_vote_state_update(&mut rng);
1130        }
1131    }
1132
1133    #[allow(clippy::integer_arithmetic)]
1134    fn run_serde_compact_vote_state_update<R: Rng>(rng: &mut R) {
1135        let lockouts: VecDeque<_> = std::iter::repeat_with(|| Lockout {
1136            slot: 149_303_885 + rng.gen_range(0, 10_000),
1137            confirmation_count: rng.gen_range(0, 33),
1138        })
1139        .take(32)
1140        .sorted_by_key(|lockout| lockout.slot)
1141        .collect();
1142        let root = rng
1143            .gen_ratio(1, 2)
1144            .then(|| lockouts[0].slot - rng.gen_range(0, 1_000));
1145        let timestamp = rng.gen_ratio(1, 2).then(|| rng.gen());
1146        let hash = Hash::from(rng.gen::<[u8; 32]>());
1147        let vote_state_update = VoteStateUpdate {
1148            lockouts,
1149            root,
1150            hash,
1151            timestamp,
1152        };
1153        #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
1154        enum VoteInstruction {
1155            #[serde(with = "serde_compact_vote_state_update")]
1156            UpdateVoteState(VoteStateUpdate),
1157            UpdateVoteStateSwitch(
1158                #[serde(with = "serde_compact_vote_state_update")] VoteStateUpdate,
1159                Hash,
1160            ),
1161        }
1162        let vote = VoteInstruction::UpdateVoteState(vote_state_update.clone());
1163        let bytes = bincode::serialize(&vote).unwrap();
1164        assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
1165        let hash = Hash::from(rng.gen::<[u8; 32]>());
1166        let vote = VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash);
1167        let bytes = bincode::serialize(&vote).unwrap();
1168        assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
1169    }
1170}