solana_extra_wasm/program/vote/vote_state/
mod.rs

1use {
2    crate::program::vote::{authorized_voters::AuthorizedVoters, id, vote_error::VoteError},
3    bincode::{deserialize, serialize_into, ErrorKind},
4    serde_derive::{Deserialize, Serialize},
5    solana_sdk::{
6        account::{AccountSharedData, ReadableAccount, WritableAccount},
7        clock::{Epoch, Slot, UnixTimestamp},
8        feature_set::{self, filter_votes_outside_slot_hashes, FeatureSet},
9        hash::Hash,
10        instruction::InstructionError,
11        pubkey::Pubkey,
12        rent::Rent,
13        slot_hashes::SlotHash,
14        sysvar::clock::Clock,
15        transaction_context::{BorrowedAccount, InstructionContext, TransactionContext},
16    },
17    std::{
18        cmp::Ordering,
19        collections::{HashSet, VecDeque},
20        fmt::Debug,
21    },
22};
23
24mod vote_state_0_23_5;
25pub mod vote_state_versions;
26pub use vote_state_versions::*;
27
28// Maximum number of votes to keep around, tightly coupled with epoch_schedule::MINIMUM_SLOTS_PER_EPOCH
29pub const MAX_LOCKOUT_HISTORY: usize = 31;
30pub const INITIAL_LOCKOUT: usize = 2;
31
32// Maximum number of credits history to keep around
33pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
34
35// Offset of VoteState::prior_voters, for determining initialization status without deserialization
36const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82;
37
38#[frozen_abi(digest = "6LBwH5w3WyAWZhsM3KTG9QZP7nYBhcC61K33kHR6gMAD")]
39#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiEnumVisitor, AbiExample)]
40pub enum VoteTransaction {
41    Vote(Vote),
42    VoteStateUpdate(VoteStateUpdate),
43}
44
45impl VoteTransaction {
46    pub fn slots(&self) -> Vec<Slot> {
47        match self {
48            VoteTransaction::Vote(vote) => vote.slots.clone(),
49            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.slots(),
50        }
51    }
52
53    pub fn slot(&self, i: usize) -> Slot {
54        match self {
55            VoteTransaction::Vote(vote) => vote.slots[i],
56            VoteTransaction::VoteStateUpdate(vote_state_update) => {
57                vote_state_update.lockouts[i].slot
58            }
59        }
60    }
61
62    pub fn len(&self) -> usize {
63        match self {
64            VoteTransaction::Vote(vote) => vote.slots.len(),
65            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.lockouts.len(),
66        }
67    }
68
69    pub fn is_empty(&self) -> bool {
70        match self {
71            VoteTransaction::Vote(vote) => vote.slots.is_empty(),
72            VoteTransaction::VoteStateUpdate(vote_state_update) => {
73                vote_state_update.lockouts.is_empty()
74            }
75        }
76    }
77
78    pub fn hash(&self) -> Hash {
79        match self {
80            VoteTransaction::Vote(vote) => vote.hash,
81            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
82        }
83    }
84
85    pub fn timestamp(&self) -> Option<UnixTimestamp> {
86        match self {
87            VoteTransaction::Vote(vote) => vote.timestamp,
88            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp,
89        }
90    }
91
92    pub fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
93        match self {
94            VoteTransaction::Vote(vote) => vote.timestamp = ts,
95            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp = ts,
96        }
97    }
98
99    pub fn last_voted_slot(&self) -> Option<Slot> {
100        match self {
101            VoteTransaction::Vote(vote) => vote.slots.last().copied(),
102            VoteTransaction::VoteStateUpdate(vote_state_update) => {
103                Some(vote_state_update.lockouts.back()?.slot)
104            }
105        }
106    }
107
108    pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
109        Some((self.last_voted_slot()?, self.hash()))
110    }
111}
112
113impl From<Vote> for VoteTransaction {
114    fn from(vote: Vote) -> Self {
115        VoteTransaction::Vote(vote)
116    }
117}
118
119impl From<VoteStateUpdate> for VoteTransaction {
120    fn from(vote_state_update: VoteStateUpdate) -> Self {
121        VoteTransaction::VoteStateUpdate(vote_state_update)
122    }
123}
124
125#[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")]
126#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
127pub struct Vote {
128    /// A stack of votes starting with the oldest vote
129    pub slots: Vec<Slot>,
130    /// signature of the bank's state at the last slot
131    pub hash: Hash,
132    /// processing timestamp of last slot
133    pub timestamp: Option<UnixTimestamp>,
134}
135
136impl Vote {
137    pub fn new(slots: Vec<Slot>, hash: Hash) -> Self {
138        Self {
139            slots,
140            hash,
141            timestamp: None,
142        }
143    }
144}
145
146#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Copy, Clone, AbiExample)]
147pub struct Lockout {
148    pub slot: Slot,
149    pub confirmation_count: u32,
150}
151
152impl Lockout {
153    pub fn new(slot: Slot) -> Self {
154        Self {
155            slot,
156            confirmation_count: 1,
157        }
158    }
159
160    // The number of slots for which this vote is locked
161    pub fn lockout(&self) -> u64 {
162        (INITIAL_LOCKOUT as u64).pow(self.confirmation_count)
163    }
164
165    // The last slot at which a vote is still locked out. Validators should not
166    // vote on a slot in another fork which is less than or equal to this slot
167    // to avoid having their stake slashed.
168    pub fn last_locked_out_slot(&self) -> Slot {
169        self.slot + self.lockout()
170    }
171
172    pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
173        self.last_locked_out_slot() >= slot
174    }
175}
176
177#[frozen_abi(digest = "BctadFJjUKbvPJzr6TszbX6rBfQUNSRKpKKngkzgXgeY")]
178#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
179pub struct VoteStateUpdate {
180    /// The proposed tower
181    pub lockouts: VecDeque<Lockout>,
182    /// The proposed root
183    pub root: Option<Slot>,
184    /// signature of the bank's state at the last slot
185    pub hash: Hash,
186    /// processing timestamp of last slot
187    pub timestamp: Option<UnixTimestamp>,
188}
189
190impl From<Vec<(Slot, u32)>> for VoteStateUpdate {
191    fn from(recent_slots: Vec<(Slot, u32)>) -> Self {
192        let lockouts: VecDeque<Lockout> = recent_slots
193            .into_iter()
194            .map(|(slot, confirmation_count)| Lockout {
195                slot,
196                confirmation_count,
197            })
198            .collect();
199        Self {
200            lockouts,
201            root: None,
202            hash: Hash::default(),
203            timestamp: None,
204        }
205    }
206}
207
208impl VoteStateUpdate {
209    pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
210        Self {
211            lockouts,
212            root,
213            hash,
214            timestamp: None,
215        }
216    }
217
218    pub fn slots(&self) -> Vec<Slot> {
219        self.lockouts.iter().map(|lockout| lockout.slot).collect()
220    }
221}
222
223#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
224pub struct VoteInit {
225    pub node_pubkey: Pubkey,
226    pub authorized_voter: Pubkey,
227    pub authorized_withdrawer: Pubkey,
228    pub commission: u8,
229}
230
231#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
232pub enum VoteAuthorize {
233    Voter,
234    Withdrawer,
235}
236
237#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
238pub struct BlockTimestamp {
239    pub slot: Slot,
240    pub timestamp: UnixTimestamp,
241}
242
243// this is how many epochs a voter can be remembered for slashing
244const MAX_ITEMS: usize = 32;
245
246#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
247pub struct CircBuf<I> {
248    buf: [I; MAX_ITEMS],
249    /// next pointer
250    idx: usize,
251    is_empty: bool,
252}
253
254impl<I: Default + Copy> Default for CircBuf<I> {
255    fn default() -> Self {
256        Self {
257            buf: [I::default(); MAX_ITEMS],
258            idx: MAX_ITEMS - 1,
259            is_empty: true,
260        }
261    }
262}
263
264impl<I> CircBuf<I> {
265    pub fn append(&mut self, item: I) {
266        // remember prior delegate and when we switched, to support later slashing
267        self.idx += 1;
268        self.idx %= MAX_ITEMS;
269
270        self.buf[self.idx] = item;
271        self.is_empty = false;
272    }
273
274    pub fn buf(&self) -> &[I; MAX_ITEMS] {
275        &self.buf
276    }
277
278    pub fn last(&self) -> Option<&I> {
279        if !self.is_empty {
280            Some(&self.buf[self.idx])
281        } else {
282            None
283        }
284    }
285}
286
287#[frozen_abi(digest = "331ZmXrmsUcwbKhzR3C1UEU6uNwZr48ExE54JDKGWA4w")]
288#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
289pub struct VoteState {
290    /// the node that votes in this account
291    pub node_pubkey: Pubkey,
292
293    /// the signer for withdrawals
294    pub authorized_withdrawer: Pubkey,
295    /// percentage (0-100) that represents what part of a rewards
296    ///  payout should be given to this VoteAccount
297    pub commission: u8,
298
299    pub votes: VecDeque<Lockout>,
300
301    // This usually the last Lockout which was popped from self.votes.
302    // However, it can be arbitrary slot, when being used inside Tower
303    pub root_slot: Option<Slot>,
304
305    /// the signer for vote transactions
306    authorized_voters: AuthorizedVoters,
307
308    /// history of prior authorized voters and the epochs for which
309    /// they were set, the bottom end of the range is inclusive,
310    /// the top of the range is exclusive
311    prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
312
313    /// history of how many credits earned by the end of each epoch
314    ///  each tuple is (Epoch, credits, prev_credits)
315    pub epoch_credits: Vec<(Epoch, u64, u64)>,
316
317    /// most recent timestamp submitted with a vote
318    pub last_timestamp: BlockTimestamp,
319}
320
321impl VoteState {
322    pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
323        Self {
324            node_pubkey: vote_init.node_pubkey,
325            authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
326            authorized_withdrawer: vote_init.authorized_withdrawer,
327            commission: vote_init.commission,
328            ..VoteState::default()
329        }
330    }
331
332    pub fn get_authorized_voter(&self, epoch: Epoch) -> Option<Pubkey> {
333        self.authorized_voters.get_authorized_voter(epoch)
334    }
335
336    pub fn authorized_voters(&self) -> &AuthorizedVoters {
337        &self.authorized_voters
338    }
339
340    pub fn prior_voters(&mut self) -> &CircBuf<(Pubkey, Epoch, Epoch)> {
341        &self.prior_voters
342    }
343
344    pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
345        rent.minimum_balance(VoteState::size_of())
346    }
347
348    /// Upper limit on the size of the Vote State
349    /// when votes.len() is MAX_LOCKOUT_HISTORY.
350    pub const fn size_of() -> usize {
351        3731 // see test_vote_state_size_of.
352    }
353
354    // utility function, used by Stakes, tests
355    pub fn from<T: ReadableAccount>(account: &T) -> Option<VoteState> {
356        Self::deserialize(account.data()).ok()
357    }
358
359    // utility function, used by Stakes, tests
360    pub fn to<T: WritableAccount>(versioned: &VoteStateVersions, account: &mut T) -> Option<()> {
361        Self::serialize(versioned, account.data_as_mut_slice()).ok()
362    }
363
364    pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
365        deserialize::<VoteStateVersions>(input)
366            .map(|versioned| versioned.convert_to_current())
367            .map_err(|_| InstructionError::InvalidAccountData)
368    }
369
370    pub fn serialize(
371        versioned: &VoteStateVersions,
372        output: &mut [u8],
373    ) -> Result<(), InstructionError> {
374        serialize_into(output, versioned).map_err(|err| match *err {
375            ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
376            _ => InstructionError::GenericError,
377        })
378    }
379
380    pub fn credits_from<T: ReadableAccount>(account: &T) -> Option<u64> {
381        Self::from(account).map(|state| state.credits())
382    }
383
384    /// returns commission split as (voter_portion, staker_portion, was_split) tuple
385    ///
386    ///  if commission calculation is 100% one way or other,
387    ///   indicate with false for was_split
388    pub fn commission_split(&self, on: u64) -> (u64, u64, bool) {
389        match self.commission.min(100) {
390            0 => (0, on, false),
391            100 => (on, 0, false),
392            split => {
393                let on = u128::from(on);
394                // Calculate mine and theirs independently and symmetrically instead of
395                // using the remainder of the other to treat them strictly equally.
396                // This is also to cancel the rewarding if either of the parties
397                // should receive only fractional lamports, resulting in not being rewarded at all.
398                // Thus, note that we intentionally discard any residual fractional lamports.
399                let mine = on * u128::from(split) / 100u128;
400                let theirs = on * u128::from(100 - split) / 100u128;
401
402                (mine as u64, theirs as u64, true)
403            }
404        }
405    }
406
407    /// Returns if the vote state contains a slot `candidate_slot`
408    pub fn contains_slot(&self, candidate_slot: Slot) -> bool {
409        self.votes
410            .binary_search_by(|lockout| lockout.slot.cmp(&candidate_slot))
411            .is_ok()
412    }
413
414    fn check_update_vote_state_slots_are_valid(
415        &self,
416        vote_state_update: &mut VoteStateUpdate,
417        slot_hashes: &[(Slot, Hash)],
418    ) -> Result<(), VoteError> {
419        if vote_state_update.lockouts.is_empty() {
420            return Err(VoteError::EmptySlots);
421        }
422
423        // If the vote state update is not new enough, return
424        if let Some(last_vote_slot) = self.votes.back().map(|lockout| lockout.slot) {
425            if vote_state_update.lockouts.back().unwrap().slot <= last_vote_slot {
426                return Err(VoteError::VoteTooOld);
427            }
428        }
429
430        let last_vote_state_update_slot = vote_state_update
431            .lockouts
432            .back()
433            .expect("must be nonempty, checked above")
434            .slot;
435
436        if slot_hashes.is_empty() {
437            return Err(VoteError::SlotsMismatch);
438        }
439        let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
440
441        // Check if the proposed vote is too old to be in the SlotHash history
442        if last_vote_state_update_slot < earliest_slot_hash_in_history {
443            // If this is the last slot in the vote update, it must be in SlotHashes,
444            // otherwise we have no way of confirming if the hash matches
445            return Err(VoteError::VoteTooOld);
446        }
447
448        // Check if the proposed root is too old
449        if let Some(new_proposed_root) = vote_state_update.root {
450            // If the root is less than the earliest slot hash in the history such that we
451            // cannot verify whether the slot was actually was on this fork, set the root
452            // to the current vote state root for safety.
453            if earliest_slot_hash_in_history > new_proposed_root {
454                vote_state_update.root = self.root_slot;
455            }
456        }
457
458        // index into the new proposed vote state's slots, starting with the root if it exists then
459        // we use this mutable root to fold the root slot case into this loop for performance
460        let mut check_root = vote_state_update.root;
461        let mut vote_state_update_index = 0;
462
463        // index into the slot_hashes, starting at the oldest known
464        // slot hash
465        let mut slot_hashes_index = slot_hashes.len();
466
467        let mut vote_state_update_indexes_to_filter = vec![];
468
469        // Note:
470        //
471        // 1) `vote_state_update.lockouts` is sorted from oldest/smallest vote to newest/largest
472        // vote, due to the way votes are applied to the vote state (newest votes
473        // pushed to the back).
474        //
475        // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
476        // the oldest/smallest vote
477        //
478        // Unlike for vote updates, vote state updates here can't only check votes older than the last vote
479        // because have to ensure that every slot is actually part of the history, not just the most
480        // recent ones
481        while vote_state_update_index < vote_state_update.lockouts.len() && slot_hashes_index > 0 {
482            let proposed_vote_slot = if let Some(root) = check_root {
483                root
484            } else {
485                vote_state_update.lockouts[vote_state_update_index].slot
486            };
487            if check_root.is_none()
488                && vote_state_update_index > 0
489                && proposed_vote_slot
490                    <= vote_state_update.lockouts[vote_state_update_index - 1].slot
491            {
492                return Err(VoteError::SlotsNotOrdered);
493            }
494            let ancestor_slot = slot_hashes[slot_hashes_index - 1].0;
495
496            // Find if this slot in the proposed vote state exists in the SlotHashes history
497            // to confirm if it was a valid ancestor on this fork
498            match proposed_vote_slot.cmp(&ancestor_slot) {
499                Ordering::Less => {
500                    if slot_hashes_index == slot_hashes.len() {
501                        // The vote slot does not exist in the SlotHashes history because it's too old,
502                        // i.e. older than the oldest slot in the history.
503                        assert!(proposed_vote_slot < earliest_slot_hash_in_history);
504                        if !self.contains_slot(proposed_vote_slot) && check_root.is_none() {
505                            // If the vote slot is both:
506                            // 1) Too old
507                            // 2) Doesn't already exist in vote state
508                            //
509                            // Then filter it out
510                            vote_state_update_indexes_to_filter.push(vote_state_update_index);
511                        }
512                        if check_root.is_some() {
513                            // If the vote state update has a root < earliest_slot_hash_in_history
514                            // then we use the current root. The only case where this can happen
515                            // is if the current root itself is not in slot hashes.
516                            assert!(self.root_slot.unwrap() < earliest_slot_hash_in_history);
517                            check_root = None;
518                        } else {
519                            vote_state_update_index += 1;
520                        }
521                        continue;
522                    } else {
523                        // If the vote slot is new enough to be in the slot history,
524                        // but is not part of the slot history, then it must belong to another fork,
525                        // which means this vote state update is invalid.
526                        if check_root.is_some() {
527                            return Err(VoteError::RootOnDifferentFork);
528                        } else {
529                            return Err(VoteError::SlotsMismatch);
530                        }
531                    }
532                }
533                Ordering::Greater => {
534                    // Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
535                    slot_hashes_index -= 1;
536                    continue;
537                }
538                Ordering::Equal => {
539                    // Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
540                    // in `vote_state_update.lockouts` and continue. If we were checking the root,
541                    // start checking the vote state instead.
542                    if check_root.is_some() {
543                        check_root = None;
544                    } else {
545                        vote_state_update_index += 1;
546                        slot_hashes_index -= 1;
547                    }
548                }
549            }
550        }
551
552        if vote_state_update_index != vote_state_update.lockouts.len() {
553            // The last vote slot in the update did not exist in SlotHashes
554            return Err(VoteError::SlotsMismatch);
555        }
556
557        // This assertion must be true at this point because we can assume by now:
558        // 1) vote_state_update_index == vote_state_update.lockouts.len()
559        // 2) last_vote_state_update_slot >= earliest_slot_hash_in_history
560        // 3) !vote_state_update.lockouts.is_empty()
561        //
562        // 1) implies that during the last iteration of the loop above,
563        // `vote_state_update_index` was equal to `vote_state_update.lockouts.len() - 1`,
564        // and was then incremented to `vote_state_update.lockouts.len()`.
565        // This means in that last loop iteration,
566        // `proposed_vote_slot ==
567        //  vote_state_update.lockouts[vote_state_update.lockouts.len() - 1] ==
568        //  last_vote_state_update_slot`.
569        //
570        // Then we know the last comparison `match proposed_vote_slot.cmp(&ancestor_slot)`
571        // is equivalent to `match last_vote_state_update_slot.cmp(&ancestor_slot)`. The result
572        // of this match to increment `vote_state_update_index` must have been either:
573        //
574        // 1) The Equal case ran, in which case then we know this assertion must be true
575        // 2) The Less case ran, and more specifically the case
576        // `proposed_vote_slot < earliest_slot_hash_in_history` ran, which is equivalent to
577        // `last_vote_state_update_slot < earliest_slot_hash_in_history`, but this is impossible
578        // due to assumption 3) above.
579        assert_eq!(
580            last_vote_state_update_slot,
581            slot_hashes[slot_hashes_index].0
582        );
583
584        if slot_hashes[slot_hashes_index].1 != vote_state_update.hash {
585            // This means the newest vote in the slot has a match that
586            // doesn't match the expected hash for that slot on this
587            // fork
588            // TODO:
589            // warn!(
590            //     "{} dropped vote {:?} failed to match hash {} {}",
591            //     self.node_pubkey,
592            //     vote_state_update,
593            //     vote_state_update.hash,
594            //     slot_hashes[slot_hashes_index].1
595            // );
596            // inc_new_counter_info!("dropped-vote-hash", 1);
597            return Err(VoteError::SlotHashMismatch);
598        }
599
600        // Filter out the irrelevant votes
601        let mut vote_state_update_index = 0;
602        let mut filter_votes_index = 0;
603        vote_state_update.lockouts.retain(|_lockout| {
604            let should_retain = if filter_votes_index == vote_state_update_indexes_to_filter.len() {
605                true
606            } else if vote_state_update_index
607                == vote_state_update_indexes_to_filter[filter_votes_index]
608            {
609                filter_votes_index += 1;
610                false
611            } else {
612                true
613            };
614
615            vote_state_update_index += 1;
616            should_retain
617        });
618
619        Ok(())
620    }
621
622    fn check_slots_are_valid(
623        &self,
624        vote_slots: &[Slot],
625        vote_hash: &Hash,
626        slot_hashes: &[(Slot, Hash)],
627    ) -> Result<(), VoteError> {
628        // index into the vote's slots, starting at the oldest
629        // slot
630        let mut i = 0;
631
632        // index into the slot_hashes, starting at the oldest known
633        // slot hash
634        let mut j = slot_hashes.len();
635
636        // Note:
637        //
638        // 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
639        // vote, due to the way votes are applied to the vote state (newest votes
640        // pushed to the back).
641        //
642        // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
643        // the oldest/smallest vote
644        while i < vote_slots.len() && j > 0 {
645            // 1) increment `i` to find the smallest slot `s` in `vote_slots`
646            // where `s` >= `last_voted_slot`
647            if self
648                .last_voted_slot()
649                .map_or(false, |last_voted_slot| vote_slots[i] <= last_voted_slot)
650            {
651                i += 1;
652                continue;
653            }
654
655            // 2) Find the hash for this slot `s`.
656            if vote_slots[i] != slot_hashes[j - 1].0 {
657                // Decrement `j` to find newer slots
658                j -= 1;
659                continue;
660            }
661
662            // 3) Once the hash for `s` is found, bump `s` to the next slot
663            // in `vote_slots` and continue.
664            i += 1;
665            j -= 1;
666        }
667
668        if j == slot_hashes.len() {
669            // This means we never made it to steps 2) or 3) above, otherwise
670            // `j` would have been decremented at least once. This means
671            // there are not slots in `vote_slots` greater than `last_voted_slot`
672            // TODO:
673            // debug!(
674            //     "{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
675            //     self.node_pubkey, vote_slots, vote_hash, slot_hashes
676            // );
677            return Err(VoteError::VoteTooOld);
678        }
679        if i != vote_slots.len() {
680            // This means there existed some slot for which we couldn't find
681            // a matching slot hash in step 2)
682            // TODO:
683            // info!(
684            //     "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
685            //     self.node_pubkey, vote_slots, slot_hashes,
686            // );
687            // inc_new_counter_info!("dropped-vote-slot", 1);
688            return Err(VoteError::SlotsMismatch);
689        }
690        if &slot_hashes[j].1 != vote_hash {
691            // This means the newest slot in the `vote_slots` has a match that
692            // doesn't match the expected hash for that slot on this
693            // fork
694            // TODO:
695            // warn!(
696            //     "{} dropped vote slots {:?} failed to match hash {} {}",
697            //     self.node_pubkey, vote_slots, vote_hash, slot_hashes[j].1
698            // );
699            // inc_new_counter_info!("dropped-vote-hash", 1);
700            return Err(VoteError::SlotHashMismatch);
701        }
702        Ok(())
703    }
704
705    //`Ensure check_update_vote_state_slots_are_valid()` runs on the slots in `new_state`
706    // before `process_new_vote_state()` is called
707
708    // This function should guarantee the following about `new_state`:
709    //
710    // 1) It's well ordered, i.e. the slots are sorted from smallest to largest,
711    // and the confirmations sorted from largest to smallest.
712    // 2) Confirmations `c` on any vote slot satisfy `0 < c <= MAX_LOCKOUT_HISTORY`
713    // 3) Lockouts are not expired by consecutive votes, i.e. for every consecutive
714    // `v_i`, `v_{i + 1}` satisfy `v_i.last_locked_out_slot() >= v_{i + 1}`.
715
716    // We also guarantee that compared to the current vote state, `new_state`
717    // introduces no rollback. This means:
718    //
719    // 1) The last slot in `new_state` is always greater than any slot in the
720    // current vote state.
721    //
722    // 2) From 1), this means that for every vote `s` in the current state:
723    //    a) If there exists an `s'` in `new_state` where `s.slot == s'.slot`, then
724    //    we must guarantee `s.confirmations <= s'.confirmations`
725    //
726    //    b) If there does not exist any such `s'` in `new_state`, then there exists
727    //    some `t` that is the smallest vote in `new_state` where `t.slot > s.slot`.
728    //    `t` must have expired/popped off s', so it must be guaranteed that
729    //    `s.last_locked_out_slot() < t`.
730
731    // Note these two above checks do not guarantee that the vote state being submitted
732    // is a vote state that could have been created by iteratively building a tower
733    // by processing one vote at a time. For instance, the tower:
734    //
735    // { slot 0, confirmations: 31 }
736    // { slot 1, confirmations: 30 }
737    //
738    // is a legal tower that could be submitted on top of a previously empty tower. However,
739    // there is no way to create this tower from the iterative process, because slot 1 would
740    // have to have at least one other slot on top of it, even if the first 30 votes were all
741    // popped off.
742    pub fn process_new_vote_state(
743        &mut self,
744        new_state: VecDeque<Lockout>,
745        new_root: Option<Slot>,
746        timestamp: Option<i64>,
747        epoch: Epoch,
748        feature_set: Option<&FeatureSet>,
749    ) -> Result<(), VoteError> {
750        assert!(!new_state.is_empty());
751        if new_state.len() > MAX_LOCKOUT_HISTORY {
752            return Err(VoteError::TooManyVotes);
753        }
754
755        match (new_root, self.root_slot) {
756            (Some(new_root), Some(current_root)) => {
757                if new_root < current_root {
758                    return Err(VoteError::RootRollBack);
759                }
760            }
761            (None, Some(_)) => {
762                return Err(VoteError::RootRollBack);
763            }
764            _ => (),
765        }
766
767        let mut previous_vote: Option<&Lockout> = None;
768
769        // Check that all the votes in the new proposed state are:
770        // 1) Strictly sorted from oldest to newest vote
771        // 2) The confirmations are strictly decreasing
772        // 3) Not zero confirmation votes
773        for vote in &new_state {
774            if vote.confirmation_count == 0 {
775                return Err(VoteError::ZeroConfirmations);
776            } else if vote.confirmation_count > MAX_LOCKOUT_HISTORY as u32 {
777                return Err(VoteError::ConfirmationTooLarge);
778            } else if let Some(new_root) = new_root {
779                if vote.slot <= new_root
780                &&
781                // This check is necessary because
782                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L120
783                // always sets a root for even empty towers, which is then hard unwrapped here
784                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L776
785                new_root != Slot::default()
786                {
787                    return Err(VoteError::SlotSmallerThanRoot);
788                }
789            }
790
791            if let Some(previous_vote) = previous_vote {
792                if previous_vote.slot >= vote.slot {
793                    return Err(VoteError::SlotsNotOrdered);
794                } else if previous_vote.confirmation_count <= vote.confirmation_count {
795                    return Err(VoteError::ConfirmationsNotOrdered);
796                } else if vote.slot > previous_vote.last_locked_out_slot() {
797                    return Err(VoteError::NewVoteStateLockoutMismatch);
798                }
799            }
800            previous_vote = Some(vote);
801        }
802
803        // Find the first vote in the current vote state for a slot greater
804        // than the new proposed root
805        let mut current_vote_state_index = 0;
806        let mut new_vote_state_index = 0;
807
808        // Count the number of slots at and before the new root within the current vote state lockouts.  Start with 1
809        // for the new root.  The purpose of this is to know how many slots were rooted by this state update:
810        // - The new root was rooted
811        // - As were any slots that were in the current state but are not in the new state.  The only slots which
812        //   can be in this set are those oldest slots in the current vote state that are not present in the
813        //   new vote state; these have been "popped off the back" of the tower and thus represent finalized slots
814        let mut finalized_slot_count = 1_u64;
815
816        for current_vote in &self.votes {
817            // Find the first vote in the current vote state for a slot greater
818            // than the new proposed root
819            if let Some(new_root) = new_root {
820                if current_vote.slot <= new_root {
821                    current_vote_state_index += 1;
822                    if current_vote.slot != new_root {
823                        finalized_slot_count += 1;
824                    }
825                    continue;
826                }
827            }
828
829            break;
830        }
831
832        // All the votes in our current vote state that are missing from the new vote state
833        // must have been expired by later votes. Check that the lockouts match this assumption.
834        while current_vote_state_index < self.votes.len() && new_vote_state_index < new_state.len()
835        {
836            let current_vote = &self.votes[current_vote_state_index];
837            let new_vote = &new_state[new_vote_state_index];
838
839            // If the current slot is less than the new proposed slot, then the
840            // new slot must have popped off the old slot, so check that the
841            // lockouts are corrects.
842            match current_vote.slot.cmp(&new_vote.slot) {
843                Ordering::Less => {
844                    if current_vote.last_locked_out_slot() >= new_vote.slot {
845                        return Err(VoteError::LockoutConflict);
846                    }
847                    current_vote_state_index += 1;
848                }
849                Ordering::Equal => {
850                    // The new vote state should never have less lockout than
851                    // the previous vote state for the same slot
852                    if new_vote.confirmation_count < current_vote.confirmation_count {
853                        return Err(VoteError::ConfirmationRollBack);
854                    }
855
856                    current_vote_state_index += 1;
857                    new_vote_state_index += 1;
858                }
859                Ordering::Greater => {
860                    new_vote_state_index += 1;
861                }
862            }
863        }
864
865        // `new_vote_state` passed all the checks, finalize the change by rewriting
866        // our state.
867        if self.root_slot != new_root {
868            // Award vote credits based on the number of slots that were voted on and have reached finality
869            if feature_set
870                .map(|feature_set| {
871                    feature_set.is_active(&feature_set::vote_state_update_credit_per_dequeue::id())
872                })
873                .unwrap_or(false)
874            {
875                // For each finalized slot, there was one voted-on slot in the new vote state that was responsible for
876                // finalizing it. Each of those votes is awarded 1 credit.
877                self.increment_credits(epoch, finalized_slot_count);
878            } else {
879                self.increment_credits(epoch, 1);
880            }
881        }
882        if let Some(timestamp) = timestamp {
883            let last_slot = new_state.back().unwrap().slot;
884            self.process_timestamp(last_slot, timestamp)?;
885        }
886        self.root_slot = new_root;
887        self.votes = new_state;
888        Ok(())
889    }
890
891    pub fn process_vote(
892        &mut self,
893        vote: &Vote,
894        slot_hashes: &[SlotHash],
895        epoch: Epoch,
896        feature_set: Option<&FeatureSet>,
897    ) -> Result<(), VoteError> {
898        if vote.slots.is_empty() {
899            return Err(VoteError::EmptySlots);
900        }
901        let filtered_vote_slots = feature_set.and_then(|feature_set| {
902            if feature_set.is_active(&filter_votes_outside_slot_hashes::id()) {
903                let earliest_slot_in_history =
904                    slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
905                Some(
906                    vote.slots
907                        .iter()
908                        .filter(|slot| **slot >= earliest_slot_in_history)
909                        .cloned()
910                        .collect::<Vec<Slot>>(),
911                )
912            } else {
913                None
914            }
915        });
916
917        let vote_slots = filtered_vote_slots.as_ref().unwrap_or(&vote.slots);
918        if vote_slots.is_empty() {
919            return Err(VoteError::VotesTooOldAllFiltered);
920        }
921
922        self.check_slots_are_valid(vote_slots, &vote.hash, slot_hashes)?;
923
924        vote_slots
925            .iter()
926            .for_each(|s| self.process_next_vote_slot(*s, epoch));
927        Ok(())
928    }
929
930    pub fn process_next_vote_slot(&mut self, next_vote_slot: Slot, epoch: Epoch) {
931        // Ignore votes for slots earlier than we already have votes for
932        if self
933            .last_voted_slot()
934            .map_or(false, |last_voted_slot| next_vote_slot <= last_voted_slot)
935        {
936            return;
937        }
938
939        let vote = Lockout::new(next_vote_slot);
940
941        self.pop_expired_votes(next_vote_slot);
942
943        // Once the stack is full, pop the oldest lockout and distribute rewards
944        if self.votes.len() == MAX_LOCKOUT_HISTORY {
945            let vote = self.votes.pop_front().unwrap();
946            self.root_slot = Some(vote.slot);
947
948            self.increment_credits(epoch, 1);
949        }
950        self.votes.push_back(vote);
951        self.double_lockouts();
952    }
953
954    /// increment credits, record credits for last epoch if new epoch
955    pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
956        // increment credits, record by epoch
957
958        // never seen a credit
959        if self.epoch_credits.is_empty() {
960            self.epoch_credits.push((epoch, 0, 0));
961        } else if epoch != self.epoch_credits.last().unwrap().0 {
962            let (_, credits, prev_credits) = *self.epoch_credits.last().unwrap();
963
964            if credits != prev_credits {
965                // if credits were earned previous epoch
966                // append entry at end of list for the new epoch
967                self.epoch_credits.push((epoch, credits, credits));
968            } else {
969                // else just move the current epoch
970                self.epoch_credits.last_mut().unwrap().0 = epoch;
971            }
972
973            // Remove too old epoch_credits
974            if self.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
975                self.epoch_credits.remove(0);
976            }
977        }
978
979        self.epoch_credits.last_mut().unwrap().1 += credits;
980    }
981
982    /// "unchecked" functions used by tests and Tower
983    pub fn process_vote_unchecked(&mut self, vote: Vote) {
984        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
985        let _ignored = self.process_vote(&vote, &slot_hashes, self.current_epoch(), None);
986    }
987
988    #[cfg(test)]
989    pub fn process_slot_votes_unchecked(&mut self, slots: &[Slot]) {
990        for slot in slots {
991            self.process_slot_vote_unchecked(*slot);
992        }
993    }
994
995    pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
996        self.process_vote_unchecked(Vote::new(vec![slot], Hash::default()));
997    }
998
999    pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
1000        if position < self.votes.len() {
1001            let pos = self.votes.len() - 1 - position;
1002            self.votes.get(pos)
1003        } else {
1004            None
1005        }
1006    }
1007
1008    pub fn last_lockout(&self) -> Option<&Lockout> {
1009        self.votes.back()
1010    }
1011
1012    pub fn last_voted_slot(&self) -> Option<Slot> {
1013        self.last_lockout().map(|v| v.slot)
1014    }
1015
1016    // Upto MAX_LOCKOUT_HISTORY many recent unexpired
1017    // vote slots pushed onto the stack.
1018    pub fn tower(&self) -> Vec<Slot> {
1019        self.votes.iter().map(|v| v.slot).collect()
1020    }
1021
1022    pub fn current_epoch(&self) -> Epoch {
1023        if self.epoch_credits.is_empty() {
1024            0
1025        } else {
1026            self.epoch_credits.last().unwrap().0
1027        }
1028    }
1029
1030    /// Number of "credits" owed to this account from the mining pool. Submit this
1031    /// VoteState to the Rewards program to trade credits for lamports.
1032    pub fn credits(&self) -> u64 {
1033        if self.epoch_credits.is_empty() {
1034            0
1035        } else {
1036            self.epoch_credits.last().unwrap().1
1037        }
1038    }
1039
1040    /// Number of "credits" owed to this account from the mining pool on a per-epoch basis,
1041    ///  starting from credits observed.
1042    /// Each tuple of (Epoch, u64, u64) is read as (epoch, credits, prev_credits), where
1043    ///   credits for each epoch is credits - prev_credits; while redundant this makes
1044    ///   calculating rewards over partial epochs nice and simple
1045    pub fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
1046        &self.epoch_credits
1047    }
1048
1049    fn set_new_authorized_voter<F>(
1050        &mut self,
1051        authorized_pubkey: &Pubkey,
1052        current_epoch: Epoch,
1053        target_epoch: Epoch,
1054        verify: F,
1055    ) -> Result<(), InstructionError>
1056    where
1057        F: Fn(Pubkey) -> Result<(), InstructionError>,
1058    {
1059        let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
1060        verify(epoch_authorized_voter)?;
1061
1062        // The offset in slots `n` on which the target_epoch
1063        // (default value `DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET`) is
1064        // calculated is the number of slots available from the
1065        // first slot `S` of an epoch in which to set a new voter for
1066        // the epoch at `S` + `n`
1067        if self.authorized_voters.contains(target_epoch) {
1068            return Err(VoteError::TooSoonToReauthorize.into());
1069        }
1070
1071        // Get the latest authorized_voter
1072        let (latest_epoch, latest_authorized_pubkey) = self
1073            .authorized_voters
1074            .last()
1075            .ok_or(InstructionError::InvalidAccountData)?;
1076
1077        // If we're not setting the same pubkey as authorized pubkey again,
1078        // then update the list of prior voters to mark the expiration
1079        // of the old authorized pubkey
1080        if latest_authorized_pubkey != authorized_pubkey {
1081            // Update the epoch ranges of authorized pubkeys that will be expired
1082            let epoch_of_last_authorized_switch =
1083                self.prior_voters.last().map(|range| range.2).unwrap_or(0);
1084
1085            // target_epoch must:
1086            // 1) Be monotonically increasing due to the clock always
1087            //    moving forward
1088            // 2) not be equal to latest epoch otherwise this
1089            //    function would have returned TooSoonToReauthorize error
1090            //    above
1091            assert!(target_epoch > *latest_epoch);
1092
1093            // Commit the new state
1094            self.prior_voters.append((
1095                *latest_authorized_pubkey,
1096                epoch_of_last_authorized_switch,
1097                target_epoch,
1098            ));
1099        }
1100
1101        self.authorized_voters
1102            .insert(target_epoch, *authorized_pubkey);
1103
1104        Ok(())
1105    }
1106
1107    fn get_and_update_authorized_voter(
1108        &mut self,
1109        current_epoch: Epoch,
1110    ) -> Result<Pubkey, InstructionError> {
1111        let pubkey = self
1112            .authorized_voters
1113            .get_and_cache_authorized_voter_for_epoch(current_epoch)
1114            .ok_or(InstructionError::InvalidAccountData)?;
1115        self.authorized_voters
1116            .purge_authorized_voters(current_epoch);
1117        Ok(pubkey)
1118    }
1119
1120    // Pop all recent votes that are not locked out at the next vote slot.  This
1121    // allows validators to switch forks once their votes for another fork have
1122    // expired. This also allows validators continue voting on recent blocks in
1123    // the same fork without increasing lockouts.
1124    fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
1125        while let Some(vote) = self.last_lockout() {
1126            if !vote.is_locked_out_at_slot(next_vote_slot) {
1127                self.votes.pop_back();
1128            } else {
1129                break;
1130            }
1131        }
1132    }
1133
1134    fn double_lockouts(&mut self) {
1135        let stack_depth = self.votes.len();
1136        for (i, v) in self.votes.iter_mut().enumerate() {
1137            // Don't increase the lockout for this vote until we get more confirmations
1138            // than the max number of confirmations this vote has seen
1139            if stack_depth > i + v.confirmation_count as usize {
1140                v.confirmation_count += 1;
1141            }
1142        }
1143    }
1144
1145    pub fn process_timestamp(
1146        &mut self,
1147        slot: Slot,
1148        timestamp: UnixTimestamp,
1149    ) -> Result<(), VoteError> {
1150        if (slot < self.last_timestamp.slot || timestamp < self.last_timestamp.timestamp)
1151            || (slot == self.last_timestamp.slot
1152                && BlockTimestamp { slot, timestamp } != self.last_timestamp
1153                && self.last_timestamp.slot != 0)
1154        {
1155            return Err(VoteError::TimestampTooOld);
1156        }
1157        self.last_timestamp = BlockTimestamp { slot, timestamp };
1158        Ok(())
1159    }
1160
1161    pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
1162        const VERSION_OFFSET: usize = 4;
1163        data.len() == VoteState::size_of()
1164            && data[VERSION_OFFSET..VERSION_OFFSET + DEFAULT_PRIOR_VOTERS_OFFSET]
1165                != [0; DEFAULT_PRIOR_VOTERS_OFFSET]
1166    }
1167}
1168
1169/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
1170/// but will implicitly withdraw authorization from the previously authorized
1171/// key
1172pub fn authorize<S: std::hash::BuildHasher>(
1173    vote_account: &mut BorrowedAccount,
1174    authorized: &Pubkey,
1175    vote_authorize: VoteAuthorize,
1176    signers: &HashSet<Pubkey, S>,
1177    clock: &Clock,
1178    feature_set: &FeatureSet,
1179) -> Result<(), InstructionError> {
1180    let mut vote_state: VoteState = vote_account
1181        .get_state::<VoteStateVersions>()?
1182        .convert_to_current();
1183
1184    match vote_authorize {
1185        VoteAuthorize::Voter => {
1186            let authorized_withdrawer_signer = if feature_set
1187                .is_active(&feature_set::vote_withdraw_authority_may_change_authorized_voter::id())
1188            {
1189                verify_authorized_signer(&vote_state.authorized_withdrawer, signers).is_ok()
1190            } else {
1191                false
1192            };
1193
1194            vote_state.set_new_authorized_voter(
1195                authorized,
1196                clock.epoch,
1197                clock.leader_schedule_epoch + 1,
1198                |epoch_authorized_voter| {
1199                    // current authorized withdrawer or authorized voter must say "yay"
1200                    if authorized_withdrawer_signer {
1201                        Ok(())
1202                    } else {
1203                        verify_authorized_signer(&epoch_authorized_voter, signers)
1204                    }
1205                },
1206            )?;
1207        }
1208        VoteAuthorize::Withdrawer => {
1209            // current authorized withdrawer must say "yay"
1210            verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
1211            vote_state.authorized_withdrawer = *authorized;
1212        }
1213    }
1214
1215    vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
1216}
1217
1218/// Update the node_pubkey, requires signature of the authorized voter
1219pub fn update_validator_identity<S: std::hash::BuildHasher>(
1220    vote_account: &mut BorrowedAccount,
1221    node_pubkey: &Pubkey,
1222    signers: &HashSet<Pubkey, S>,
1223    feature_set: &FeatureSet,
1224) -> Result<(), InstructionError> {
1225    let mut vote_state: VoteState = vote_account
1226        .get_state::<VoteStateVersions>()?
1227        .convert_to_current();
1228
1229    // current authorized withdrawer must say "yay"
1230    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
1231
1232    // new node must say "yay"
1233    verify_authorized_signer(node_pubkey, signers)?;
1234
1235    vote_state.node_pubkey = *node_pubkey;
1236
1237    vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
1238}
1239
1240/// Update the vote account's commission
1241pub fn update_commission<S: std::hash::BuildHasher>(
1242    vote_account: &mut BorrowedAccount,
1243    commission: u8,
1244    signers: &HashSet<Pubkey, S>,
1245    feature_set: &FeatureSet,
1246) -> Result<(), InstructionError> {
1247    let mut vote_state: VoteState = vote_account
1248        .get_state::<VoteStateVersions>()?
1249        .convert_to_current();
1250
1251    // current authorized withdrawer must say "yay"
1252    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
1253
1254    vote_state.commission = commission;
1255
1256    vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
1257}
1258
1259fn verify_authorized_signer<S: std::hash::BuildHasher>(
1260    authorized: &Pubkey,
1261    signers: &HashSet<Pubkey, S>,
1262) -> Result<(), InstructionError> {
1263    if signers.contains(authorized) {
1264        Ok(())
1265    } else {
1266        Err(InstructionError::MissingRequiredSignature)
1267    }
1268}
1269
1270/// Withdraw funds from the vote account
1271#[allow(clippy::too_many_arguments)]
1272pub fn withdraw<S: std::hash::BuildHasher>(
1273    transaction_context: &TransactionContext,
1274    instruction_context: &InstructionContext,
1275    vote_account_index: u16,
1276    lamports: u64,
1277    to_account_index: u16,
1278    signers: &HashSet<Pubkey, S>,
1279    rent_sysvar: Option<&Rent>,
1280    clock: Option<&Clock>,
1281    feature_set: &FeatureSet,
1282) -> Result<(), InstructionError> {
1283    // NOTE: solana-sdk 1.11 `try_borrow_account` is private(this lib was written in 1.10.29)
1284    // We use `try_borrow_instruction_account` to fix it.
1285    let mut vote_account = instruction_context
1286        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
1287    let vote_state: VoteState = vote_account
1288        .get_state::<VoteStateVersions>()?
1289        .convert_to_current();
1290
1291    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
1292
1293    let remaining_balance = vote_account
1294        .get_lamports()
1295        .checked_sub(lamports)
1296        .ok_or(InstructionError::InsufficientFunds)?;
1297
1298    if remaining_balance == 0 {
1299        let reject_active_vote_account_close = clock
1300            .zip(vote_state.epoch_credits.last())
1301            .map(|(clock, (last_epoch_with_credits, _, _))| {
1302                let current_epoch = clock.epoch;
1303                // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits
1304                // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least
1305                // one full epoch since the validator has received credits.
1306                current_epoch.saturating_sub(*last_epoch_with_credits) < 2
1307            })
1308            .unwrap_or(false);
1309
1310        if reject_active_vote_account_close {
1311            // TODO:
1312            // datapoint_debug!("vote-account-close", ("reject-active", 1, i64));
1313            return Err(InstructionError::InvalidAccountData);
1314        } else {
1315            // Deinitialize upon zero-balance
1316            // TODO:
1317            // datapoint_debug!("vote-account-close", ("allow", 1, i64));
1318            vote_account.set_state(
1319                &VoteStateVersions::new_current(VoteState::default()),
1320                feature_set,
1321            )?;
1322        }
1323    } else if let Some(rent_sysvar) = rent_sysvar {
1324        let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
1325        if remaining_balance < min_rent_exempt_balance {
1326            return Err(InstructionError::InsufficientFunds);
1327        }
1328    }
1329
1330    vote_account.checked_sub_lamports(lamports, feature_set)?;
1331    drop(vote_account);
1332    // NOTE: solana-sdk 1.11 `try_borrow_account` is private(this lib was written in 1.10.29)
1333    // We use `try_borrow_instruction_account` to fix it.
1334    let mut to_account = instruction_context
1335        .try_borrow_instruction_account(transaction_context, to_account_index)?;
1336    to_account.checked_add_lamports(lamports, feature_set)?;
1337    Ok(())
1338}
1339
1340/// Initialize the vote_state for a vote account
1341/// Assumes that the account is being init as part of a account creation or balance transfer and
1342/// that the transaction must be signed by the staker's keys
1343pub fn initialize_account<S: std::hash::BuildHasher>(
1344    vote_account: &mut BorrowedAccount,
1345    vote_init: &VoteInit,
1346    signers: &HashSet<Pubkey, S>,
1347    clock: &Clock,
1348    feature_set: &FeatureSet,
1349) -> Result<(), InstructionError> {
1350    if vote_account.get_data().len() != VoteState::size_of() {
1351        return Err(InstructionError::InvalidAccountData);
1352    }
1353    let versioned = vote_account.get_state::<VoteStateVersions>()?;
1354
1355    if !versioned.is_uninitialized() {
1356        return Err(InstructionError::AccountAlreadyInitialized);
1357    }
1358
1359    // node must agree to accept this vote account
1360    verify_authorized_signer(&vote_init.node_pubkey, signers)?;
1361
1362    vote_account.set_state(
1363        &VoteStateVersions::new_current(VoteState::new(vote_init, clock)),
1364        feature_set,
1365    )
1366}
1367
1368fn verify_and_get_vote_state<S: std::hash::BuildHasher>(
1369    vote_account: &BorrowedAccount,
1370    clock: &Clock,
1371    signers: &HashSet<Pubkey, S>,
1372) -> Result<VoteState, InstructionError> {
1373    let versioned = vote_account.get_state::<VoteStateVersions>()?;
1374
1375    if versioned.is_uninitialized() {
1376        return Err(InstructionError::UninitializedAccount);
1377    }
1378
1379    let mut vote_state = versioned.convert_to_current();
1380    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1381    verify_authorized_signer(&authorized_voter, signers)?;
1382
1383    Ok(vote_state)
1384}
1385
1386pub fn process_vote<S: std::hash::BuildHasher>(
1387    vote_account: &mut BorrowedAccount,
1388    slot_hashes: &[SlotHash],
1389    clock: &Clock,
1390    vote: &Vote,
1391    signers: &HashSet<Pubkey, S>,
1392    feature_set: &FeatureSet,
1393) -> Result<(), InstructionError> {
1394    let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
1395
1396    vote_state.process_vote(vote, slot_hashes, clock.epoch, Some(feature_set))?;
1397    if let Some(timestamp) = vote.timestamp {
1398        vote.slots
1399            .iter()
1400            .max()
1401            .ok_or(VoteError::EmptySlots)
1402            .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
1403    }
1404    vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
1405}
1406
1407pub fn process_vote_state_update<S: std::hash::BuildHasher>(
1408    vote_account: &mut BorrowedAccount,
1409    slot_hashes: &[SlotHash],
1410    clock: &Clock,
1411    mut vote_state_update: VoteStateUpdate,
1412    signers: &HashSet<Pubkey, S>,
1413    feature_set: &FeatureSet,
1414) -> Result<(), InstructionError> {
1415    let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
1416    vote_state.check_update_vote_state_slots_are_valid(&mut vote_state_update, slot_hashes)?;
1417    vote_state.process_new_vote_state(
1418        vote_state_update.lockouts,
1419        vote_state_update.root,
1420        vote_state_update.timestamp,
1421        clock.epoch,
1422        Some(feature_set),
1423    )?;
1424    vote_account.set_state(&VoteStateVersions::new_current(vote_state), feature_set)
1425}
1426
1427pub fn create_account_with_authorized(
1428    node_pubkey: &Pubkey,
1429    authorized_voter: &Pubkey,
1430    authorized_withdrawer: &Pubkey,
1431    commission: u8,
1432    lamports: u64,
1433) -> AccountSharedData {
1434    let mut vote_account = AccountSharedData::new(lamports, VoteState::size_of(), &id());
1435
1436    let vote_state = VoteState::new(
1437        &VoteInit {
1438            node_pubkey: *node_pubkey,
1439            authorized_voter: *authorized_voter,
1440            authorized_withdrawer: *authorized_withdrawer,
1441            commission,
1442        },
1443        &Clock::default(),
1444    );
1445
1446    let versioned = VoteStateVersions::new_current(vote_state);
1447    VoteState::to(&versioned, &mut vote_account).unwrap();
1448
1449    vote_account
1450}
1451
1452// create_account() should be removed, use create_account_with_authorized() instead
1453pub fn create_account(
1454    vote_pubkey: &Pubkey,
1455    node_pubkey: &Pubkey,
1456    commission: u8,
1457    lamports: u64,
1458) -> AccountSharedData {
1459    create_account_with_authorized(node_pubkey, vote_pubkey, vote_pubkey, commission, lamports)
1460}