1#[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
24pub const MAX_LOCKOUT_HISTORY: usize = 31;
26pub const INITIAL_LOCKOUT: usize = 2;
27
28pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
30
31const 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 pub slots: Vec<Slot>,
39 pub hash: Hash,
41 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 pub fn lockout(&self) -> u64 {
75 (INITIAL_LOCKOUT as u64).pow(self.confirmation_count)
76 }
77
78 #[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 pub lockouts: VecDeque<Lockout>,
96 pub root: Option<Slot>,
98 pub hash: Hash,
100 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
176const MAX_ITEMS: usize = 32;
178
179#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
180pub struct CircBuf<I> {
181 buf: [I; MAX_ITEMS],
182 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 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 pub node_pubkey: Pubkey,
227
228 pub authorized_withdrawer: Pubkey,
230 pub commission: u8,
233
234 pub votes: VecDeque<Lockout>,
235
236 pub root_slot: Option<Slot>,
239
240 authorized_voters: AuthorizedVoters,
242
243 prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
247
248 pub epoch_credits: Vec<(Epoch, u64, u64)>,
251
252 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 pub const fn size_of() -> usize {
286 3731 }
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 #[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 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 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 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 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 #[allow(clippy::integer_arithmetic)]
378 pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
379 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 self.epoch_credits.push((epoch, credits, credits));
391 } else {
392 self.epoch_credits.last_mut().unwrap().0 = epoch;
394 }
395
396 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 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 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 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 if self.authorized_voters.contains(target_epoch) {
475 return Err(VoteError::TooSoonToReauthorize.into());
476 }
477
478 let (latest_epoch, latest_authorized_pubkey) = self
480 .authorized_voters
481 .last()
482 .ok_or(InstructionError::InvalidAccountData)?;
483
484 if latest_authorized_pubkey != authorized_pubkey {
488 let epoch_of_last_authorized_switch =
490 self.prior_voters.last().map(|range| range.2).unwrap_or(0);
491
492 assert!(target_epoch > *latest_epoch);
499
500 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 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 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 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 assert_eq!(
849 vote_state.get_and_update_authorized_voter(1).unwrap(),
850 original_voter
851 );
852
853 assert_eq!(
856 vote_state.get_and_update_authorized_voter(5).unwrap(),
857 original_voter
858 );
859
860 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 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 assert_eq!(
878 vote_state.get_and_update_authorized_voter(6).unwrap(),
879 original_voter
880 );
881
882 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 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 assert_eq!(
923 vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(())),
924 Err(VoteError::TooSoonToReauthorize.into())
925 );
926
927 vote_state
929 .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, |_| Ok(()))
930 .unwrap();
931
932 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 vote_state
955 .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, |_| Ok(()))
956 .unwrap();
957
958 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 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 assert_eq!(
1018 vote_state.set_new_authorized_voter(&new_voter, 1, 2, |_| Ok(())),
1019 Ok(())
1020 );
1021
1022 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 assert!(VoteStateVersions::new_current(VoteState::default()).is_uninitialized());
1072 }
1073
1074 #[test]
1075 fn test_is_correct_size_and_initialized() {
1076 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 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 let short_data = vec![1; DEFAULT_PRIOR_VOTERS_OFFSET];
1091 assert!(!VoteState::is_correct_size_and_initialized(&short_data));
1092
1093 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 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 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}