1pub mod handler;
5
6pub use solana_vote_interface::state::{vote_state_versions::*, *};
7use {
8 crate::vote_state::handler::VoteStateVersionsInline,
9 handler::{VoteStateHandler, VoteStateTargetVersion},
10 log::*,
11 solana_account::{AccountSharedData, WritableAccount},
12 solana_bls_signatures::{VerifiableProofOfPossession, keypair::Keypair as BLSKeypair},
13 solana_clock::{Clock, Epoch, Slot},
14 solana_epoch_schedule::EpochSchedule,
15 solana_hash::Hash,
16 solana_instruction::error::InstructionError,
17 solana_program_runtime::invoke_context::InvokeContext,
18 solana_pubkey::Pubkey,
19 solana_rent::Rent,
20 solana_sdk_ids::system_program,
21 solana_slot_hashes::SlotHash,
22 solana_system_interface::instruction as system_instruction,
23 solana_transaction_context::{
24 IndexOfAccount, instruction::InstructionContext,
25 instruction_accounts::BorrowedInstructionAccount,
26 },
27 solana_vote_interface::{error::VoteError, instruction::CommissionKind, program::id},
28 std::{
29 cmp::Ordering,
30 collections::{HashSet, VecDeque},
31 },
32};
33
34fn get_vote_state_handler_checked(
35 vote_account: &BorrowedInstructionAccount,
36 target_version: VoteStateTargetVersion,
37) -> Result<VoteStateHandler, InstructionError> {
38 match target_version {
39 VoteStateTargetVersion::V4 => {
40 let versioned = VoteStateVersionsInline::deserialize(vote_account.get_data())?;
45 if versioned.is_uninitialized() {
46 return Err(InstructionError::UninitializedAccount);
47 }
48 let vote_state =
49 handler::try_convert_to_vote_state_v4(versioned, vote_account.get_key())?;
50 Ok(VoteStateHandler::new_v4(vote_state))
51 }
52 }
53}
54
55fn check_and_filter_proposed_vote_state(
59 vote_state: &VoteStateHandler,
60 proposed_lockouts: &mut VecDeque<Lockout>,
61 proposed_root: &mut Option<Slot>,
62 proposed_hash: Hash,
63 slot_hashes: &[(Slot, Hash)],
64) -> Result<(), VoteError> {
65 if proposed_lockouts.is_empty() {
66 return Err(VoteError::EmptySlots);
67 }
68
69 let last_proposed_slot = proposed_lockouts
70 .back()
71 .expect("must be nonempty, checked above")
72 .slot();
73
74 if let Some(last_vote_slot) = vote_state.votes().back().map(|lockout| lockout.slot())
76 && last_proposed_slot <= last_vote_slot
77 {
78 return Err(VoteError::VoteTooOld);
79 }
80
81 if slot_hashes.is_empty() {
82 return Err(VoteError::SlotsMismatch);
83 }
84 let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;
85
86 if last_proposed_slot < earliest_slot_hash_in_history {
88 return Err(VoteError::VoteTooOld);
91 }
92
93 if let Some(root) = *proposed_root {
95 if root < earliest_slot_hash_in_history {
100 *proposed_root = vote_state.root_slot();
102
103 for vote in vote_state.votes().iter().rev() {
105 if vote.slot() <= root {
106 *proposed_root = Some(vote.slot());
107 break;
108 }
109 }
110 }
111 }
112
113 let mut root_to_check = *proposed_root;
117 let mut proposed_lockouts_index = 0;
118
119 let mut slot_hashes_index = slot_hashes.len();
122
123 let mut proposed_lockouts_indices_to_filter = vec![];
124
125 while proposed_lockouts_index < proposed_lockouts.len() && slot_hashes_index > 0 {
137 let proposed_vote_slot = if let Some(root) = root_to_check {
138 root
139 } else {
140 proposed_lockouts[proposed_lockouts_index].slot()
141 };
142 if root_to_check.is_none()
143 && proposed_lockouts_index > 0
144 && proposed_vote_slot
145 <= proposed_lockouts[proposed_lockouts_index.checked_sub(1).expect(
146 "`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`",
147 )]
148 .slot()
149 {
150 return Err(VoteError::SlotsNotOrdered);
151 }
152 let ancestor_slot = slot_hashes[slot_hashes_index
153 .checked_sub(1)
154 .expect("`slot_hashes_index` is positive when computing `ancestor_slot`")]
155 .0;
156
157 match proposed_vote_slot.cmp(&ancestor_slot) {
160 Ordering::Less => {
161 if slot_hashes_index == slot_hashes.len() {
162 if proposed_vote_slot >= earliest_slot_hash_in_history {
165 return Err(VoteError::AssertionFailed);
166 }
167 if !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
168 proposed_lockouts_indices_to_filter.push(proposed_lockouts_index);
174 }
175 if let Some(new_proposed_root) = root_to_check {
176 assert_eq!(new_proposed_root, proposed_vote_slot);
180 if new_proposed_root >= earliest_slot_hash_in_history {
184 return Err(VoteError::AssertionFailed);
185 }
186 root_to_check = None;
187 } else {
188 proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
189 "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
190 `proposed_vote_slot` is too old to be in SlotHashes history",
191 );
192 }
193 continue;
194 } else {
195 if root_to_check.is_some() {
199 return Err(VoteError::RootOnDifferentFork);
200 } else {
201 return Err(VoteError::SlotsMismatch);
202 }
203 }
204 }
205 Ordering::Greater => {
206 slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
208 "`slot_hashes_index` is positive when finding newer slots in SlotHashes \
209 history",
210 );
211 continue;
212 }
213 Ordering::Equal => {
214 if root_to_check.is_some() {
218 root_to_check = None;
219 } else {
220 proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
221 "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when match \
222 is found in SlotHashes history",
223 );
224 slot_hashes_index = slot_hashes_index.checked_sub(1).expect(
225 "`slot_hashes_index` is positive when match is found in SlotHashes history",
226 );
227 }
228 }
229 }
230 }
231
232 if proposed_lockouts_index != proposed_lockouts.len() {
233 return Err(VoteError::SlotsMismatch);
235 }
236
237 assert_eq!(last_proposed_slot, slot_hashes[slot_hashes_index].0);
260
261 if slot_hashes[slot_hashes_index].1 != proposed_hash {
262 warn!(
266 "{} dropped vote {:?} root {:?} failed to match hash {} {}",
267 vote_state.node_pubkey(),
268 proposed_lockouts,
269 proposed_root,
270 proposed_hash,
271 slot_hashes[slot_hashes_index].1
272 );
273 return Err(VoteError::SlotHashMismatch);
274 }
275
276 let mut proposed_lockouts_index = 0;
278 let mut filter_votes_index = 0;
279 proposed_lockouts.retain(|_lockout| {
280 let should_retain = if filter_votes_index == proposed_lockouts_indices_to_filter.len() {
281 true
282 } else if proposed_lockouts_index == proposed_lockouts_indices_to_filter[filter_votes_index]
283 {
284 filter_votes_index = filter_votes_index.checked_add(1).unwrap();
285 false
286 } else {
287 true
288 };
289
290 proposed_lockouts_index = proposed_lockouts_index.checked_add(1).expect(
291 "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when filtering out \
292 irrelevant votes",
293 );
294 should_retain
295 });
296
297 Ok(())
298}
299
300fn check_slots_are_valid(
301 vote_state: &VoteStateHandler,
302 vote_slots: &[Slot],
303 vote_hash: &Hash,
304 slot_hashes: &[(Slot, Hash)],
305) -> Result<(), VoteError> {
306 let mut i = 0;
309
310 let mut j = slot_hashes.len();
313
314 while i < vote_slots.len() && j > 0 {
323 if vote_state
326 .last_voted_slot()
327 .is_some_and(|last_voted_slot| vote_slots[i] <= last_voted_slot)
328 {
329 i = i
330 .checked_add(1)
331 .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots");
332 continue;
333 }
334
335 if vote_slots[i] != slot_hashes[j.checked_sub(1).expect("`j` is positive")].0 {
337 j = j
339 .checked_sub(1)
340 .expect("`j` is positive when finding newer slots");
341 continue;
342 }
343
344 i = i
347 .checked_add(1)
348 .expect("`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found");
349 j = j
350 .checked_sub(1)
351 .expect("`j` is positive when hash is found");
352 }
353
354 if j == slot_hashes.len() {
355 debug!(
359 "{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
360 vote_state.node_pubkey(),
361 vote_slots,
362 vote_hash,
363 slot_hashes
364 );
365 return Err(VoteError::VoteTooOld);
366 }
367 if i != vote_slots.len() {
368 info!(
371 "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
372 vote_state.node_pubkey(),
373 vote_slots,
374 slot_hashes,
375 );
376 return Err(VoteError::SlotsMismatch);
377 }
378 if &slot_hashes[j].1 != vote_hash {
379 warn!(
383 "{} dropped vote slots {:?} failed to match hash {} {}",
384 vote_state.node_pubkey(),
385 vote_slots,
386 vote_hash,
387 slot_hashes[j].1
388 );
389 return Err(VoteError::SlotHashMismatch);
390 }
391 Ok(())
392}
393
394pub fn process_new_vote_state(
432 vote_state: &mut VoteStateHandler,
433 mut new_state: VecDeque<LandedVote>,
434 new_root: Option<Slot>,
435 timestamp: Option<i64>,
436 epoch: Epoch,
437 current_slot: Slot,
438) -> Result<(), VoteError> {
439 assert!(!new_state.is_empty());
440 if new_state.len() > MAX_LOCKOUT_HISTORY {
441 return Err(VoteError::TooManyVotes);
442 }
443
444 match (new_root, vote_state.root_slot()) {
445 (Some(new_root), Some(current_root)) if new_root < current_root => {
446 return Err(VoteError::RootRollBack);
447 }
448 (None, Some(_)) => {
449 return Err(VoteError::RootRollBack);
450 }
451 _ => (),
452 }
453
454 let mut previous_vote: Option<&LandedVote> = None;
455
456 for vote in &new_state {
461 if vote.confirmation_count() == 0 {
462 return Err(VoteError::ZeroConfirmations);
463 } else if vote.confirmation_count() > MAX_LOCKOUT_HISTORY as u32 {
464 return Err(VoteError::ConfirmationTooLarge);
465 } else if let Some(new_root) = new_root
466 && vote.slot() <= new_root
467 &&
468 new_root != Slot::default()
473 {
474 return Err(VoteError::SlotSmallerThanRoot);
475 }
476
477 if let Some(previous_vote) = previous_vote {
478 if previous_vote.slot() >= vote.slot() {
479 return Err(VoteError::SlotsNotOrdered);
480 } else if previous_vote.confirmation_count() <= vote.confirmation_count() {
481 return Err(VoteError::ConfirmationsNotOrdered);
482 } else if vote.slot() > previous_vote.lockout.last_locked_out_slot() {
483 return Err(VoteError::NewVoteStateLockoutMismatch);
484 }
485 }
486 previous_vote = Some(vote);
487 }
488
489 let mut current_vote_state_index: usize = 0;
492 let mut new_vote_state_index = 0;
493
494 let mut earned_credits = 0_u64;
496
497 if let Some(new_root) = new_root {
498 for current_vote in vote_state.votes() {
499 if current_vote.slot() <= new_root {
502 earned_credits = earned_credits
503 .checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index))
504 .expect("`earned_credits` does not overflow");
505 current_vote_state_index = current_vote_state_index.checked_add(1).expect(
506 "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when \
507 processing new root",
508 );
509 continue;
510 }
511
512 break;
513 }
514 }
515
516 while current_vote_state_index < vote_state.votes().len()
536 && new_vote_state_index < new_state.len()
537 {
538 let current_vote = &vote_state.votes()[current_vote_state_index];
539 let new_vote = &mut new_state[new_vote_state_index];
540
541 match current_vote.slot().cmp(&new_vote.slot()) {
545 Ordering::Less => {
546 if current_vote.lockout.last_locked_out_slot() >= new_vote.slot() {
547 return Err(VoteError::LockoutConflict);
548 }
549 current_vote_state_index = current_vote_state_index.checked_add(1).expect(
550 "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
551 less than proposed",
552 );
553 }
554 Ordering::Equal => {
555 if new_vote.confirmation_count() < current_vote.confirmation_count() {
558 return Err(VoteError::ConfirmationRollBack);
559 }
560
561 new_vote.latency = vote_state.votes()[current_vote_state_index].latency;
563
564 current_vote_state_index = current_vote_state_index.checked_add(1).expect(
565 "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
566 equal to proposed",
567 );
568 new_vote_state_index = new_vote_state_index.checked_add(1).expect(
569 "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
570 equal to proposed",
571 );
572 }
573 Ordering::Greater => {
574 new_vote_state_index = new_vote_state_index.checked_add(1).expect(
575 "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` when slot is \
576 greater than proposed",
577 );
578 }
579 }
580 }
581
582 for new_vote in new_state.iter_mut() {
588 if new_vote.latency == 0 {
589 new_vote.latency = handler::compute_vote_latency(new_vote.slot(), current_slot);
590 }
591 }
592
593 if vote_state.root_slot() != new_root {
594 vote_state.increment_credits(epoch, earned_credits);
598 }
599 if let Some(timestamp) = timestamp {
600 let last_slot = new_state.back().unwrap().slot();
601 vote_state.process_timestamp(last_slot, timestamp)?;
602 }
603 vote_state.set_root_slot(new_root);
604 vote_state.set_votes(new_state);
605
606 Ok(())
607}
608
609pub fn process_vote_unfiltered(
610 vote_state: &mut VoteStateHandler,
611 vote_slots: &[Slot],
612 vote: &Vote,
613 slot_hashes: &[SlotHash],
614 epoch: Epoch,
615 current_slot: Slot,
616) -> Result<(), VoteError> {
617 check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?;
618 vote_slots
619 .iter()
620 .for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot));
621 Ok(())
622}
623
624pub fn process_vote(
625 vote_state: &mut VoteStateHandler,
626 vote: &Vote,
627 slot_hashes: &[SlotHash],
628 epoch: Epoch,
629 current_slot: Slot,
630) -> Result<(), VoteError> {
631 if vote.slots.is_empty() {
632 return Err(VoteError::EmptySlots);
633 }
634 let earliest_slot_in_history = slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
635 let vote_slots = vote
636 .slots
637 .iter()
638 .filter(|slot| **slot >= earliest_slot_in_history)
639 .cloned()
640 .collect::<Vec<Slot>>();
641 if vote_slots.is_empty() {
642 return Err(VoteError::VotesTooOldAllFiltered);
643 }
644 process_vote_unfiltered(
645 vote_state,
646 &vote_slots,
647 vote,
648 slot_hashes,
649 epoch,
650 current_slot,
651 )
652}
653
654pub fn process_vote_unchecked(
656 vote_state: &mut VoteStateHandler,
657 vote: Vote,
658) -> Result<(), VoteError> {
659 if vote.slots.is_empty() {
660 return Err(VoteError::EmptySlots);
661 }
662 let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
663 process_vote_unfiltered(
664 vote_state,
665 &vote.slots,
666 &vote,
667 &slot_hashes,
668 vote_state.current_epoch(),
669 0,
670 )
671}
672
673#[cfg(test)]
674pub fn process_slot_votes_unchecked(vote_state: &mut VoteStateHandler, slots: &[Slot]) {
675 for slot in slots {
676 process_slot_vote_unchecked(vote_state, *slot);
677 }
678}
679
680pub fn process_slot_vote_unchecked(vote_state: &mut VoteStateHandler, slot: Slot) {
681 let _ = process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
682}
683
684pub fn authorize<S: std::hash::BuildHasher, F>(
688 vote_account: &mut BorrowedInstructionAccount,
689 target_version: VoteStateTargetVersion,
690 authorized: &Pubkey,
691 vote_authorize: VoteAuthorize,
692 signers: &HashSet<Pubkey, S>,
693 clock: &Clock,
694 is_vote_authorize_with_bls_enabled: bool,
695 consume_pop_compute_units: F,
696) -> Result<(), InstructionError>
697where
698 F: FnOnce() -> Result<(), InstructionError>,
699{
700 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
701
702 match vote_authorize {
703 VoteAuthorize::Voter => {
704 if is_vote_authorize_with_bls_enabled && vote_state.has_bls_pubkey() {
705 return Err(InstructionError::InvalidInstructionData);
706 }
707 let authorized_withdrawer_signer =
708 verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
709
710 vote_state.set_new_authorized_voter(
711 authorized,
712 clock.epoch,
713 clock
714 .leader_schedule_epoch
715 .checked_add(1)
716 .ok_or(InstructionError::InvalidAccountData)?,
717 None,
718 |epoch_authorized_voter| {
719 if authorized_withdrawer_signer {
721 Ok(())
722 } else {
723 verify_authorized_signer(&epoch_authorized_voter, signers)
724 }
725 },
726 )?;
727 }
728 VoteAuthorize::Withdrawer => {
729 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
731 vote_state.set_authorized_withdrawer(*authorized);
732 }
733 VoteAuthorize::VoterWithBLS(args) => {
734 if !is_vote_authorize_with_bls_enabled {
735 return Err(InstructionError::InvalidInstructionData);
736 }
737 let authorized_withdrawer_signer =
738 verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok();
739
740 verify_bls_proof_of_possession(
741 vote_account.get_key(),
742 &args.bls_pubkey,
743 &args.bls_proof_of_possession,
744 consume_pop_compute_units,
745 )?;
746
747 vote_state.set_new_authorized_voter(
748 authorized,
749 clock.epoch,
750 clock
751 .leader_schedule_epoch
752 .checked_add(1)
753 .ok_or(InstructionError::InvalidAccountData)?,
754 Some(&args.bls_pubkey),
755 |epoch_authorized_voter| {
756 if authorized_withdrawer_signer {
758 Ok(())
759 } else {
760 verify_authorized_signer(&epoch_authorized_voter, signers)
761 }
762 },
763 )?;
764 }
765 }
766
767 vote_state.set_vote_account_state(vote_account)
768}
769
770pub fn update_validator_identity<S: std::hash::BuildHasher>(
772 vote_account: &mut BorrowedInstructionAccount,
773 target_version: VoteStateTargetVersion,
774 node_pubkey: &Pubkey,
775 signers: &HashSet<Pubkey, S>,
776 custom_commission_collector_enabled: bool,
777) -> Result<(), InstructionError> {
778 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
779
780 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
782
783 verify_authorized_signer(node_pubkey, signers)?;
785
786 vote_state.set_node_pubkey(*node_pubkey);
787
788 if !custom_commission_collector_enabled {
791 vote_state.set_block_revenue_collector(*node_pubkey);
792 }
793
794 vote_state.set_vote_account_state(vote_account)
795}
796
797pub fn update_commission<S: std::hash::BuildHasher>(
799 vote_account: &mut BorrowedInstructionAccount,
800 target_version: VoteStateTargetVersion,
801 commission: u8,
802 signers: &HashSet<Pubkey, S>,
803 epoch_schedule: &EpochSchedule,
804 clock: &Clock,
805 disable_commission_update_rule: bool,
806) -> Result<(), InstructionError> {
807 let vote_state_result = get_vote_state_handler_checked(vote_account, target_version);
808 let enforce_commission_update_rule = !disable_commission_update_rule
809 && match vote_state_result.as_ref() {
810 Ok(decoded_vote_state) => commission > decoded_vote_state.commission(),
811 Err(_) => true,
812 };
813
814 if enforce_commission_update_rule && !is_commission_update_allowed(clock.slot, epoch_schedule) {
815 return Err(VoteError::CommissionUpdateTooLate.into());
816 }
817
818 let mut vote_state = vote_state_result?;
819
820 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
822
823 vote_state.set_commission(commission);
824
825 vote_state.set_vote_account_state(vote_account)
826}
827
828pub fn update_commission_bps<S: std::hash::BuildHasher>(
830 vote_account: &mut BorrowedInstructionAccount,
831 target_version: VoteStateTargetVersion,
832 commission_bps: u16,
833 kind: CommissionKind,
834 signers: &HashSet<Pubkey, S>,
835 block_revenue_sharing_enabled: bool,
836) -> Result<(), InstructionError> {
837 if matches!(kind, CommissionKind::BlockRevenue) && !block_revenue_sharing_enabled {
840 return Err(InstructionError::InvalidInstructionData);
841 }
842
843 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
844
845 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
849
850 match kind {
851 CommissionKind::InflationRewards => {
852 vote_state.set_inflation_rewards_commission_bps(commission_bps);
853 }
854 CommissionKind::BlockRevenue => {
855 vote_state.set_block_revenue_commission_bps(commission_bps);
856 }
857 }
858
859 vote_state.set_vote_account_state(vote_account)
860}
861
862pub enum NewCommissionCollector<'a, 'b> {
863 VoteAccount,
864 NewAccount(BorrowedInstructionAccount<'a, 'b>),
865}
866
867impl NewCommissionCollector<'_, '_> {
868 pub fn validate_and_resolve_key(
877 &self,
878 vote_account: &BorrowedInstructionAccount,
879 rent: &Rent,
880 ) -> Result<Pubkey, InstructionError> {
881 match self {
882 NewCommissionCollector::VoteAccount => Ok(*vote_account.get_key()),
883 NewCommissionCollector::NewAccount(collector_account) => {
884 if collector_account.get_owner() != &system_program::id() {
886 return Err(InstructionError::InvalidAccountOwner);
887 }
888
889 if !rent.is_exempt(
891 collector_account.get_lamports(),
892 collector_account.get_data().len(),
893 ) {
894 return Err(InstructionError::InsufficientFunds);
895 }
896
897 if !collector_account.is_writable() {
899 return Err(InstructionError::InvalidArgument);
900 }
901
902 Ok(*collector_account.get_key())
903 }
904 }
905 }
906}
907
908pub fn update_commission_collector<S: std::hash::BuildHasher>(
910 vote_account: &mut BorrowedInstructionAccount,
911 target_version: VoteStateTargetVersion,
912 new_collector: NewCommissionCollector,
913 kind: CommissionKind,
914 signers: &HashSet<Pubkey, S>,
915 rent: &Rent,
916) -> Result<(), InstructionError> {
917 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
918
919 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
921
922 let new_collector_key = new_collector.validate_and_resolve_key(vote_account, rent)?;
923
924 match kind {
925 CommissionKind::InflationRewards => {
926 vote_state.set_inflation_rewards_collector(new_collector_key);
927 }
928 CommissionKind::BlockRevenue => {
929 vote_state.set_block_revenue_collector(new_collector_key);
930 }
931 }
932
933 vote_state.set_vote_account_state(vote_account)
934}
935
936pub fn deposit_delegator_rewards<S: std::hash::BuildHasher>(
938 invoke_context: &mut InvokeContext,
939 vote_account_index: IndexOfAccount,
940 sender_account_index: IndexOfAccount,
941 deposit: u64,
942 signers: &HashSet<Pubkey, S>,
943) -> Result<(), InstructionError> {
944 let transaction_context = &invoke_context.transaction_context;
945 let instruction_context = transaction_context.get_current_instruction_context()?;
946
947 let vote_address = *instruction_context.get_key_of_instruction_account(vote_account_index)?;
948 let source_address =
949 *instruction_context.get_key_of_instruction_account(sender_account_index)?;
950
951 verify_authorized_signer(&source_address, signers)?;
953
954 let mut vote_state = {
961 let vote_account =
962 instruction_context.try_borrow_instruction_account(vote_account_index)?;
963
964 let versioned = VoteStateVersionsInline::deserialize(vote_account.get_data())?;
968 if let VoteStateVersionsInline::V4(vote_state_v4) = versioned {
969 Ok(VoteStateHandler::new_v4(vote_state_v4))
970 } else {
971 Err(InstructionError::InvalidAccountData)
972 }
973 }?;
974
975 invoke_context.native_invoke_signed(
977 system_instruction::transfer(&source_address, &vote_address, deposit),
978 &[],
979 )?;
980
981 let transaction_context = &invoke_context.transaction_context;
983 let instruction_context = transaction_context.get_current_instruction_context()?;
984 let mut vote_account =
985 instruction_context.try_borrow_instruction_account(vote_account_index)?;
986
987 vote_state.add_pending_delegator_rewards(deposit)?;
988 vote_state.set_vote_account_state(&mut vote_account)
989}
990
991pub fn is_commission_update_allowed(slot: Slot, epoch_schedule: &EpochSchedule) -> bool {
994 if let Some(relative_slot) = slot
996 .saturating_sub(epoch_schedule.first_normal_slot)
997 .checked_rem(epoch_schedule.slots_per_epoch)
998 {
999 relative_slot.saturating_mul(2) <= epoch_schedule.slots_per_epoch
1001 } else {
1002 true
1004 }
1005}
1006
1007fn verify_authorized_signer<S: std::hash::BuildHasher>(
1008 authorized: &Pubkey,
1009 signers: &HashSet<Pubkey, S>,
1010) -> Result<(), InstructionError> {
1011 if signers.contains(authorized) {
1012 Ok(())
1013 } else {
1014 Err(InstructionError::MissingRequiredSignature)
1015 }
1016}
1017
1018const POP_MESSAGE_SIZE: usize = 9 + size_of::<Pubkey>();
1023
1024pub(crate) fn generate_pop_message(vote_account_pubkey: &Pubkey) -> [u8; POP_MESSAGE_SIZE] {
1025 const LABEL_LEN: usize = 9;
1026 const PUBKEY_LEN: usize = size_of::<Pubkey>();
1027
1028 const LABEL_START: usize = 0;
1029 const LABEL_END: usize = LABEL_START + LABEL_LEN;
1030
1031 const PUBKEY_START: usize = LABEL_END;
1032 const PUBKEY_END: usize = PUBKEY_START + PUBKEY_LEN;
1033
1034 const _: () = assert!(PUBKEY_END == POP_MESSAGE_SIZE);
1036
1037 let mut message = [0u8; POP_MESSAGE_SIZE];
1038
1039 message[LABEL_START..LABEL_END].copy_from_slice(b"ALPENGLOW");
1040 message[PUBKEY_START..PUBKEY_END].copy_from_slice(vote_account_pubkey.as_ref());
1041
1042 message
1043}
1044
1045pub fn verify_bls_proof_of_possession<F>(
1046 vote_account_pubkey: &Pubkey,
1047 bls_pubkey_compressed_bytes: &[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1048 bls_proof_of_possession_compressed_bytes: &[u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1049 consume_pop_compute_units: F,
1050) -> Result<(), InstructionError>
1051where
1052 F: FnOnce() -> Result<(), InstructionError>,
1053{
1054 consume_pop_compute_units()?;
1056
1057 let message = generate_pop_message(vote_account_pubkey);
1058 bls_proof_of_possession_compressed_bytes
1059 .verify(bls_pubkey_compressed_bytes, Some(&message))
1060 .map_err(|_| InstructionError::InvalidArgument)
1061}
1062
1063pub fn withdraw<S: std::hash::BuildHasher>(
1065 instruction_context: &InstructionContext,
1066 vote_account_index: IndexOfAccount,
1067 target_version: VoteStateTargetVersion,
1068 lamports: u64,
1069 to_account_index: IndexOfAccount,
1070 signers: &HashSet<Pubkey, S>,
1071 rent_sysvar: &Rent,
1072 clock: &Clock,
1073) -> Result<(), InstructionError> {
1074 let mut vote_account =
1075 instruction_context.try_borrow_instruction_account(vote_account_index)?;
1076 let vote_state = get_vote_state_handler_checked(&vote_account, target_version)?;
1077
1078 verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?;
1079
1080 let remaining_balance = vote_account
1081 .get_lamports()
1082 .checked_sub(lamports)
1083 .ok_or(InstructionError::InsufficientFunds)?;
1084
1085 let pending_delegator_rewards = vote_state.pending_delegator_rewards();
1087
1088 if remaining_balance == 0 {
1089 if pending_delegator_rewards > 0 {
1092 return Err(InstructionError::InsufficientFunds);
1093 }
1094
1095 let reject_active_vote_account_close = vote_state
1096 .epoch_credits()
1097 .last()
1098 .map(|(last_epoch_with_credits, _, _)| {
1099 let current_epoch = clock.epoch;
1100 current_epoch.saturating_sub(*last_epoch_with_credits) < 2
1104 })
1105 .unwrap_or(false);
1106
1107 if reject_active_vote_account_close {
1108 return Err(VoteError::ActiveVoteAccountClose.into());
1109 } else {
1110 VoteStateHandler::deinitialize_vote_account_state(&mut vote_account, target_version)?;
1112 }
1113 } else {
1114 let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
1117 let min_balance = min_rent_exempt_balance
1118 .checked_add(pending_delegator_rewards)
1119 .ok_or(InstructionError::ArithmeticOverflow)?;
1120 if remaining_balance < min_balance {
1121 return Err(InstructionError::InsufficientFunds);
1122 }
1123 }
1124
1125 vote_account.checked_sub_lamports(lamports)?;
1126 drop(vote_account);
1127 let mut to_account = instruction_context.try_borrow_instruction_account(to_account_index)?;
1128 to_account.checked_add_lamports(lamports)?;
1129 Ok(())
1130}
1131
1132pub fn initialize_account_v2<S: std::hash::BuildHasher, F>(
1141 vote_account: &mut BorrowedInstructionAccount,
1142 target_version: VoteStateTargetVersion,
1143 vote_init: &VoteInitV2,
1144 inflation_rewards_collector: NewCommissionCollector,
1145 block_revenue_collector: NewCommissionCollector,
1146 signers: &HashSet<Pubkey, S>,
1147 clock: &Clock,
1148 rent: &Rent,
1149 consume_pop_compute_units: F,
1150) -> Result<(), InstructionError>
1151where
1152 F: FnOnce() -> Result<(), InstructionError>,
1153{
1154 VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
1155 let versioned = vote_account.get_state::<VoteStateVersions>()?;
1156
1157 if !versioned.is_uninitialized() {
1158 return Err(InstructionError::AccountAlreadyInitialized);
1159 }
1160
1161 verify_authorized_signer(&vote_init.node_pubkey, signers)?;
1163
1164 let inflation_rewards_collector_key =
1167 inflation_rewards_collector.validate_and_resolve_key(vote_account, rent)?;
1168 let block_revenue_collector_key =
1169 block_revenue_collector.validate_and_resolve_key(vote_account, rent)?;
1170
1171 verify_bls_proof_of_possession(
1173 vote_account.get_key(),
1174 &vote_init.authorized_voter_bls_pubkey,
1175 &vote_init.authorized_voter_bls_proof_of_possession,
1176 consume_pop_compute_units,
1177 )?;
1178
1179 VoteStateHandler::init_vote_account_state_v2(
1180 vote_account,
1181 vote_init,
1182 &inflation_rewards_collector_key,
1183 &block_revenue_collector_key,
1184 clock,
1185 target_version,
1186 )
1187}
1188
1189pub fn initialize_account<S: std::hash::BuildHasher>(
1193 vote_account: &mut BorrowedInstructionAccount,
1194 target_version: VoteStateTargetVersion,
1195 vote_init: &VoteInit,
1196 signers: &HashSet<Pubkey, S>,
1197 clock: &Clock,
1198) -> Result<(), InstructionError> {
1199 VoteStateHandler::check_vote_account_length(vote_account, target_version)?;
1200 let versioned = vote_account.get_state::<VoteStateVersions>()?;
1201
1202 if !versioned.is_uninitialized() {
1203 return Err(InstructionError::AccountAlreadyInitialized);
1204 }
1205
1206 verify_authorized_signer(&vote_init.node_pubkey, signers)?;
1208
1209 VoteStateHandler::init_vote_account_state(vote_account, vote_init, clock, target_version)
1210}
1211
1212pub fn process_vote_with_account<S: std::hash::BuildHasher>(
1213 vote_account: &mut BorrowedInstructionAccount,
1214 target_version: VoteStateTargetVersion,
1215 slot_hashes: &[SlotHash],
1216 clock: &Clock,
1217 vote: &Vote,
1218 signers: &HashSet<Pubkey, S>,
1219) -> Result<(), InstructionError> {
1220 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1221
1222 let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1223 verify_authorized_signer(&authorized_voter, signers)?;
1224
1225 process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?;
1226 if let Some(timestamp) = vote.timestamp {
1227 vote.slots
1228 .iter()
1229 .max()
1230 .ok_or(VoteError::EmptySlots)
1231 .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
1232 }
1233 vote_state.set_vote_account_state(vote_account)
1234}
1235
1236pub fn process_vote_state_update<S: std::hash::BuildHasher>(
1237 vote_account: &mut BorrowedInstructionAccount,
1238 target_version: VoteStateTargetVersion,
1239 slot_hashes: &[SlotHash],
1240 clock: &Clock,
1241 vote_state_update: VoteStateUpdate,
1242 signers: &HashSet<Pubkey, S>,
1243) -> Result<(), InstructionError> {
1244 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1245
1246 let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1247 verify_authorized_signer(&authorized_voter, signers)?;
1248
1249 do_process_vote_state_update(
1250 &mut vote_state,
1251 slot_hashes,
1252 clock.epoch,
1253 clock.slot,
1254 vote_state_update,
1255 )?;
1256 vote_state.set_vote_account_state(vote_account)
1257}
1258
1259pub fn do_process_vote_state_update(
1260 vote_state: &mut VoteStateHandler,
1261 slot_hashes: &[SlotHash],
1262 epoch: u64,
1263 slot: u64,
1264 mut vote_state_update: VoteStateUpdate,
1265) -> Result<(), VoteError> {
1266 check_and_filter_proposed_vote_state(
1267 vote_state,
1268 &mut vote_state_update.lockouts,
1269 &mut vote_state_update.root,
1270 vote_state_update.hash,
1271 slot_hashes,
1272 )?;
1273 process_new_vote_state(
1274 vote_state,
1275 vote_state_update
1276 .lockouts
1277 .iter()
1278 .map(|lockout| LandedVote::from(*lockout))
1279 .collect(),
1280 vote_state_update.root,
1281 vote_state_update.timestamp,
1282 epoch,
1283 slot,
1284 )
1285}
1286
1287pub fn process_tower_sync<S: std::hash::BuildHasher>(
1288 vote_account: &mut BorrowedInstructionAccount,
1289 target_version: VoteStateTargetVersion,
1290 slot_hashes: &[SlotHash],
1291 clock: &Clock,
1292 tower_sync: TowerSync,
1293 signers: &HashSet<Pubkey, S>,
1294) -> Result<(), InstructionError> {
1295 let mut vote_state = get_vote_state_handler_checked(vote_account, target_version)?;
1296
1297 let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
1298 verify_authorized_signer(&authorized_voter, signers)?;
1299
1300 do_process_tower_sync(
1301 &mut vote_state,
1302 slot_hashes,
1303 clock.epoch,
1304 clock.slot,
1305 tower_sync,
1306 )?;
1307 vote_state.set_vote_account_state(vote_account)
1308}
1309
1310fn do_process_tower_sync(
1311 vote_state: &mut VoteStateHandler,
1312 slot_hashes: &[SlotHash],
1313 epoch: u64,
1314 slot: u64,
1315 mut tower_sync: TowerSync,
1316) -> Result<(), VoteError> {
1317 check_and_filter_proposed_vote_state(
1318 vote_state,
1319 &mut tower_sync.lockouts,
1320 &mut tower_sync.root,
1321 tower_sync.hash,
1322 slot_hashes,
1323 )?;
1324 process_new_vote_state(
1325 vote_state,
1326 tower_sync
1327 .lockouts
1328 .iter()
1329 .map(|lockout| LandedVote::from(*lockout))
1330 .collect(),
1331 tower_sync.root,
1332 tower_sync.timestamp,
1333 epoch,
1334 slot,
1335 )
1336}
1337
1338pub fn create_v4_account_with_authorized(
1339 node_pubkey: &Pubkey,
1340 authorized_voter: &Pubkey,
1341 authorized_voter_bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1342 authorized_withdrawer: &Pubkey,
1343 inflation_rewards_commission_bps: u16,
1344 inflation_rewards_collector: &Pubkey,
1345 block_revenue_commission_bps: u16,
1346 block_revenue_collector: &Pubkey,
1347 lamports: u64,
1348) -> AccountSharedData {
1349 let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id());
1350
1351 let authorized_voter_bls_proof_of_possession = [0; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE];
1354
1355 let vote_state = VoteStateV4::new(
1356 &VoteInitV2 {
1357 node_pubkey: *node_pubkey,
1358 authorized_voter: *authorized_voter,
1359 authorized_voter_bls_pubkey,
1360 authorized_voter_bls_proof_of_possession,
1361 authorized_withdrawer: *authorized_withdrawer,
1362 inflation_rewards_commission_bps,
1363 block_revenue_commission_bps,
1364 },
1365 inflation_rewards_collector,
1366 block_revenue_collector,
1367 &Clock::default(),
1368 );
1369
1370 VoteStateV4::serialize(
1371 &VoteStateVersions::V4(Box::new(vote_state)),
1372 vote_account.data_as_mut_slice(),
1373 )
1374 .unwrap();
1375
1376 vote_account
1377}
1378
1379pub fn create_bls_pubkey_and_proof_of_possession(
1380 vote_account_pubkey: &Pubkey,
1381) -> (
1382 [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1383 [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1384) {
1385 let bls_keypair = BLSKeypair::new();
1386 create_bls_proof_of_possession(vote_account_pubkey, &bls_keypair)
1387}
1388
1389pub fn create_bls_proof_of_possession(
1390 vote_account_pubkey: &Pubkey,
1391 bls_keypair: &BLSKeypair,
1392) -> (
1393 [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1394 [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1395) {
1396 let bls_pubkey_bytes = bls_keypair.public.to_bytes_compressed();
1397 let message = generate_pop_message(vote_account_pubkey);
1398
1399 let proof_of_possession = bls_keypair.proof_of_possession(Some(&message));
1400 let proof_of_possession_bytes = proof_of_possession.to_bytes_compressed();
1401
1402 (bls_pubkey_bytes, proof_of_possession_bytes)
1403}
1404
1405#[allow(clippy::arithmetic_side_effects)]
1406#[cfg(test)]
1407mod tests {
1408 use {
1409 super::*,
1410 assert_matches::assert_matches,
1411 solana_account::{AccountSharedData, ReadableAccount},
1412 solana_clock::DEFAULT_SLOTS_PER_EPOCH,
1413 solana_sha256_hasher::hash,
1414 solana_transaction_context::{
1415 instruction_accounts::InstructionAccount, transaction::TransactionContext,
1416 },
1417 solana_vote_interface::authorized_voters::AuthorizedVoters,
1418 test_case::{test_case, test_matrix},
1419 };
1420
1421 const MAX_RECENT_VOTES: usize = 16;
1422
1423 fn vote_state_new_for_test(
1424 vote_pubkey: &Pubkey,
1425 target_version: VoteStateTargetVersion,
1426 ) -> VoteStateHandler {
1427 let auth_pubkey = solana_pubkey::new_rand();
1428 let vote_init = VoteInit {
1429 node_pubkey: solana_pubkey::new_rand(),
1430 authorized_voter: auth_pubkey,
1431 authorized_withdrawer: auth_pubkey,
1432 commission: 0,
1433 };
1434 let clock = Clock::default();
1435
1436 match target_version {
1437 VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1438 vote_pubkey,
1439 &vote_init,
1440 &clock,
1441 )),
1442 }
1443 }
1444
1445 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1446 fn test_vote_state_upgrade_from_1_14_11(target_version: VoteStateTargetVersion) {
1447 let vote_pubkey = solana_pubkey::new_rand();
1448 let mut vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
1449
1450 vote_state.increment_credits(0, 100);
1452 assert_eq!(
1453 vote_state.set_new_authorized_voter(
1454 &solana_pubkey::new_rand(),
1455 0,
1456 1,
1457 None,
1458 |_pubkey| Ok(())
1459 ),
1460 Ok(())
1461 );
1462 vote_state.increment_credits(1, 200);
1463 assert_eq!(
1464 vote_state.set_new_authorized_voter(
1465 &solana_pubkey::new_rand(),
1466 1,
1467 2,
1468 None,
1469 |_pubkey| Ok(())
1470 ),
1471 Ok(())
1472 );
1473 vote_state.increment_credits(2, 300);
1474 assert_eq!(
1475 vote_state.set_new_authorized_voter(
1476 &solana_pubkey::new_rand(),
1477 2,
1478 3,
1479 None,
1480 |_pubkey| Ok(())
1481 ),
1482 Ok(())
1483 );
1484
1485 vec![
1487 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
1488 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
1489 134, 135,
1490 ]
1491 .into_iter()
1492 .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0));
1493
1494 let vote_state_v1_14_11 = match target_version {
1497 VoteStateTargetVersion::V4 => {
1498 VoteState1_14_11 {
1500 node_pubkey: *vote_state.node_pubkey(),
1501 authorized_withdrawer: *vote_state.authorized_withdrawer(),
1502 commission: vote_state.commission(),
1503 votes: vote_state
1504 .votes()
1505 .iter()
1506 .map(|landed_vote| (*landed_vote).into())
1507 .collect(),
1508 root_slot: vote_state.root_slot(),
1509 authorized_voters: vote_state.authorized_voters().clone(),
1510 epoch_credits: vote_state.epoch_credits().clone(),
1511 last_timestamp: vote_state.last_timestamp().clone(),
1512 prior_voters: CircBuf::default(), }
1514 }
1515 };
1516 let version1_14_11_serialized =
1517 bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(vote_state_v1_14_11)))
1518 .unwrap();
1519 let version1_14_11_serialized_len = version1_14_11_serialized.len();
1520 let rent = Rent::default();
1521 let lamports = rent.minimum_balance(version1_14_11_serialized_len);
1522 let mut vote_account =
1523 AccountSharedData::new(lamports, version1_14_11_serialized_len, &id());
1524 vote_account.set_data_from_slice(&version1_14_11_serialized);
1525
1526 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1529 let mut transaction_context = TransactionContext::new(
1530 vec![(id(), processor_account), (vote_pubkey, vote_account)],
1531 rent.clone(),
1532 0,
1533 0,
1534 1,
1535 );
1536 transaction_context
1537 .configure_top_level_instruction_for_tests(
1538 0,
1539 vec![InstructionAccount::new(1, false, true)],
1540 vec![],
1541 )
1542 .unwrap();
1543 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1544
1545 let mut borrowed_account = instruction_context
1548 .try_borrow_instruction_account(0)
1549 .unwrap();
1550
1551 let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1553 assert_matches!(vote_state_version, VoteStateVersions::V1_14_11(_));
1554
1555 let converted_vote_state =
1557 get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1558
1559 assert!(vote_state == converted_vote_state);
1561
1562 let vote_state = converted_vote_state;
1563
1564 match target_version {
1567 VoteStateTargetVersion::V4 => {
1568 assert_eq!(
1570 vote_state
1571 .clone()
1572 .set_vote_account_state(&mut borrowed_account),
1573 Err(InstructionError::AccountNotRentExempt)
1574 );
1575 }
1576 }
1577
1578 let converted_vote_state =
1580 get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1581
1582 assert!(vote_state == converted_vote_state);
1584
1585 let vote_state = converted_vote_state;
1586
1587 let space = match target_version {
1589 VoteStateTargetVersion::V4 => VoteStateV4::size_of(),
1590 };
1591 assert_eq!(
1592 borrowed_account.set_lamports(rent.minimum_balance(space)),
1593 Ok(())
1594 );
1595 assert_eq!(
1596 vote_state
1597 .clone()
1598 .set_vote_account_state(&mut borrowed_account),
1599 Ok(())
1600 );
1601
1602 let vote_state_version = borrowed_account.get_state::<VoteStateVersions>().unwrap();
1604 match target_version {
1605 VoteStateTargetVersion::V4 => {
1606 assert_matches!(vote_state_version, VoteStateVersions::V4(_));
1607 }
1608 }
1609
1610 let converted_vote_state =
1612 get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1613
1614 assert_eq!(vote_state, converted_vote_state);
1616 }
1617
1618 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1619 fn test_vote_lockout(target_version: VoteStateTargetVersion) {
1620 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1621
1622 for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
1623 process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
1624 }
1625
1626 assert_eq!(vote_state.votes().len(), MAX_LOCKOUT_HISTORY);
1628 assert_eq!(vote_state.root_slot(), Some(0));
1629 check_lockouts(&vote_state);
1630
1631 let top_vote = vote_state.votes().front().unwrap().slot();
1635 let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
1636 process_slot_vote_unchecked(&mut vote_state, slot);
1637 assert_eq!(Some(top_vote), vote_state.root_slot());
1638
1639 let slot = vote_state
1641 .votes()
1642 .front()
1643 .unwrap()
1644 .lockout
1645 .last_locked_out_slot();
1646 process_slot_vote_unchecked(&mut vote_state, slot);
1647 assert_eq!(vote_state.votes().len(), 2);
1649 }
1650
1651 #[test_matrix(
1652 [VoteStateTargetVersion::V4],
1653 [true, false]
1654 )]
1655 fn test_update_commission(
1656 target_version: VoteStateTargetVersion,
1657 disable_commission_update_rule: bool,
1658 ) {
1659 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1660 let node_pubkey = *vote_state.node_pubkey();
1661 let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1662
1663 vote_state.set_commission(10);
1665
1666 let serialized = vote_state.serialize();
1667 let serialized_len = serialized.len();
1668 let rent = Rent::default();
1669 let lamports = rent.minimum_balance(serialized_len);
1670 let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1671 vote_account.set_data_from_slice(&serialized);
1672
1673 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1676 let mut transaction_context = TransactionContext::new(
1677 vec![(id(), processor_account), (node_pubkey, vote_account)],
1678 rent,
1679 0,
1680 0,
1681 1,
1682 );
1683 transaction_context
1684 .configure_top_level_instruction_for_tests(
1685 0,
1686 vec![InstructionAccount::new(1, false, true)],
1687 vec![],
1688 )
1689 .unwrap();
1690 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1691
1692 let mut borrowed_account = instruction_context
1695 .try_borrow_instruction_account(0)
1696 .unwrap();
1697
1698 let epoch_schedule = std::sync::Arc::new(EpochSchedule::without_warmup());
1699
1700 let first_half_clock = std::sync::Arc::new(Clock {
1701 slot: epoch_schedule.slots_per_epoch / 4,
1702 ..Clock::default()
1703 });
1704
1705 let second_half_clock = std::sync::Arc::new(Clock {
1706 slot: (epoch_schedule.slots_per_epoch * 3) / 4,
1707 ..Clock::default()
1708 });
1709
1710 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1711
1712 assert_eq!(
1714 get_vote_state_handler_checked(&borrowed_account, target_version,)
1715 .unwrap()
1716 .commission(),
1717 10
1718 );
1719 assert_matches!(
1720 update_commission(
1721 &mut borrowed_account,
1722 target_version,
1723 11,
1724 &signers,
1725 &epoch_schedule,
1726 &first_half_clock,
1727 disable_commission_update_rule,
1728 ),
1729 Ok(())
1730 );
1731 assert_eq!(
1732 get_vote_state_handler_checked(&borrowed_account, target_version,)
1733 .unwrap()
1734 .commission(),
1735 11
1736 );
1737
1738 let result = update_commission(
1740 &mut borrowed_account,
1741 target_version,
1742 12,
1743 &signers,
1744 &epoch_schedule,
1745 &second_half_clock,
1746 disable_commission_update_rule,
1747 );
1748 let state_commission = get_vote_state_handler_checked(&borrowed_account, target_version)
1749 .unwrap()
1750 .commission();
1751 if disable_commission_update_rule {
1752 assert_matches!(result, Ok(()));
1753 assert_eq!(state_commission, 12);
1754 } else {
1755 assert_matches!(result, Err(_));
1756 assert_eq!(state_commission, 11);
1757 }
1758
1759 assert_matches!(
1761 update_commission(
1762 &mut borrowed_account,
1763 target_version,
1764 10,
1765 &signers,
1766 &epoch_schedule,
1767 &first_half_clock,
1768 disable_commission_update_rule,
1769 ),
1770 Ok(())
1771 );
1772 assert_eq!(
1773 get_vote_state_handler_checked(&borrowed_account, target_version,)
1774 .unwrap()
1775 .commission(),
1776 10
1777 );
1778
1779 assert_eq!(
1780 get_vote_state_handler_checked(&borrowed_account, target_version,)
1781 .unwrap()
1782 .commission(),
1783 10
1784 );
1785
1786 assert_matches!(
1788 update_commission(
1789 &mut borrowed_account,
1790 target_version,
1791 9,
1792 &signers,
1793 &epoch_schedule,
1794 &second_half_clock,
1795 disable_commission_update_rule,
1796 ),
1797 Ok(())
1798 );
1799 assert_eq!(
1800 get_vote_state_handler_checked(&borrowed_account, target_version,)
1801 .unwrap()
1802 .commission(),
1803 9
1804 );
1805 }
1806
1807 #[test]
1814 fn test_update_commission_bps() {
1815 let target_version = VoteStateTargetVersion::V4;
1816 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1817 let withdrawer_pubkey = *vote_state.authorized_withdrawer();
1818 let node_pubkey = *vote_state.node_pubkey();
1819
1820 vote_state.set_commission(10); let serialized = vote_state.serialize();
1824 let serialized_len = serialized.len();
1825 let rent = Rent::default();
1826 let lamports = rent.minimum_balance(serialized_len);
1827 let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
1828 vote_account.set_data_from_slice(&serialized);
1829
1830 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
1831 let mut transaction_context = TransactionContext::new(
1832 vec![(id(), processor_account), (node_pubkey, vote_account)],
1833 rent,
1834 0,
1835 0,
1836 1,
1837 );
1838 transaction_context
1839 .configure_top_level_instruction_for_tests(
1840 0,
1841 vec![InstructionAccount::new(1, false, true)],
1842 vec![],
1843 )
1844 .unwrap();
1845 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1846 let mut borrowed_account = instruction_context
1847 .try_borrow_instruction_account(0)
1848 .unwrap();
1849
1850 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
1851 let non_signers: HashSet<Pubkey> = HashSet::new();
1852
1853 assert_eq!(
1856 update_commission_bps(
1857 &mut borrowed_account,
1858 target_version,
1859 500,
1860 CommissionKind::BlockRevenue,
1861 &signers,
1862 false, ),
1864 Err(InstructionError::InvalidInstructionData)
1865 );
1866
1867 assert_eq!(
1869 update_commission_bps(
1870 &mut borrowed_account,
1871 target_version,
1872 500,
1873 CommissionKind::InflationRewards,
1874 &non_signers,
1875 false,
1876 ),
1877 Err(InstructionError::MissingRequiredSignature)
1878 );
1879
1880 let wrong_signers: HashSet<Pubkey> = vec![Pubkey::new_unique()].into_iter().collect();
1882 assert_eq!(
1883 update_commission_bps(
1884 &mut borrowed_account,
1885 target_version,
1886 500,
1887 CommissionKind::InflationRewards,
1888 &wrong_signers,
1889 false,
1890 ),
1891 Err(InstructionError::MissingRequiredSignature)
1892 );
1893
1894 let mut commission_bps_roundtrip = |new_commission_bps: u16| {
1895 update_commission_bps(
1896 &mut borrowed_account,
1897 target_version,
1898 new_commission_bps,
1899 CommissionKind::InflationRewards,
1900 &signers,
1901 false,
1902 )
1903 .unwrap();
1904 update_commission_bps(
1905 &mut borrowed_account,
1906 target_version,
1907 new_commission_bps,
1908 CommissionKind::BlockRevenue,
1909 &signers,
1910 true,
1911 )
1912 .unwrap();
1913 let handler =
1914 get_vote_state_handler_checked(&borrowed_account, target_version).unwrap();
1915 assert_eq!(
1916 handler.as_ref_v4().inflation_rewards_commission_bps,
1917 new_commission_bps
1918 );
1919 assert_eq!(
1920 handler.as_ref_v4().block_revenue_commission_bps,
1921 new_commission_bps
1922 );
1923 };
1924
1925 commission_bps_roundtrip(1_100); commission_bps_roundtrip(5_000); commission_bps_roundtrip(4_400); commission_bps_roundtrip(4_600); commission_bps_roundtrip(15_000); commission_bps_roundtrip(50_000); }
1937
1938 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1939 fn test_vote_double_lockout_after_expiration(target_version: VoteStateTargetVersion) {
1940 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1941
1942 for i in 0..3 {
1943 process_slot_vote_unchecked(&mut vote_state, i as u64);
1944 }
1945
1946 check_lockouts(&vote_state);
1947
1948 process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
1952 check_lockouts(&vote_state);
1953
1954 process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
1957 check_lockouts(&vote_state);
1958
1959 process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
1962 check_lockouts(&vote_state);
1963 }
1964
1965 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1966 fn test_expire_multiple_votes(target_version: VoteStateTargetVersion) {
1967 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1968
1969 for i in 0..3 {
1970 process_slot_vote_unchecked(&mut vote_state, i as u64);
1971 }
1972
1973 assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1974
1975 let expire_slot =
1977 vote_state.votes()[1].slot() + vote_state.votes()[1].lockout.lockout() + 1;
1978 process_slot_vote_unchecked(&mut vote_state, expire_slot);
1979 assert_eq!(vote_state.votes().len(), 2);
1980
1981 assert_eq!(vote_state.votes()[0].slot(), 0);
1983 assert_eq!(vote_state.votes()[1].slot(), expire_slot);
1984
1985 process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);
1987
1988 assert_eq!(vote_state.votes()[0].confirmation_count(), 3);
1990
1991 assert_eq!(vote_state.votes()[1].confirmation_count(), 2);
1993 assert_eq!(vote_state.votes()[2].confirmation_count(), 1);
1994 }
1995
1996 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
1997 fn test_vote_credits(target_version: VoteStateTargetVersion) {
1998 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
1999
2000 for i in 0..MAX_LOCKOUT_HISTORY {
2001 process_slot_vote_unchecked(&mut vote_state, i as u64);
2002 }
2003
2004 assert_eq!(vote_state.credits(), 0);
2005
2006 process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
2007 assert_eq!(vote_state.credits(), 1);
2008 process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
2009 assert_eq!(vote_state.credits(), 2);
2010 process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
2011 assert_eq!(vote_state.credits(), 3);
2012 }
2013
2014 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2015 fn test_duplicate_vote(target_version: VoteStateTargetVersion) {
2016 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2017 process_slot_vote_unchecked(&mut vote_state, 0);
2018 process_slot_vote_unchecked(&mut vote_state, 1);
2019 process_slot_vote_unchecked(&mut vote_state, 0);
2020 assert_eq!(vote_state.nth_recent_lockout(0).unwrap().slot(), 1);
2021 assert_eq!(vote_state.nth_recent_lockout(1).unwrap().slot(), 0);
2022 assert!(vote_state.nth_recent_lockout(2).is_none());
2023 }
2024
2025 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2026 fn test_nth_recent_lockout(target_version: VoteStateTargetVersion) {
2027 let mut vote_state = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2028 for i in 0..MAX_LOCKOUT_HISTORY {
2029 process_slot_vote_unchecked(&mut vote_state, i as u64);
2030 }
2031 for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
2032 assert_eq!(
2033 vote_state.nth_recent_lockout(i).unwrap().slot() as usize,
2034 MAX_LOCKOUT_HISTORY - i - 1,
2035 );
2036 }
2037 assert!(vote_state.nth_recent_lockout(MAX_LOCKOUT_HISTORY).is_none());
2038 }
2039
2040 fn check_lockouts(vote_state: &VoteStateHandler) {
2041 let votes = vote_state.votes();
2042 for (i, vote) in votes.iter().enumerate() {
2043 let num_votes = votes
2044 .len()
2045 .checked_sub(i)
2046 .expect("`i` is less than `vote_state.votes().len()`");
2047 assert_eq!(
2048 vote.lockout.lockout(),
2049 INITIAL_LOCKOUT.pow(num_votes as u32) as u64
2050 );
2051 }
2052 }
2053
2054 fn recent_votes(vote_state: &VoteStateHandler) -> Vec<Vote> {
2055 let votes = vote_state.votes();
2056 let start = votes.len().saturating_sub(MAX_RECENT_VOTES);
2057 (start..votes.len())
2058 .map(|i| Vote::new(vec![votes.get(i).unwrap().slot()], Hash::default()))
2059 .collect()
2060 }
2061
2062 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2064 fn test_process_missed_votes(target_version: VoteStateTargetVersion) {
2065 let mut vote_state_a = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2066 let mut vote_state_b = vote_state_new_for_test(&solana_pubkey::new_rand(), target_version);
2067
2068 (0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
2070 assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
2071
2072 let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
2074 let vote = Vote::new(slots, Hash::default());
2075 let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2076
2077 assert_eq!(
2078 process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0),
2079 Ok(())
2080 );
2081 assert_eq!(
2082 process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0),
2083 Ok(())
2084 );
2085 assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
2086 }
2087
2088 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2089 fn test_process_vote_skips_old_vote(mut vote_state: VoteStateHandler) {
2090 let vote = Vote::new(vec![0], Hash::default());
2091 let slot_hashes: Vec<_> = vec![(0, vote.hash)];
2092 assert_eq!(
2093 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2094 Ok(())
2095 );
2096 let recent = recent_votes(&vote_state);
2097 assert_eq!(
2098 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2099 Err(VoteError::VoteTooOld)
2100 );
2101 assert_eq!(recent, recent_votes(&vote_state));
2102 }
2103
2104 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2105 fn test_check_slots_are_valid_vote_empty_slot_hashes(vote_state: VoteStateHandler) {
2106 let vote = Vote::new(vec![0], Hash::default());
2107 assert_eq!(
2108 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
2109 Err(VoteError::VoteTooOld)
2110 );
2111 }
2112
2113 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2114 fn test_check_slots_are_valid_new_vote(vote_state: VoteStateHandler) {
2115 let vote = Vote::new(vec![0], Hash::default());
2116 let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2117 assert_eq!(
2118 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2119 Ok(())
2120 );
2121 }
2122
2123 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2124 fn test_check_slots_are_valid_bad_hash(vote_state: VoteStateHandler) {
2125 let vote = Vote::new(vec![0], Hash::default());
2126 let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
2127 assert_eq!(
2128 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2129 Err(VoteError::SlotHashMismatch)
2130 );
2131 }
2132
2133 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2134 fn test_check_slots_are_valid_bad_slot(vote_state: VoteStateHandler) {
2135 let vote = Vote::new(vec![1], Hash::default());
2136 let slot_hashes: Vec<_> = vec![(0, vote.hash)];
2137 assert_eq!(
2138 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2139 Err(VoteError::SlotsMismatch)
2140 );
2141 }
2142
2143 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2144 fn test_check_slots_are_valid_duplicate_vote(mut vote_state: VoteStateHandler) {
2145 let vote = Vote::new(vec![0], Hash::default());
2146 let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2147 assert_eq!(
2148 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2149 Ok(())
2150 );
2151 assert_eq!(
2152 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2153 Err(VoteError::VoteTooOld)
2154 );
2155 }
2156
2157 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2158 fn test_check_slots_are_valid_next_vote(mut vote_state: VoteStateHandler) {
2159 let vote = Vote::new(vec![0], Hash::default());
2160 let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2161 assert_eq!(
2162 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2163 Ok(())
2164 );
2165
2166 let vote = Vote::new(vec![0, 1], Hash::default());
2167 let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
2168 assert_eq!(
2169 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2170 Ok(())
2171 );
2172 }
2173
2174 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2175 fn test_check_slots_are_valid_next_vote_only(mut vote_state: VoteStateHandler) {
2176 let vote = Vote::new(vec![0], Hash::default());
2177 let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
2178 assert_eq!(
2179 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
2180 Ok(())
2181 );
2182
2183 let vote = Vote::new(vec![1], Hash::default());
2184 let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
2185 assert_eq!(
2186 check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
2187 Ok(())
2188 );
2189 }
2190
2191 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2192 fn test_process_vote_empty_slots(mut vote_state: VoteStateHandler) {
2193 let vote = Vote::new(vec![], Hash::default());
2194 assert_eq!(
2195 process_vote(&mut vote_state, &vote, &[], 0, 0),
2196 Err(VoteError::EmptySlots)
2197 );
2198 }
2199
2200 pub fn process_new_vote_state_from_lockouts(
2201 vote_state: &mut VoteStateHandler,
2202 new_state: VecDeque<Lockout>,
2203 new_root: Option<Slot>,
2204 timestamp: Option<i64>,
2205 epoch: Epoch,
2206 ) -> Result<(), VoteError> {
2207 process_new_vote_state(
2208 vote_state,
2209 new_state.into_iter().map(LandedVote::from).collect(),
2210 new_root,
2211 timestamp,
2212 epoch,
2213 0,
2214 )
2215 }
2216
2217 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2219 fn test_vote_state_update_increment_credits(mut vote_state: VoteStateHandler) {
2220 let test_vote_groups: Vec<Vec<Slot>> = vec![
2223 vec![1, 2, 3, 4, 5, 6, 7, 8],
2225 vec![
2226 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
2227 30, 31,
2228 ],
2229 vec![32],
2231 vec![33],
2233 vec![34, 35],
2235 vec![36, 37, 38],
2237 vec![
2239 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
2240 60, 61, 62, 63, 64, 65, 66, 67, 68,
2241 ],
2242 vec![
2244 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
2245 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2246 ],
2247 vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
2249 vec![200, 201],
2251 vec![
2252 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2253 218, 219, 220, 221, 222, 223, 224, 225, 226,
2254 ],
2255 vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2256 ];
2257
2258 for vote_group in test_vote_groups {
2259 let mut vote_state_after_vote = vote_state.clone();
2261
2262 process_vote_unchecked(
2263 &mut vote_state_after_vote,
2264 Vote {
2265 slots: vote_group.clone(),
2266 hash: Hash::new_unique(),
2267 timestamp: None,
2268 },
2269 )
2270 .unwrap();
2271
2272 assert_eq!(
2274 process_new_vote_state(
2275 &mut vote_state,
2276 vote_state_after_vote.votes().clone(),
2277 vote_state_after_vote.root_slot(),
2278 None,
2279 0,
2280 0,
2281 ),
2282 Ok(())
2283 );
2284
2285 assert_eq!(
2287 vote_state.epoch_credits(),
2288 vote_state_after_vote.epoch_credits()
2289 );
2290 }
2291 }
2292
2293 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
2295 fn test_timely_credits(target_version: VoteStateTargetVersion) {
2296 let test_vote_groups: Vec<(Vec<Slot>, Slot, u32)> = vec![
2300 (
2302 vec![1, 2, 3, 4, 5, 6, 7, 8],
2303 9,
2304 0,
2306 ),
2307 (
2308 vec![
2309 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
2310 29, 30, 31,
2311 ],
2312 34,
2313 0,
2316 ),
2317 (
2319 vec![32],
2320 35,
2321 10,
2324 ),
2325 (
2327 vec![33],
2328 36,
2329 10 + 11, ),
2333 (
2335 vec![34, 35],
2336 37,
2337 21 + 12 + 13, ),
2341 (
2343 vec![36, 37, 38],
2344 39,
2345 46 + 14 + 15 + 16, ),
2349 (
2350 vec![
2352 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
2353 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
2354 ],
2355 69,
2356 91 + 16
2366 + 9 + 2
2368 + 3
2369 + 4
2370 + 5
2371 + 6
2372 + 7
2373 + 8
2374 + 9
2375 + 10
2376 + 11
2377 + 12
2378 + 13
2379 + 14
2380 + 15
2381 + 15
2382 + 15
2383 + 15
2384 + 16
2385 + 15
2386 + 16, ),
2388 (
2390 vec![
2391 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
2392 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
2393 ],
2394 100,
2395 327 + 16
2402 + 14 + 2
2404 + 3
2405 + 4
2406 + 5
2407 + 6
2408 + 7
2409 + 8
2410 + 9
2411 + 10
2412 + 11
2413 + 12
2414 + 13
2415 + 14
2416 + 15
2417 + 16
2418 + 16, ),
2420 (
2422 vec![115, 116, 117, 118, 119, 120, 121, 122, 123, 124],
2423 130,
2424 508 + ((74 - 69) + 1), ),
2429 (
2431 vec![200, 201],
2432 202,
2433 514,
2436 ),
2437 (
2438 vec![
2439 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
2440 218, 219, 220, 221, 222, 223, 224, 225, 226,
2441 ],
2442 227,
2443 514 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13, ),
2449 (
2450 vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
2451 237,
2452 613 + 3 + 4 + 5 + 6 + 16 + 16 + 1 + 1 + 1 + 1, ),
2458 ];
2459
2460 let new_vote_state = || match target_version {
2461 VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
2462 };
2463
2464 for i in 0..test_vote_groups.len() {
2467 let mut vote_state_1 = new_vote_state();
2469 let mut vote_state_2 = new_vote_state();
2471 test_vote_groups.iter().take(i + 1).for_each(|vote_group| {
2472 let vote = Vote {
2473 slots: vote_group.0.clone(), hash: Hash::new_unique(),
2475 timestamp: None,
2476 };
2477 let slot_hashes: Vec<_> =
2478 vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
2479 assert_eq!(
2480 process_vote(
2481 &mut vote_state_1,
2482 &vote,
2483 &slot_hashes,
2484 0,
2485 vote_group.1, ),
2487 Ok(())
2488 );
2489
2490 assert_eq!(
2491 process_new_vote_state(
2492 &mut vote_state_2,
2493 vote_state_1.votes().clone(),
2494 vote_state_1.root_slot(),
2495 None,
2496 0,
2497 vote_group.1, ),
2499 Ok(())
2500 );
2501 });
2502
2503 let vote_group = &test_vote_groups[i];
2505 assert_eq!(vote_state_1.credits(), vote_group.2 as u64); assert_eq!(vote_state_2.credits(), vote_group.2 as u64); }
2508 }
2509
2510 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2511 fn test_retroactive_voting_timely_credits(mut vote_state: VoteStateHandler) {
2512 #[allow(clippy::type_complexity)]
2518 let test_vote_state_updates: Vec<(Vec<(Slot, u32)>, Slot, Option<Slot>, u32)> = vec![
2519 (
2521 vec![(7, 4), (8, 3), (9, 2), (10, 1)],
2522 11,
2523 None,
2525 0,
2527 ),
2528 (
2530 vec![
2531 (1, 10),
2532 (2, 9),
2533 (3, 8),
2534 (4, 7),
2535 (5, 6),
2536 (6, 5),
2537 (7, 4),
2538 (8, 3),
2539 (9, 2),
2540 (10, 1),
2541 ],
2542 12,
2543 None,
2545 0,
2547 ),
2548 (
2550 vec![
2551 (11, 31),
2552 (12, 30),
2553 (13, 29),
2554 (14, 28),
2555 (15, 27),
2556 (16, 26),
2557 (17, 25),
2558 (18, 24),
2559 (19, 23),
2560 (20, 22),
2561 (21, 21),
2562 (22, 20),
2563 (23, 19),
2564 (24, 18),
2565 (25, 17),
2566 (26, 16),
2567 (27, 15),
2568 (28, 14),
2569 (29, 13),
2570 (30, 12),
2571 (31, 11),
2572 (32, 10),
2573 (33, 9),
2574 (34, 8),
2575 (35, 7),
2576 (36, 6),
2577 (37, 5),
2578 (38, 4),
2579 (39, 3),
2580 (40, 2),
2581 (41, 1),
2582 ],
2583 42,
2584 Some(10),
2586 7 + 8 + 9 + 10 + 11 + 12 + 14 + 15 + 16 + 16,
2589 ),
2590 ];
2591
2592 test_vote_state_updates
2595 .iter()
2596 .for_each(|proposed_vote_state| {
2597 let new_state = proposed_vote_state
2598 .0 .iter()
2600 .map(|(slot, confirmation_count)| LandedVote {
2601 latency: 0,
2602 lockout: Lockout::new_with_confirmation_count(*slot, *confirmation_count),
2603 })
2604 .collect::<VecDeque<LandedVote>>();
2605 assert_eq!(
2606 process_new_vote_state(
2607 &mut vote_state,
2608 new_state,
2609 proposed_vote_state.2, None,
2611 0,
2612 proposed_vote_state.1, ),
2614 Ok(())
2615 );
2616
2617 assert_eq!(vote_state.credits(), proposed_vote_state.3 as u64);
2619 });
2620 }
2621
2622 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2623 fn test_process_new_vote_too_many_votes(mut vote_state1: VoteStateHandler) {
2624 let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
2625 .map(|slot| {
2626 Lockout::new_with_confirmation_count(
2627 slot as Slot,
2628 (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
2629 )
2630 })
2631 .collect();
2632
2633 let current_epoch = vote_state1.current_epoch();
2634 assert_eq!(
2635 process_new_vote_state_from_lockouts(
2636 &mut vote_state1,
2637 bad_votes,
2638 None,
2639 None,
2640 current_epoch,
2641 ),
2642 Err(VoteError::TooManyVotes)
2643 );
2644 }
2645
2646 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2647 fn test_process_new_vote_state_root_rollback(mut vote_state1: VoteStateHandler) {
2648 for i in 0..MAX_LOCKOUT_HISTORY + 2 {
2649 process_slot_vote_unchecked(&mut vote_state1, i as Slot);
2650 }
2651 assert_eq!(vote_state1.root_slot().unwrap(), 1);
2652
2653 let mut vote_state2 = vote_state1.clone();
2656 process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);
2657
2658 let lesser_root = Some(0);
2660
2661 let current_epoch = vote_state2.current_epoch();
2662 assert_eq!(
2663 process_new_vote_state(
2664 &mut vote_state1,
2665 vote_state2.votes().clone(),
2666 lesser_root,
2667 None,
2668 current_epoch,
2669 0,
2670 ),
2671 Err(VoteError::RootRollBack)
2672 );
2673
2674 let none_root = None;
2676 assert_eq!(
2677 process_new_vote_state(
2678 &mut vote_state1,
2679 vote_state2.votes().clone(),
2680 none_root,
2681 None,
2682 current_epoch,
2683 0,
2684 ),
2685 Err(VoteError::RootRollBack)
2686 );
2687 }
2688
2689 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2690 fn test_process_new_vote_state_zero_confirmations(mut vote_state1: VoteStateHandler) {
2691 let current_epoch = vote_state1.current_epoch();
2692
2693 let bad_votes: VecDeque<Lockout> = vec![
2694 Lockout::new_with_confirmation_count(0, 0),
2695 Lockout::new_with_confirmation_count(1, 1),
2696 ]
2697 .into_iter()
2698 .collect();
2699 assert_eq!(
2700 process_new_vote_state_from_lockouts(
2701 &mut vote_state1,
2702 bad_votes,
2703 None,
2704 None,
2705 current_epoch,
2706 ),
2707 Err(VoteError::ZeroConfirmations)
2708 );
2709
2710 let bad_votes: VecDeque<Lockout> = vec![
2711 Lockout::new_with_confirmation_count(0, 2),
2712 Lockout::new_with_confirmation_count(1, 0),
2713 ]
2714 .into_iter()
2715 .collect();
2716 assert_eq!(
2717 process_new_vote_state_from_lockouts(
2718 &mut vote_state1,
2719 bad_votes,
2720 None,
2721 None,
2722 current_epoch,
2723 ),
2724 Err(VoteError::ZeroConfirmations)
2725 );
2726 }
2727
2728 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2729 fn test_process_new_vote_state_confirmations_too_large(initial_vote_state: VoteStateHandler) {
2730 let mut vote_state1 = initial_vote_state.clone();
2731 let current_epoch = vote_state1.current_epoch();
2732
2733 let good_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2734 0,
2735 MAX_LOCKOUT_HISTORY as u32,
2736 )]
2737 .into_iter()
2738 .collect();
2739
2740 process_new_vote_state_from_lockouts(
2741 &mut vote_state1,
2742 good_votes,
2743 None,
2744 None,
2745 current_epoch,
2746 )
2747 .unwrap();
2748
2749 let mut vote_state1 = initial_vote_state;
2750 let bad_votes: VecDeque<Lockout> = vec![Lockout::new_with_confirmation_count(
2751 0,
2752 MAX_LOCKOUT_HISTORY as u32 + 1,
2753 )]
2754 .into_iter()
2755 .collect();
2756 assert_eq!(
2757 process_new_vote_state_from_lockouts(
2758 &mut vote_state1,
2759 bad_votes,
2760 None,
2761 None,
2762 current_epoch,
2763 ),
2764 Err(VoteError::ConfirmationTooLarge)
2765 );
2766 }
2767
2768 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2769 fn test_process_new_vote_state_slot_smaller_than_root(mut vote_state1: VoteStateHandler) {
2770 let current_epoch = vote_state1.current_epoch();
2771 let root_slot = 5;
2772
2773 let bad_votes: VecDeque<Lockout> = vec![
2774 Lockout::new_with_confirmation_count(root_slot, 2),
2775 Lockout::new_with_confirmation_count(root_slot + 1, 1),
2776 ]
2777 .into_iter()
2778 .collect();
2779 assert_eq!(
2780 process_new_vote_state_from_lockouts(
2781 &mut vote_state1,
2782 bad_votes,
2783 Some(root_slot),
2784 None,
2785 current_epoch,
2786 ),
2787 Err(VoteError::SlotSmallerThanRoot)
2788 );
2789
2790 let bad_votes: VecDeque<Lockout> = vec![
2791 Lockout::new_with_confirmation_count(root_slot - 1, 2),
2792 Lockout::new_with_confirmation_count(root_slot + 1, 1),
2793 ]
2794 .into_iter()
2795 .collect();
2796 assert_eq!(
2797 process_new_vote_state_from_lockouts(
2798 &mut vote_state1,
2799 bad_votes,
2800 Some(root_slot),
2801 None,
2802 current_epoch,
2803 ),
2804 Err(VoteError::SlotSmallerThanRoot)
2805 );
2806 }
2807
2808 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2809 fn test_process_new_vote_state_slots_not_ordered(mut vote_state1: VoteStateHandler) {
2810 let current_epoch = vote_state1.current_epoch();
2811
2812 let bad_votes: VecDeque<Lockout> = vec![
2813 Lockout::new_with_confirmation_count(1, 2),
2814 Lockout::new_with_confirmation_count(0, 1),
2815 ]
2816 .into_iter()
2817 .collect();
2818 assert_eq!(
2819 process_new_vote_state_from_lockouts(
2820 &mut vote_state1,
2821 bad_votes,
2822 None,
2823 None,
2824 current_epoch,
2825 ),
2826 Err(VoteError::SlotsNotOrdered)
2827 );
2828
2829 let bad_votes: VecDeque<Lockout> = vec![
2830 Lockout::new_with_confirmation_count(1, 2),
2831 Lockout::new_with_confirmation_count(1, 1),
2832 ]
2833 .into_iter()
2834 .collect();
2835 assert_eq!(
2836 process_new_vote_state_from_lockouts(
2837 &mut vote_state1,
2838 bad_votes,
2839 None,
2840 None,
2841 current_epoch,
2842 ),
2843 Err(VoteError::SlotsNotOrdered)
2844 );
2845 }
2846
2847 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2848 fn test_process_new_vote_state_confirmations_not_ordered(mut vote_state1: VoteStateHandler) {
2849 let current_epoch = vote_state1.current_epoch();
2850
2851 let bad_votes: VecDeque<Lockout> = vec![
2852 Lockout::new_with_confirmation_count(0, 1),
2853 Lockout::new_with_confirmation_count(1, 2),
2854 ]
2855 .into_iter()
2856 .collect();
2857 assert_eq!(
2858 process_new_vote_state_from_lockouts(
2859 &mut vote_state1,
2860 bad_votes,
2861 None,
2862 None,
2863 current_epoch,
2864 ),
2865 Err(VoteError::ConfirmationsNotOrdered)
2866 );
2867
2868 let bad_votes: VecDeque<Lockout> = vec![
2869 Lockout::new_with_confirmation_count(0, 1),
2870 Lockout::new_with_confirmation_count(1, 1),
2871 ]
2872 .into_iter()
2873 .collect();
2874 assert_eq!(
2875 process_new_vote_state_from_lockouts(
2876 &mut vote_state1,
2877 bad_votes,
2878 None,
2879 None,
2880 current_epoch,
2881 ),
2882 Err(VoteError::ConfirmationsNotOrdered)
2883 );
2884 }
2885
2886 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2887 fn test_process_new_vote_state_new_vote_state_lockout_mismatch(
2888 mut vote_state1: VoteStateHandler,
2889 ) {
2890 let current_epoch = vote_state1.current_epoch();
2891
2892 let bad_votes: VecDeque<Lockout> = vec![
2893 Lockout::new_with_confirmation_count(0, 2),
2894 Lockout::new_with_confirmation_count(7, 1),
2895 ]
2896 .into_iter()
2897 .collect();
2898
2899 assert_eq!(
2901 process_new_vote_state_from_lockouts(
2902 &mut vote_state1,
2903 bad_votes,
2904 None,
2905 None,
2906 current_epoch,
2907 ),
2908 Err(VoteError::NewVoteStateLockoutMismatch)
2909 );
2910 }
2911
2912 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2913 fn test_process_new_vote_state_confirmation_rollback(mut vote_state1: VoteStateHandler) {
2914 let current_epoch = vote_state1.current_epoch();
2915 let votes: VecDeque<Lockout> = vec![
2916 Lockout::new_with_confirmation_count(0, 4),
2917 Lockout::new_with_confirmation_count(1, 3),
2918 ]
2919 .into_iter()
2920 .collect();
2921 process_new_vote_state_from_lockouts(&mut vote_state1, votes, None, None, current_epoch)
2922 .unwrap();
2923
2924 let votes: VecDeque<Lockout> = vec![
2925 Lockout::new_with_confirmation_count(0, 4),
2926 Lockout::new_with_confirmation_count(1, 2),
2928 Lockout::new_with_confirmation_count(2, 1),
2929 ]
2930 .into_iter()
2931 .collect();
2932 assert_eq!(
2935 process_new_vote_state_from_lockouts(
2936 &mut vote_state1,
2937 votes,
2938 None,
2939 None,
2940 current_epoch,
2941 ),
2942 Err(VoteError::ConfirmationRollBack)
2943 );
2944 }
2945
2946 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2947 fn test_process_new_vote_state_root_progress(mut vote_state1: VoteStateHandler) {
2948 for i in 0..MAX_LOCKOUT_HISTORY {
2949 process_slot_vote_unchecked(&mut vote_state1, i as u64);
2950 }
2951
2952 assert!(vote_state1.root_slot().is_none());
2953 let mut vote_state2 = vote_state1.clone();
2954
2955 for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
2962 process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
2963 assert_ne!(vote_state1.root_slot(), vote_state2.root_slot());
2964
2965 process_new_vote_state(
2966 &mut vote_state1,
2967 vote_state2.votes().clone(),
2968 vote_state2.root_slot(),
2969 None,
2970 vote_state2.current_epoch(),
2971 0,
2972 )
2973 .unwrap();
2974
2975 assert_eq!(vote_state1, vote_state2);
2976 }
2977 }
2978
2979 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
2980 fn test_process_new_vote_state_same_slot_but_not_common_ancestor(
2981 initial_vote_state: VoteStateHandler,
2982 ) {
2983 let mut vote_state1 = initial_vote_state.clone();
3002 process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
3003 assert_eq!(
3004 vote_state1
3005 .votes()
3006 .iter()
3007 .map(|vote| vote.slot())
3008 .collect::<Vec<Slot>>(),
3009 vec![1, 5]
3010 );
3011
3012 let mut vote_state2 = initial_vote_state;
3014 process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
3015 assert_eq!(
3016 vote_state2
3017 .votes()
3018 .iter()
3019 .map(|vote| vote.slot())
3020 .collect::<Vec<Slot>>(),
3021 vec![1, 2, 3, 5, 7]
3022 );
3023
3024 process_new_vote_state(
3026 &mut vote_state1,
3027 vote_state2.votes().clone(),
3028 vote_state2.root_slot(),
3029 None,
3030 vote_state2.current_epoch(),
3031 0,
3032 )
3033 .unwrap();
3034
3035 assert_eq!(vote_state1, vote_state2);
3036 }
3037
3038 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3039 fn test_process_new_vote_state_lockout_violation(initial_vote_state: VoteStateHandler) {
3040 let mut vote_state1 = initial_vote_state.clone();
3042 process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
3043 assert_eq!(
3044 vote_state1
3045 .votes()
3046 .iter()
3047 .map(|vote| vote.slot())
3048 .collect::<Vec<Slot>>(),
3049 vec![1, 2, 4, 5]
3050 );
3051
3052 let mut vote_state2 = initial_vote_state;
3055 process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
3056 assert_eq!(
3057 vote_state2
3058 .votes()
3059 .iter()
3060 .map(|vote| vote.slot())
3061 .collect::<Vec<Slot>>(),
3062 vec![1, 2, 3, 5, 7]
3063 );
3064
3065 assert_eq!(
3067 process_new_vote_state(
3068 &mut vote_state1,
3069 vote_state2.votes().clone(),
3070 vote_state2.root_slot(),
3071 None,
3072 vote_state2.current_epoch(),
3073 0,
3074 ),
3075 Err(VoteError::LockoutConflict)
3076 );
3077 }
3078
3079 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3080 fn test_process_new_vote_state_lockout_violation2(initial_vote_state: VoteStateHandler) {
3081 let mut vote_state1 = initial_vote_state.clone();
3083 process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
3084 assert_eq!(
3085 vote_state1
3086 .votes()
3087 .iter()
3088 .map(|vote| vote.slot())
3089 .collect::<Vec<Slot>>(),
3090 vec![1, 5, 6, 7]
3091 );
3092
3093 let mut vote_state2 = initial_vote_state;
3096 process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
3097 assert_eq!(
3098 vote_state2
3099 .votes()
3100 .iter()
3101 .map(|vote| vote.slot())
3102 .collect::<Vec<Slot>>(),
3103 vec![1, 2, 3, 5, 6, 8]
3104 );
3105
3106 assert_eq!(
3109 process_new_vote_state(
3110 &mut vote_state1,
3111 vote_state2.votes().clone(),
3112 vote_state2.root_slot(),
3113 None,
3114 vote_state2.current_epoch(),
3115 0,
3116 ),
3117 Err(VoteError::LockoutConflict)
3118 );
3119 }
3120
3121 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3122 fn test_process_new_vote_state_expired_ancestor_not_removed(mut vote_state1: VoteStateHandler) {
3123 process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
3125 assert_eq!(
3126 vote_state1
3127 .votes()
3128 .iter()
3129 .map(|vote| vote.slot())
3130 .collect::<Vec<Slot>>(),
3131 vec![1, 9]
3132 );
3133
3134 let mut vote_state2 = vote_state1.clone();
3137 process_slot_vote_unchecked(&mut vote_state2, 10);
3138
3139 assert_eq!(vote_state2.votes()[0].slot(), 1);
3142 assert_eq!(vote_state2.votes()[0].lockout.last_locked_out_slot(), 9);
3143 assert_eq!(
3144 vote_state2
3145 .votes()
3146 .iter()
3147 .map(|vote| vote.slot())
3148 .collect::<Vec<Slot>>(),
3149 vec![1, 9, 10]
3150 );
3151
3152 process_new_vote_state(
3154 &mut vote_state1,
3155 vote_state2.votes().clone(),
3156 vote_state2.root_slot(),
3157 None,
3158 vote_state2.current_epoch(),
3159 0,
3160 )
3161 .unwrap();
3162 assert_eq!(vote_state1, vote_state2,);
3163 }
3164
3165 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3166 fn test_process_new_vote_current_state_contains_bigger_slots(
3167 mut vote_state1: VoteStateHandler,
3168 ) {
3169 process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
3170 assert_eq!(
3171 vote_state1
3172 .votes()
3173 .iter()
3174 .map(|vote| vote.slot())
3175 .collect::<Vec<Slot>>(),
3176 vec![6, 7, 8]
3177 );
3178
3179 let bad_votes: VecDeque<Lockout> = vec![
3181 Lockout::new_with_confirmation_count(2, 5),
3182 Lockout::new_with_confirmation_count(14, 1),
3184 ]
3185 .into_iter()
3186 .collect();
3187 let root = Some(1);
3188
3189 let current_epoch = vote_state1.current_epoch();
3190 assert_eq!(
3191 process_new_vote_state_from_lockouts(
3192 &mut vote_state1,
3193 bad_votes,
3194 root,
3195 None,
3196 current_epoch,
3197 ),
3198 Err(VoteError::LockoutConflict)
3199 );
3200
3201 let good_votes: VecDeque<LandedVote> = vec![
3202 Lockout::new_with_confirmation_count(2, 5).into(),
3203 Lockout::new_with_confirmation_count(15, 1).into(),
3204 ]
3205 .into_iter()
3206 .collect();
3207
3208 let current_epoch = vote_state1.current_epoch();
3209 process_new_vote_state(
3210 &mut vote_state1,
3211 good_votes.clone(),
3212 root,
3213 None,
3214 current_epoch,
3215 0,
3216 )
3217 .unwrap();
3218 assert_eq!(*vote_state1.votes(), good_votes);
3219 }
3220
3221 #[test_case(VoteStateHandler::default_v4() ; "VoteStateV4")]
3222 fn test_filter_old_votes(mut vote_state: VoteStateHandler) {
3223 let old_vote_slot = 1;
3224 let vote = Vote::new(vec![old_vote_slot], Hash::default());
3225
3226 let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
3229 assert_eq!(
3230 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0),
3231 Err(VoteError::VotesTooOldAllFiltered)
3232 );
3233
3234 let vote_slot = 2;
3237 let vote_slot_hash = slot_hashes
3238 .iter()
3239 .find(|(slot, _hash)| *slot == vote_slot)
3240 .unwrap()
3241 .1;
3242
3243 let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
3244 process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap();
3245 assert_eq!(
3246 vote_state
3247 .votes()
3248 .iter()
3249 .map(|vote| vote.lockout)
3250 .collect::<Vec<Lockout>>(),
3251 vec![Lockout::new_with_confirmation_count(vote_slot, 1)]
3252 );
3253 }
3254
3255 fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
3256 slots
3257 .iter()
3258 .rev()
3259 .map(|x| (*x, Hash::new_unique()))
3260 .collect()
3261 }
3262
3263 fn build_vote_state(
3264 target_version: VoteStateTargetVersion,
3265 vote_slots: Vec<Slot>,
3266 slot_hashes: &[(Slot, Hash)],
3267 ) -> VoteStateHandler {
3268 let mut vote_state = match target_version {
3269 VoteStateTargetVersion::V4 => VoteStateHandler::default_v4(),
3270 };
3271
3272 if !vote_slots.is_empty() {
3273 let vote_hash = slot_hashes
3274 .iter()
3275 .find(|(slot, _hash)| slot == vote_slots.last().unwrap())
3276 .unwrap()
3277 .1;
3278 let vote = Vote::new(vote_slots, vote_hash);
3279 process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0)
3280 .unwrap();
3281 }
3282
3283 vote_state
3284 }
3285
3286 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3287 fn test_check_and_filter_proposed_vote_state_empty(target_version: VoteStateTargetVersion) {
3288 let empty_slot_hashes = build_slot_hashes(vec![]);
3289 let empty_vote_state = build_vote_state(target_version, vec![], &empty_slot_hashes);
3290
3291 let mut tower_sync = TowerSync::from(vec![]);
3293 assert_eq!(
3294 check_and_filter_proposed_vote_state(
3295 &empty_vote_state,
3296 &mut tower_sync.lockouts,
3297 &mut tower_sync.root,
3298 tower_sync.hash,
3299 &empty_slot_hashes
3300 ),
3301 Err(VoteError::EmptySlots),
3302 );
3303
3304 let mut tower_sync = TowerSync::from(vec![(0, 1)]);
3306 assert_eq!(
3307 check_and_filter_proposed_vote_state(
3308 &empty_vote_state,
3309 &mut tower_sync.lockouts,
3310 &mut tower_sync.root,
3311 tower_sync.hash,
3312 &empty_slot_hashes
3313 ),
3314 Err(VoteError::SlotsMismatch),
3315 );
3316 }
3317
3318 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3319 fn test_check_and_filter_proposed_vote_state_too_old(target_version: VoteStateTargetVersion) {
3320 let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3321 let latest_vote = 4;
3322 let vote_state = build_vote_state(target_version, vec![1, 2, 3, latest_vote], &slot_hashes);
3323
3324 let mut tower_sync = TowerSync::from(vec![(latest_vote, 1)]);
3327 assert_eq!(
3328 check_and_filter_proposed_vote_state(
3329 &vote_state,
3330 &mut tower_sync.lockouts,
3331 &mut tower_sync.root,
3332 tower_sync.hash,
3333 &slot_hashes
3334 ),
3335 Err(VoteError::VoteTooOld),
3336 );
3337
3338 let earliest_slot_in_history = latest_vote + 2;
3342 let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
3343 let mut tower_sync = TowerSync::from(vec![(earliest_slot_in_history - 1, 1)]);
3344 assert_eq!(
3345 check_and_filter_proposed_vote_state(
3346 &vote_state,
3347 &mut tower_sync.lockouts,
3348 &mut tower_sync.root,
3349 tower_sync.hash,
3350 &slot_hashes
3351 ),
3352 Err(VoteError::VoteTooOld),
3353 );
3354 }
3355
3356 fn run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3357 target_version: VoteStateTargetVersion,
3358 earliest_slot_in_history: Slot,
3359 current_vote_state_slots: Vec<Slot>,
3360 current_vote_state_root: Option<Slot>,
3361 proposed_slots_and_lockouts: Vec<(Slot, u32)>,
3362 proposed_root: Slot,
3363 expected_root: Option<Slot>,
3364 expected_vote_state: Vec<Lockout>,
3365 ) {
3366 assert!(proposed_root < earliest_slot_in_history);
3367 assert_eq!(
3368 expected_root,
3369 current_vote_state_slots
3370 .iter()
3371 .rev()
3372 .find(|slot| **slot <= proposed_root)
3373 .cloned()
3374 );
3375 let latest_slot_in_history = proposed_slots_and_lockouts
3376 .last()
3377 .unwrap()
3378 .0
3379 .max(earliest_slot_in_history);
3380 let mut slot_hashes = build_slot_hashes(
3381 (current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
3382 .collect::<Vec<Slot>>(),
3383 );
3384
3385 let mut vote_state =
3386 build_vote_state(target_version, current_vote_state_slots, &slot_hashes);
3387 vote_state.set_root_slot(current_vote_state_root);
3388
3389 slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
3390 assert!(!proposed_slots_and_lockouts.is_empty());
3391 let proposed_hash = slot_hashes
3392 .iter()
3393 .find(|(slot, _hash)| *slot == proposed_slots_and_lockouts.last().unwrap().0)
3394 .unwrap()
3395 .1;
3396
3397 let mut tower_sync = TowerSync::from(proposed_slots_and_lockouts);
3401 tower_sync.hash = proposed_hash;
3402 tower_sync.root = Some(proposed_root);
3403 check_and_filter_proposed_vote_state(
3404 &vote_state,
3405 &mut tower_sync.lockouts,
3406 &mut tower_sync.root,
3407 tower_sync.hash,
3408 &slot_hashes,
3409 )
3410 .unwrap();
3411 assert_eq!(tower_sync.root, expected_root);
3412
3413 assert!(
3416 do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync.clone(),).is_ok()
3417 );
3418 assert_eq!(vote_state.root_slot(), expected_root);
3419 assert_eq!(
3420 vote_state
3421 .votes()
3422 .iter()
3423 .map(|vote| vote.lockout)
3424 .collect::<Vec<Lockout>>(),
3425 expected_vote_state,
3426 );
3427 }
3428
3429 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3430 fn test_check_and_filter_proposed_vote_state_older_than_history_root(
3431 target_version: VoteStateTargetVersion,
3432 ) {
3433 let earliest_slot_in_history = 5;
3436 let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3437 let current_vote_state_root = None;
3438 let proposed_slots_and_lockouts = vec![(5, 1)];
3439 let proposed_root = 4;
3440 let expected_root = Some(4);
3441 let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3442 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3443 target_version,
3444 earliest_slot_in_history,
3445 current_vote_state_slots,
3446 current_vote_state_root,
3447 proposed_slots_and_lockouts,
3448 proposed_root,
3449 expected_root,
3450 expected_vote_state,
3451 );
3452
3453 let earliest_slot_in_history = 5;
3456 let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3457 let current_vote_state_root = Some(0);
3458 let proposed_slots_and_lockouts = vec![(5, 1)];
3459 let proposed_root = 4;
3460 let expected_root = Some(4);
3461 let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3462 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3463 target_version,
3464 earliest_slot_in_history,
3465 current_vote_state_slots,
3466 current_vote_state_root,
3467 proposed_slots_and_lockouts,
3468 proposed_root,
3469 expected_root,
3470 expected_vote_state,
3471 );
3472
3473 let earliest_slot_in_history = 5;
3476 let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
3477 let current_vote_state_root = Some(0);
3478 let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3479 let proposed_root = 3;
3480 let expected_root = Some(3);
3481 let expected_vote_state = vec![
3482 Lockout::new_with_confirmation_count(4, 2),
3483 Lockout::new_with_confirmation_count(5, 1),
3484 ];
3485 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3486 target_version,
3487 earliest_slot_in_history,
3488 current_vote_state_slots,
3489 current_vote_state_root,
3490 proposed_slots_and_lockouts,
3491 proposed_root,
3492 expected_root,
3493 expected_vote_state,
3494 );
3495
3496 let earliest_slot_in_history = 5;
3498 let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
3499 let current_vote_state_root = Some(0);
3500 let proposed_slots_and_lockouts = vec![(4, 2), (5, 1)];
3501 let proposed_root = 3;
3502 let expected_root = Some(2);
3503 let expected_vote_state = vec![
3504 Lockout::new_with_confirmation_count(4, 2),
3505 Lockout::new_with_confirmation_count(5, 1),
3506 ];
3507 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3508 target_version,
3509 earliest_slot_in_history,
3510 current_vote_state_slots,
3511 current_vote_state_root,
3512 proposed_slots_and_lockouts,
3513 proposed_root,
3514 expected_root,
3515 expected_vote_state,
3516 );
3517
3518 let earliest_slot_in_history = 4;
3521 let current_vote_state_slots: Vec<Slot> = vec![3, 4];
3522 let current_vote_state_root = None;
3523 let proposed_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
3524 let proposed_root = 2;
3525 let expected_root = None;
3526 let expected_vote_state = vec![
3527 Lockout::new_with_confirmation_count(3, 3),
3528 Lockout::new_with_confirmation_count(4, 2),
3529 Lockout::new_with_confirmation_count(5, 1),
3530 ];
3531 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3532 target_version,
3533 earliest_slot_in_history,
3534 current_vote_state_slots,
3535 current_vote_state_root,
3536 proposed_slots_and_lockouts,
3537 proposed_root,
3538 expected_root,
3539 expected_vote_state,
3540 );
3541
3542 let earliest_slot_in_history = 4;
3544 let current_vote_state_slots: Vec<Slot> = vec![];
3545 let current_vote_state_root = None;
3546 let proposed_slots_and_lockouts = vec![(5, 1)];
3547 let proposed_root = 2;
3548 let expected_root = None;
3549 let expected_vote_state = vec![Lockout::new_with_confirmation_count(5, 1)];
3550 run_test_check_and_filter_proposed_vote_state_older_than_history_root(
3551 target_version,
3552 earliest_slot_in_history,
3553 current_vote_state_slots,
3554 current_vote_state_root,
3555 proposed_slots_and_lockouts,
3556 proposed_root,
3557 expected_root,
3558 expected_vote_state,
3559 );
3560 }
3561
3562 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3563 fn test_check_and_filter_proposed_vote_state_slots_not_ordered(
3564 target_version: VoteStateTargetVersion,
3565 ) {
3566 let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3567 let vote_state = build_vote_state(target_version, vec![1], &slot_hashes);
3568
3569 let vote_slot = 3;
3571 let vote_slot_hash = slot_hashes
3572 .iter()
3573 .find(|(slot, _hash)| *slot == vote_slot)
3574 .unwrap()
3575 .1;
3576 let mut tower_sync = TowerSync::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
3577 tower_sync.hash = vote_slot_hash;
3578 assert_eq!(
3579 check_and_filter_proposed_vote_state(
3580 &vote_state,
3581 &mut tower_sync.lockouts,
3582 &mut tower_sync.root,
3583 tower_sync.hash,
3584 &slot_hashes
3585 ),
3586 Err(VoteError::SlotsNotOrdered),
3587 );
3588
3589 let mut tower_sync = TowerSync::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
3591 tower_sync.hash = vote_slot_hash;
3592 assert_eq!(
3593 check_and_filter_proposed_vote_state(
3594 &vote_state,
3595 &mut tower_sync.lockouts,
3596 &mut tower_sync.root,
3597 tower_sync.hash,
3598 &slot_hashes
3599 ),
3600 Err(VoteError::SlotsNotOrdered),
3601 );
3602 }
3603
3604 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3605 fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered(
3606 target_version: VoteStateTargetVersion,
3607 ) {
3608 let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
3609 let mut vote_state = build_vote_state(target_version, vec![1, 2, 3, 4], &slot_hashes);
3610
3611 let earliest_slot_in_history = 11;
3616 let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3617 let vote_slot = 12;
3618 let vote_slot_hash = slot_hashes
3619 .iter()
3620 .find(|(slot, _hash)| *slot == vote_slot)
3621 .unwrap()
3622 .1;
3623 let missing_older_than_history_slot = earliest_slot_in_history - 1;
3624 let mut tower_sync = TowerSync::from(vec![
3625 (1, 4),
3626 (missing_older_than_history_slot, 2),
3627 (vote_slot, 3),
3628 ]);
3629 tower_sync.hash = vote_slot_hash;
3630 check_and_filter_proposed_vote_state(
3631 &vote_state,
3632 &mut tower_sync.lockouts,
3633 &mut tower_sync.root,
3634 tower_sync.hash,
3635 &slot_hashes,
3636 )
3637 .unwrap();
3638
3639 assert_eq!(
3641 tower_sync
3642 .clone()
3643 .lockouts
3644 .into_iter()
3645 .collect::<Vec<Lockout>>(),
3646 vec![
3647 Lockout::new_with_confirmation_count(1, 4),
3648 Lockout::new_with_confirmation_count(vote_slot, 3)
3649 ]
3650 );
3651 assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3652 }
3653
3654 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3655 fn test_check_and_filter_proposed_vote_state_older_than_history_slots_not_filtered(
3656 target_version: VoteStateTargetVersion,
3657 ) {
3658 let slot_hashes = build_slot_hashes(vec![4]);
3659 let mut vote_state = build_vote_state(target_version, vec![4], &slot_hashes);
3660
3661 let earliest_slot_in_history = 11;
3666 let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3667 let vote_slot = 12;
3668 let vote_slot_hash = slot_hashes
3669 .iter()
3670 .find(|(slot, _hash)| *slot == vote_slot)
3671 .unwrap()
3672 .1;
3673 let existing_older_than_history_slot = 4;
3674 let mut tower_sync =
3675 TowerSync::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
3676 tower_sync.hash = vote_slot_hash;
3677 check_and_filter_proposed_vote_state(
3678 &vote_state,
3679 &mut tower_sync.lockouts,
3680 &mut tower_sync.root,
3681 tower_sync.hash,
3682 &slot_hashes,
3683 )
3684 .unwrap();
3685 assert_eq!(tower_sync.lockouts.len(), 2);
3687 assert_eq!(
3688 tower_sync
3689 .clone()
3690 .lockouts
3691 .into_iter()
3692 .collect::<Vec<Lockout>>(),
3693 vec![
3694 Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3695 Lockout::new_with_confirmation_count(vote_slot, 2)
3696 ]
3697 );
3698 assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3699 }
3700
3701 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3702 fn test_check_and_filter_proposed_vote_state_older_than_history_slots_filtered_and_not_filtered(
3703 target_version: VoteStateTargetVersion,
3704 ) {
3705 let slot_hashes = build_slot_hashes(vec![6]);
3706 let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3707
3708 let earliest_slot_in_history = 11;
3719 let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
3720 let vote_slot = 14;
3721 let vote_slot_hash = slot_hashes
3722 .iter()
3723 .find(|(slot, _hash)| *slot == vote_slot)
3724 .unwrap()
3725 .1;
3726
3727 let missing_older_than_history_slot = 4;
3728 let existing_older_than_history_slot = 6;
3729
3730 let mut tower_sync = TowerSync::from(vec![
3731 (missing_older_than_history_slot, 4),
3732 (existing_older_than_history_slot, 3),
3733 (12, 2),
3734 (vote_slot, 1),
3735 ]);
3736 tower_sync.hash = vote_slot_hash;
3737 check_and_filter_proposed_vote_state(
3738 &vote_state,
3739 &mut tower_sync.lockouts,
3740 &mut tower_sync.root,
3741 tower_sync.hash,
3742 &slot_hashes,
3743 )
3744 .unwrap();
3745 assert_eq!(tower_sync.lockouts.len(), 3);
3746 assert_eq!(
3747 tower_sync
3748 .clone()
3749 .lockouts
3750 .into_iter()
3751 .collect::<Vec<Lockout>>(),
3752 vec![
3753 Lockout::new_with_confirmation_count(existing_older_than_history_slot, 3),
3754 Lockout::new_with_confirmation_count(12, 2),
3755 Lockout::new_with_confirmation_count(vote_slot, 1)
3756 ]
3757 );
3758 assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3759 }
3760
3761 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3762 fn test_check_and_filter_proposed_vote_state_slot_not_on_fork(
3763 target_version: VoteStateTargetVersion,
3764 ) {
3765 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3766 let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3767
3768 let missing_vote_slot = 3;
3774
3775 let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3778 let vote_slot_hash = slot_hashes
3779 .iter()
3780 .find(|(slot, _hash)| *slot == vote_slot)
3781 .unwrap()
3782 .1;
3783 let mut tower_sync = TowerSync::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
3784 tower_sync.hash = vote_slot_hash;
3785 assert_eq!(
3786 check_and_filter_proposed_vote_state(
3787 &vote_state,
3788 &mut tower_sync.lockouts,
3789 &mut tower_sync.root,
3790 tower_sync.hash,
3791 &slot_hashes
3792 ),
3793 Err(VoteError::SlotsMismatch),
3794 );
3795
3796 let missing_vote_slot = 7;
3798 let mut tower_sync = TowerSync::from(vec![
3799 (2, 5),
3800 (4, 4),
3801 (6, 3),
3802 (missing_vote_slot, 2),
3803 (vote_slot, 1),
3804 ]);
3805 tower_sync.hash = vote_slot_hash;
3806 assert_eq!(
3807 check_and_filter_proposed_vote_state(
3808 &vote_state,
3809 &mut tower_sync.lockouts,
3810 &mut tower_sync.root,
3811 tower_sync.hash,
3812 &slot_hashes
3813 ),
3814 Err(VoteError::SlotsMismatch),
3815 );
3816 }
3817
3818 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3819 fn test_check_and_filter_proposed_vote_state_root_on_different_fork(
3820 target_version: VoteStateTargetVersion,
3821 ) {
3822 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3823 let vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3824
3825 let new_root = 3;
3831
3832 let vote_slot = 8;
3835 assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
3836 let vote_slot_hash = slot_hashes
3837 .iter()
3838 .find(|(slot, _hash)| *slot == vote_slot)
3839 .unwrap()
3840 .1;
3841 let mut tower_sync = TowerSync::from(vec![(vote_slot, 1)]);
3842 tower_sync.hash = vote_slot_hash;
3843 tower_sync.root = Some(new_root);
3844 assert_eq!(
3845 check_and_filter_proposed_vote_state(
3846 &vote_state,
3847 &mut tower_sync.lockouts,
3848 &mut tower_sync.root,
3849 tower_sync.hash,
3850 &slot_hashes
3851 ),
3852 Err(VoteError::RootOnDifferentFork),
3853 );
3854 }
3855
3856 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3857 fn test_check_and_filter_proposed_vote_state_slot_newer_than_slot_history(
3858 target_version: VoteStateTargetVersion,
3859 ) {
3860 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3861 let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3862
3863 let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
3869 let vote_slot_hash = Hash::new_unique();
3870 let mut tower_sync = TowerSync::from(vec![(8, 2), (missing_vote_slot, 3)]);
3871 tower_sync.hash = vote_slot_hash;
3872 assert_eq!(
3873 check_and_filter_proposed_vote_state(
3874 &vote_state,
3875 &mut tower_sync.lockouts,
3876 &mut tower_sync.root,
3877 tower_sync.hash,
3878 &slot_hashes
3879 ),
3880 Err(VoteError::SlotsMismatch),
3881 );
3882 }
3883
3884 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3885 fn test_check_and_filter_proposed_vote_state_slot_all_slot_hashes_in_update_ok(
3886 target_version: VoteStateTargetVersion,
3887 ) {
3888 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3889 let mut vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3890
3891 let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3897 let vote_slot_hash = slot_hashes
3898 .iter()
3899 .find(|(slot, _hash)| *slot == vote_slot)
3900 .unwrap()
3901 .1;
3902 let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3903 tower_sync.hash = vote_slot_hash;
3904 check_and_filter_proposed_vote_state(
3905 &vote_state,
3906 &mut tower_sync.lockouts,
3907 &mut tower_sync.root,
3908 tower_sync.hash,
3909 &slot_hashes,
3910 )
3911 .unwrap();
3912
3913 assert_eq!(
3915 tower_sync
3916 .clone()
3917 .lockouts
3918 .into_iter()
3919 .collect::<Vec<Lockout>>(),
3920 vec![
3921 Lockout::new_with_confirmation_count(2, 4),
3922 Lockout::new_with_confirmation_count(4, 3),
3923 Lockout::new_with_confirmation_count(6, 2),
3924 Lockout::new_with_confirmation_count(vote_slot, 1)
3925 ]
3926 );
3927
3928 assert!(do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,).is_ok());
3929 }
3930
3931 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3932 fn test_check_and_filter_proposed_vote_state_slot_some_slot_hashes_in_update_ok(
3933 target_version: VoteStateTargetVersion,
3934 ) {
3935 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
3936 let mut vote_state = build_vote_state(target_version, vec![6], &slot_hashes);
3937
3938 let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3944 let vote_slot_hash = slot_hashes
3945 .iter()
3946 .find(|(slot, _hash)| *slot == vote_slot)
3947 .unwrap()
3948 .1;
3949 let mut tower_sync = TowerSync::from(vec![(4, 2), (vote_slot, 1)]);
3950 tower_sync.hash = vote_slot_hash;
3951 check_and_filter_proposed_vote_state(
3952 &vote_state,
3953 &mut tower_sync.lockouts,
3954 &mut tower_sync.root,
3955 tower_sync.hash,
3956 &slot_hashes,
3957 )
3958 .unwrap();
3959
3960 assert_eq!(
3962 tower_sync
3963 .clone()
3964 .lockouts
3965 .into_iter()
3966 .collect::<Vec<Lockout>>(),
3967 vec![
3968 Lockout::new_with_confirmation_count(4, 2),
3969 Lockout::new_with_confirmation_count(vote_slot, 1)
3970 ]
3971 );
3972
3973 assert_eq!(
3977 do_process_tower_sync(&mut vote_state, &slot_hashes, 0, 0, tower_sync,),
3978 Err(VoteError::LockoutConflict)
3979 );
3980 }
3981
3982 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
3983 fn test_check_and_filter_proposed_vote_state_slot_hash_mismatch(
3984 target_version: VoteStateTargetVersion,
3985 ) {
3986 let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
3987 let vote_state = build_vote_state(target_version, vec![2, 4, 6], &slot_hashes);
3988
3989 let vote_slot = vote_state.votes().back().unwrap().slot() + 2;
3994 let vote_slot_hash = Hash::new_unique();
3995 let mut tower_sync = TowerSync::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
3996 tower_sync.hash = vote_slot_hash;
3997 assert_eq!(
3998 check_and_filter_proposed_vote_state(
3999 &vote_state,
4000 &mut tower_sync.lockouts,
4001 &mut tower_sync.root,
4002 tower_sync.hash,
4003 &slot_hashes,
4004 ),
4005 Err(VoteError::SlotHashMismatch),
4006 );
4007 }
4008
4009 #[test_case(0, true; "first slot")]
4010 #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
4011 #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
4012 #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
4013 #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
4014 fn test_epoch_half_check(slot: Slot, expected_allowed: bool) {
4015 let epoch_schedule = EpochSchedule::without_warmup();
4016 assert_eq!(
4017 is_commission_update_allowed(slot, &epoch_schedule),
4018 expected_allowed
4019 );
4020 }
4021
4022 #[test]
4023 fn test_warmup_epoch_half_check_with_warmup() {
4024 let epoch_schedule = EpochSchedule::default();
4025 let first_normal_slot = epoch_schedule.first_normal_slot;
4026 assert!(is_commission_update_allowed(0, &epoch_schedule));
4028 assert!(is_commission_update_allowed(
4031 first_normal_slot - 1,
4032 &epoch_schedule
4033 ));
4034 }
4035
4036 #[test_case(0, true; "first slot")]
4037 #[test_case(DEFAULT_SLOTS_PER_EPOCH / 2, true; "halfway through epoch")]
4038 #[test_case((DEFAULT_SLOTS_PER_EPOCH / 2).saturating_add(1), false; "halfway through epoch plus one")]
4039 #[test_case(DEFAULT_SLOTS_PER_EPOCH.saturating_sub(1), false; "last slot in epoch")]
4040 #[test_case(DEFAULT_SLOTS_PER_EPOCH, true; "first slot in second epoch")]
4041 fn test_epoch_half_check_with_warmup(slot: Slot, expected_allowed: bool) {
4042 let epoch_schedule = EpochSchedule::default();
4043 let first_normal_slot = epoch_schedule.first_normal_slot;
4044 assert_eq!(
4045 is_commission_update_allowed(first_normal_slot.saturating_add(slot), &epoch_schedule),
4046 expected_allowed
4047 );
4048 }
4049
4050 #[test]
4051 fn test_create_v4_account_with_authorized() {
4052 let node_pubkey = Pubkey::new_unique();
4053 let authorized_voter = Pubkey::new_unique();
4054 let authorized_withdrawer = Pubkey::new_unique();
4055 let bls_pubkey_compressed = [42; 48];
4056 let inflation_rewards_commission_bps = 10000;
4057 let lamports = 100;
4058 let vote_account = create_v4_account_with_authorized(
4059 &node_pubkey,
4060 &authorized_voter,
4061 bls_pubkey_compressed,
4062 &authorized_withdrawer,
4063 inflation_rewards_commission_bps,
4064 &authorized_withdrawer,
4065 0,
4066 &node_pubkey,
4067 lamports,
4068 );
4069 assert_eq!(vote_account.lamports(), lamports);
4070 assert_eq!(vote_account.owner(), &id());
4071 assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
4072 let vote_state_v4 = VoteStateV4::deserialize(vote_account.data(), &node_pubkey).unwrap();
4073 assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
4074 assert_eq!(
4075 vote_state_v4.authorized_voters,
4076 AuthorizedVoters::new(0, authorized_voter)
4077 );
4078 assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
4079 assert_eq!(
4080 vote_state_v4.bls_pubkey_compressed,
4081 Some(bls_pubkey_compressed)
4082 );
4083 assert_eq!(
4084 vote_state_v4.inflation_rewards_commission_bps,
4085 inflation_rewards_commission_bps
4086 );
4087 }
4088
4089 #[test]
4090 fn test_update_validator_identity_syncs_block_revenue_collector() {
4091 let custom_commission_collector_enabled = false;
4093
4094 let vote_state =
4095 vote_state_new_for_test(&solana_pubkey::new_rand(), VoteStateTargetVersion::V4);
4096 let node_pubkey = *vote_state.node_pubkey();
4097 let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4098
4099 let serialized = vote_state.serialize();
4100 let serialized_len = serialized.len();
4101 let rent = Rent::default();
4102 let lamports = rent.minimum_balance(serialized_len);
4103 let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4104 vote_account.set_data_from_slice(&serialized);
4105
4106 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4107 let mut transaction_context = TransactionContext::new(
4108 vec![(id(), processor_account), (node_pubkey, vote_account)],
4109 rent,
4110 0,
4111 0,
4112 1,
4113 );
4114 transaction_context
4115 .configure_top_level_instruction_for_tests(
4116 0,
4117 vec![InstructionAccount::new(1, false, true)],
4118 vec![],
4119 )
4120 .unwrap();
4121 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4122 let mut borrowed_account = instruction_context
4123 .try_borrow_instruction_account(0)
4124 .unwrap();
4125
4126 let new_node_pubkey = solana_pubkey::new_rand();
4127 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4128 .into_iter()
4129 .collect();
4130
4131 update_validator_identity(
4132 &mut borrowed_account,
4133 VoteStateTargetVersion::V4,
4134 &new_node_pubkey,
4135 &signers,
4136 custom_commission_collector_enabled,
4137 )
4138 .unwrap();
4139
4140 let vote_state =
4143 VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4144 assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4145 assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
4146
4147 let new_node_pubkey = solana_pubkey::new_rand();
4149 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4150 .into_iter()
4151 .collect();
4152
4153 update_validator_identity(
4154 &mut borrowed_account,
4155 VoteStateTargetVersion::V4,
4156 &new_node_pubkey,
4157 &signers,
4158 custom_commission_collector_enabled,
4159 )
4160 .unwrap();
4161
4162 let vote_state =
4163 VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4164 assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4165 assert_eq!(vote_state.block_revenue_collector, new_node_pubkey);
4166 }
4167
4168 #[test]
4169 fn test_update_validator_identity_preserves_custom_block_revenue_collector() {
4170 let custom_commission_collector_enabled = true;
4176
4177 let vote_pubkey = solana_pubkey::new_rand();
4178 let mut vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
4179 let node_pubkey = *vote_state.node_pubkey();
4180 let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4181
4182 let custom_collector = solana_pubkey::new_rand();
4184 vote_state.set_block_revenue_collector(custom_collector);
4185
4186 let serialized = vote_state.serialize();
4187 let serialized_len = serialized.len();
4188 let rent = Rent::default();
4189 let lamports = rent.minimum_balance(serialized_len);
4190 let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4191 vote_account.set_data_from_slice(&serialized);
4192
4193 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4194 let mut transaction_context = TransactionContext::new(
4195 vec![(id(), processor_account), (node_pubkey, vote_account)],
4196 rent,
4197 0,
4198 0,
4199 1,
4200 );
4201 transaction_context
4202 .configure_top_level_instruction_for_tests(
4203 0,
4204 vec![InstructionAccount::new(1, false, true)],
4205 vec![],
4206 )
4207 .unwrap();
4208 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4209 let mut borrowed_account = instruction_context
4210 .try_borrow_instruction_account(0)
4211 .unwrap();
4212
4213 let new_node_pubkey = solana_pubkey::new_rand();
4214 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey, new_node_pubkey]
4215 .into_iter()
4216 .collect();
4217
4218 update_validator_identity(
4219 &mut borrowed_account,
4220 VoteStateTargetVersion::V4,
4221 &new_node_pubkey,
4222 &signers,
4223 custom_commission_collector_enabled,
4224 )
4225 .unwrap();
4226
4227 let vote_state =
4229 VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap();
4230 assert_eq!(vote_state.node_pubkey, new_node_pubkey);
4231 assert_eq!(vote_state.block_revenue_collector, custom_collector);
4232 assert_ne!(vote_state.block_revenue_collector, new_node_pubkey);
4233 }
4234
4235 #[test]
4236 fn test_get_and_update_authorized_voter_v4_with_bls() {
4237 let vote_account_pubkey = Pubkey::new_unique();
4238 let (bls_pubkey, bls_proof_of_possession) =
4239 create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey);
4240 let node_pubkey = Pubkey::new_unique();
4241 let authorized_voter = Pubkey::new_unique();
4242 let authorized_withdrawer = Pubkey::new_unique();
4243 let inflation_rewards_commission_bps = 10000;
4244 let rent = Rent::default();
4245 let lamports = rent.minimum_balance(VoteStateV4::size_of());
4246 let vote_account = create_v4_account_with_authorized(
4248 &node_pubkey,
4249 &authorized_voter,
4250 [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
4251 &authorized_withdrawer,
4252 inflation_rewards_commission_bps,
4253 &authorized_withdrawer,
4254 0,
4255 &node_pubkey,
4256 lamports,
4257 );
4258 assert_eq!(vote_account.lamports(), lamports);
4259 assert_eq!(vote_account.owner(), &id());
4260 assert_eq!(vote_account.data().len(), VoteStateV4::size_of());
4261
4262 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4263 let mut transaction_context = TransactionContext::new(
4264 vec![
4265 (id(), processor_account),
4266 (vote_account_pubkey, vote_account),
4267 ],
4268 rent,
4269 0,
4270 0,
4271 1,
4272 );
4273 transaction_context
4274 .configure_top_level_instruction_for_tests(
4275 0,
4276 vec![InstructionAccount::new(1, false, true)],
4277 vec![],
4278 )
4279 .unwrap();
4280 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4281 let mut borrowed_account = instruction_context
4282 .try_borrow_instruction_account(0)
4283 .unwrap();
4284
4285 let new_node_pubkey = solana_pubkey::new_rand();
4286 let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_node_pubkey]
4287 .into_iter()
4288 .collect();
4289 let clock = Clock::default();
4290 assert!(
4291 authorize(
4292 &mut borrowed_account,
4293 VoteStateTargetVersion::V4,
4294 &new_node_pubkey,
4295 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4296 bls_pubkey,
4297 bls_proof_of_possession
4298 }),
4299 &signers,
4300 &clock,
4301 true,
4302 || Ok(()),
4303 )
4304 .is_ok()
4305 );
4306 let vote_state = VoteStateHandler::new_v4(
4307 VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap(),
4308 );
4309 assert_eq!(
4310 vote_state.as_ref_v4().bls_pubkey_compressed,
4311 Some(bls_pubkey)
4312 );
4313 assert!(vote_state.has_bls_pubkey());
4314
4315 let clock = Clock {
4317 epoch: 3,
4318 ..Clock::default()
4319 };
4320 let (others_bls_pubkey, others_bls_proof_of_possession) =
4321 create_bls_pubkey_and_proof_of_possession(&Pubkey::new_unique());
4322 let new_node_pubkey = solana_pubkey::new_rand();
4323 let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_node_pubkey]
4324 .into_iter()
4325 .collect();
4326 assert_eq!(
4327 authorize(
4328 &mut borrowed_account,
4329 VoteStateTargetVersion::V4,
4330 &new_node_pubkey,
4331 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4332 bls_pubkey: others_bls_pubkey,
4333 bls_proof_of_possession: others_bls_proof_of_possession
4334 }),
4335 &signers,
4336 &clock,
4337 true,
4338 || Ok(()),
4339 ),
4340 Err(InstructionError::InvalidArgument),
4341 );
4342
4343 let clock = Clock {
4345 epoch: 5,
4346 ..Clock::default()
4347 };
4348 let (new_bls_pubkey, new_bls_proof_of_possession) =
4349 create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey);
4350 let new_authorized_voter = solana_pubkey::new_rand();
4351 let signers: HashSet<Pubkey> = vec![authorized_withdrawer, new_authorized_voter]
4352 .into_iter()
4353 .collect();
4354 assert_eq!(
4355 authorize(
4356 &mut borrowed_account,
4357 VoteStateTargetVersion::V4,
4358 &new_authorized_voter,
4359 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4360 bls_pubkey: new_bls_pubkey,
4361 bls_proof_of_possession: new_bls_proof_of_possession
4362 }),
4363 &signers,
4364 &clock,
4365 true,
4366 || Ok(()),
4367 ),
4368 Ok(())
4369 );
4370 let vote_state = VoteStateHandler::new_v4(
4371 VoteStateV4::deserialize(borrowed_account.get_data(), &new_authorized_voter).unwrap(),
4372 );
4373 assert_eq!(
4374 vote_state.as_ref_v4().bls_pubkey_compressed,
4375 Some(new_bls_pubkey)
4376 );
4377 assert!(vote_state.has_bls_pubkey());
4378 }
4379
4380 fn new_transaction_context(
4381 accounts: Vec<(Pubkey, AccountSharedData)>,
4382 instruction_accounts: Vec<InstructionAccount>,
4383 rent: &Rent,
4384 ) -> TransactionContext<'_> {
4385 let mut transaction_context = TransactionContext::new(accounts, rent.clone(), 0, 0, 1);
4386 transaction_context
4387 .configure_top_level_instruction_for_tests(0, instruction_accounts, vec![])
4388 .unwrap();
4389 transaction_context
4390 }
4391
4392 #[test]
4393 fn test_new_commission_collector_validate_and_resolve_key() {
4394 let rent = Rent::default();
4395 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4396 let vote_pubkey = solana_pubkey::new_rand();
4397 let vote_account = AccountSharedData::new(1, 0, &id());
4398 let collector_pubkey = solana_pubkey::new_rand();
4399 let valid_collector =
4400 || AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
4401
4402 {
4404 let transaction_context = new_transaction_context(
4405 vec![
4406 (id(), processor_account.clone()),
4407 (vote_pubkey, vote_account.clone()),
4408 ],
4409 vec![InstructionAccount::new(1, false, true)],
4410 &rent,
4411 );
4412 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4413 let borrowed_vote = instruction_context
4414 .try_borrow_instruction_account(0)
4415 .unwrap();
4416 assert_eq!(
4417 NewCommissionCollector::VoteAccount.validate_and_resolve_key(&borrowed_vote, &rent),
4418 Ok(vote_pubkey),
4419 );
4420 }
4421
4422 {
4424 let transaction_context = new_transaction_context(
4425 vec![
4426 (id(), processor_account.clone()),
4427 (vote_pubkey, vote_account.clone()),
4428 (collector_pubkey, valid_collector()),
4429 ],
4430 vec![
4431 InstructionAccount::new(1, false, true),
4432 InstructionAccount::new(2, false, true),
4433 ],
4434 &rent,
4435 );
4436 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4437 let borrowed_vote = instruction_context
4438 .try_borrow_instruction_account(0)
4439 .unwrap();
4440 let borrowed_collector = instruction_context
4441 .try_borrow_instruction_account(1)
4442 .unwrap();
4443 assert_eq!(
4444 NewCommissionCollector::NewAccount(borrowed_collector)
4445 .validate_and_resolve_key(&borrowed_vote, &rent),
4446 Ok(collector_pubkey),
4447 );
4448 }
4449
4450 {
4453 let incinerator_account =
4456 AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
4457 let transaction_context = new_transaction_context(
4458 vec![
4459 (id(), processor_account.clone()),
4460 (vote_pubkey, vote_account.clone()),
4461 (solana_sdk_ids::incinerator::id(), incinerator_account),
4462 ],
4463 vec![
4464 InstructionAccount::new(1, false, true),
4465 InstructionAccount::new(2, false, true),
4466 ],
4467 &rent,
4468 );
4469 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4470 let borrowed_vote = instruction_context
4471 .try_borrow_instruction_account(0)
4472 .unwrap();
4473 let borrowed_collector = instruction_context
4474 .try_borrow_instruction_account(1)
4475 .unwrap();
4476 assert_eq!(
4477 NewCommissionCollector::NewAccount(borrowed_collector)
4478 .validate_and_resolve_key(&borrowed_vote, &rent),
4479 Ok(solana_sdk_ids::incinerator::id()),
4480 );
4481 }
4482
4483 {
4485 let bad_owner =
4486 AccountSharedData::new(rent.minimum_balance(0), 0, &solana_pubkey::new_rand());
4487 let transaction_context = new_transaction_context(
4488 vec![
4489 (id(), processor_account.clone()),
4490 (vote_pubkey, vote_account.clone()),
4491 (collector_pubkey, bad_owner),
4492 ],
4493 vec![
4494 InstructionAccount::new(1, false, true),
4495 InstructionAccount::new(2, false, true),
4496 ],
4497 &rent,
4498 );
4499 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4500 let borrowed_vote = instruction_context
4501 .try_borrow_instruction_account(0)
4502 .unwrap();
4503 let borrowed_collector = instruction_context
4504 .try_borrow_instruction_account(1)
4505 .unwrap();
4506 assert_eq!(
4507 NewCommissionCollector::NewAccount(borrowed_collector)
4508 .validate_and_resolve_key(&borrowed_vote, &rent),
4509 Err(InstructionError::InvalidAccountOwner),
4510 );
4511 }
4512
4513 {
4515 let underfunded = AccountSharedData::new(0, 0, &system_program::id());
4516 let transaction_context = new_transaction_context(
4517 vec![
4518 (id(), processor_account.clone()),
4519 (vote_pubkey, vote_account.clone()),
4520 (collector_pubkey, underfunded),
4521 ],
4522 vec![
4523 InstructionAccount::new(1, false, true),
4524 InstructionAccount::new(2, false, true),
4525 ],
4526 &rent,
4527 );
4528 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4529 let borrowed_vote = instruction_context
4530 .try_borrow_instruction_account(0)
4531 .unwrap();
4532 let borrowed_collector = instruction_context
4533 .try_borrow_instruction_account(1)
4534 .unwrap();
4535 assert_eq!(
4536 NewCommissionCollector::NewAccount(borrowed_collector)
4537 .validate_and_resolve_key(&borrowed_vote, &rent),
4538 Err(InstructionError::InsufficientFunds),
4539 );
4540 }
4541
4542 {
4544 let transaction_context = new_transaction_context(
4545 vec![
4546 (id(), processor_account),
4547 (vote_pubkey, vote_account),
4548 (collector_pubkey, valid_collector()),
4549 ],
4550 vec![
4551 InstructionAccount::new(1, false, true),
4552 InstructionAccount::new(2, false, false), ],
4554 &rent,
4555 );
4556 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4557 let borrowed_vote = instruction_context
4558 .try_borrow_instruction_account(0)
4559 .unwrap();
4560 let borrowed_collector = instruction_context
4561 .try_borrow_instruction_account(1)
4562 .unwrap();
4563 assert_eq!(
4564 NewCommissionCollector::NewAccount(borrowed_collector)
4565 .validate_and_resolve_key(&borrowed_vote, &rent),
4566 Err(InstructionError::InvalidArgument),
4567 );
4568 }
4569 }
4570
4571 #[test]
4575 fn test_update_commission_collector() {
4576 let target_version = VoteStateTargetVersion::V4;
4577 let vote_pubkey = solana_pubkey::new_rand();
4578 let vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
4579 let withdrawer_pubkey = *vote_state.authorized_withdrawer();
4580 let node_pubkey = *vote_state.node_pubkey();
4581
4582 let signers: HashSet<Pubkey> = vec![withdrawer_pubkey].into_iter().collect();
4583
4584 let serialized = vote_state.serialize();
4585 let serialized_len = serialized.len();
4586 let rent = Rent::default();
4587 let lamports = rent.minimum_balance(serialized_len);
4588 let mut vote_account = AccountSharedData::new(lamports, serialized_len, &id());
4589 vote_account.set_data_from_slice(&serialized);
4590
4591 let get_commission_collector =
4592 |vote_account: &BorrowedInstructionAccount, kind: CommissionKind| {
4593 let handler = get_vote_state_handler_checked(vote_account, target_version).unwrap();
4594 let vote_state = handler.as_ref_v4();
4595 match kind {
4596 CommissionKind::InflationRewards => vote_state.inflation_rewards_collector,
4597 CommissionKind::BlockRevenue => vote_state.block_revenue_collector,
4598 }
4599 };
4600
4601 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
4602
4603 let new_collector = solana_pubkey::new_rand();
4605 let collector_lamports = rent.minimum_balance(0);
4606 let collector_account =
4607 AccountSharedData::new(collector_lamports, 0, &system_program::id());
4608
4609 let original_inflation_collector = vote_pubkey;
4610 let original_block_revenue_collector = node_pubkey;
4611
4612 {
4614 let transaction_context = new_transaction_context(
4615 vec![
4616 (id(), processor_account.clone()),
4617 (vote_pubkey, vote_account.clone()),
4618 (new_collector, collector_account.clone()),
4619 ],
4620 vec![
4621 InstructionAccount::new(1, false, true),
4622 InstructionAccount::new(2, false, true),
4623 ],
4624 &rent,
4625 );
4626 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4627 let mut borrowed_vote_account = instruction_context
4628 .try_borrow_instruction_account(0)
4629 .unwrap();
4630
4631 update_commission_collector(
4633 &mut borrowed_vote_account,
4634 target_version,
4635 NewCommissionCollector::NewAccount(
4636 instruction_context
4637 .try_borrow_instruction_account(1)
4638 .unwrap(),
4639 ),
4640 CommissionKind::InflationRewards,
4641 &signers,
4642 &rent,
4643 )
4644 .unwrap();
4645 assert_eq!(
4646 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4647 new_collector,
4648 );
4649 assert_eq!(
4650 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4651 original_block_revenue_collector, );
4653
4654 update_commission_collector(
4656 &mut borrowed_vote_account,
4657 target_version,
4658 NewCommissionCollector::NewAccount(
4659 instruction_context
4660 .try_borrow_instruction_account(1)
4661 .unwrap(),
4662 ),
4663 CommissionKind::BlockRevenue,
4664 &signers,
4665 &rent,
4666 )
4667 .unwrap();
4668 assert_eq!(
4669 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4670 new_collector,
4671 );
4672 assert_eq!(
4673 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4674 new_collector,
4675 );
4676 }
4677
4678 {
4680 let transaction_context = new_transaction_context(
4681 vec![
4682 (id(), processor_account.clone()),
4683 (vote_pubkey, vote_account.clone()),
4684 ],
4685 vec![
4686 InstructionAccount::new(1, false, true),
4687 InstructionAccount::new(1, false, true), ],
4689 &rent,
4690 );
4691 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4692 let mut borrowed_vote_account = instruction_context
4693 .try_borrow_instruction_account(0)
4694 .unwrap();
4695
4696 update_commission_collector(
4698 &mut borrowed_vote_account,
4699 target_version,
4700 NewCommissionCollector::VoteAccount,
4701 CommissionKind::InflationRewards,
4702 &signers,
4703 &rent,
4704 )
4705 .unwrap();
4706 assert_eq!(
4707 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4708 vote_pubkey,
4709 );
4710 assert_eq!(
4711 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4712 original_block_revenue_collector, );
4714
4715 update_commission_collector(
4717 &mut borrowed_vote_account,
4718 target_version,
4719 NewCommissionCollector::VoteAccount,
4720 CommissionKind::BlockRevenue,
4721 &signers,
4722 &rent,
4723 )
4724 .unwrap();
4725 assert_eq!(
4726 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4727 vote_pubkey,
4728 );
4729 assert_eq!(
4730 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4731 vote_pubkey,
4732 );
4733 }
4734
4735 {
4740 let v3 = get_max_sized_vote_state_v3();
4741 let v3_node_pubkey = v3.node_pubkey;
4742 let v3_withdrawer = v3.authorized_withdrawer;
4743 let v3_vote_pubkey = solana_pubkey::new_rand();
4744
4745 let v4_size = VoteStateV4::size_of();
4746 let mut account_data = vec![0u8; v4_size];
4747 bincode::serialize_into(&mut account_data[..], &VoteStateVersions::V3(Box::new(v3)))
4748 .unwrap();
4749 let mut v3_vote_account =
4750 AccountSharedData::new(rent.minimum_balance(v4_size), v4_size, &id());
4751 v3_vote_account.set_data_from_slice(&account_data);
4752
4753 let v3_signers: HashSet<Pubkey> = vec![v3_withdrawer].into_iter().collect();
4754
4755 let transaction_context = new_transaction_context(
4756 vec![
4757 (id(), processor_account.clone()),
4758 (v3_vote_pubkey, v3_vote_account),
4759 (new_collector, collector_account.clone()),
4760 ],
4761 vec![
4762 InstructionAccount::new(1, false, true),
4763 InstructionAccount::new(2, false, true),
4764 ],
4765 &rent,
4766 );
4767 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4768 let mut borrowed_vote_account = instruction_context
4769 .try_borrow_instruction_account(0)
4770 .unwrap();
4771
4772 update_commission_collector(
4773 &mut borrowed_vote_account,
4774 target_version,
4775 NewCommissionCollector::NewAccount(
4776 instruction_context
4777 .try_borrow_instruction_account(1)
4778 .unwrap(),
4779 ),
4780 CommissionKind::InflationRewards,
4781 &v3_signers,
4782 &rent,
4783 )
4784 .unwrap();
4785
4786 assert_eq!(
4788 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4789 new_collector,
4790 );
4791 assert_eq!(
4794 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4795 v3_node_pubkey,
4796 );
4797 }
4798
4799 let run_with_account_data = |bytes: Vec<u8>| -> Result<(), InstructionError> {
4805 let mut bad_vote_account =
4806 AccountSharedData::new(rent.minimum_balance(bytes.len()), bytes.len(), &id());
4807 bad_vote_account.set_data_from_slice(&bytes);
4808 let transaction_context = new_transaction_context(
4809 vec![
4810 (id(), processor_account.clone()),
4811 (vote_pubkey, bad_vote_account),
4812 (new_collector, collector_account.clone()),
4813 ],
4814 vec![
4815 InstructionAccount::new(1, false, true),
4816 InstructionAccount::new(2, false, true),
4817 ],
4818 &rent,
4819 );
4820 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4821 let mut borrowed_vote_account = instruction_context
4822 .try_borrow_instruction_account(0)
4823 .unwrap();
4824 update_commission_collector(
4825 &mut borrowed_vote_account,
4826 target_version,
4827 NewCommissionCollector::NewAccount(
4828 instruction_context
4829 .try_borrow_instruction_account(1)
4830 .unwrap(),
4831 ),
4832 CommissionKind::InflationRewards,
4833 &signers,
4834 &rent,
4835 )
4836 };
4837 let variant_with_short_body = |variant: u32| -> Vec<u8> {
4838 let mut bytes = vec![0u8; 8];
4839 bytes[..4].copy_from_slice(&variant.to_le_bytes());
4840 bytes
4841 };
4842
4843 assert_eq!(
4845 run_with_account_data(variant_with_short_body(0)),
4846 Err(InstructionError::InvalidAccountData),
4847 );
4848
4849 assert_eq!(
4851 run_with_account_data(variant_with_short_body(1)),
4852 Err(InstructionError::InvalidAccountData),
4853 );
4854
4855 assert_eq!(
4857 run_with_account_data(variant_with_short_body(2)),
4858 Err(InstructionError::InvalidAccountData),
4859 );
4860
4861 assert_eq!(
4863 run_with_account_data(variant_with_short_body(3)),
4864 Err(InstructionError::InvalidAccountData),
4865 );
4866
4867 {
4869 let non_signers: HashSet<Pubkey> = HashSet::new();
4870 let transaction_context = new_transaction_context(
4871 vec![
4872 (id(), processor_account.clone()),
4873 (vote_pubkey, vote_account.clone()),
4874 (new_collector, collector_account.clone()),
4875 ],
4876 vec![
4877 InstructionAccount::new(1, false, true),
4878 InstructionAccount::new(2, false, true),
4879 ],
4880 &rent,
4881 );
4882 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4883 let mut borrowed_vote_account = instruction_context
4884 .try_borrow_instruction_account(0)
4885 .unwrap();
4886
4887 assert_eq!(
4888 update_commission_collector(
4889 &mut borrowed_vote_account,
4890 target_version,
4891 NewCommissionCollector::NewAccount(
4892 instruction_context
4893 .try_borrow_instruction_account(1)
4894 .unwrap()
4895 ),
4896 CommissionKind::InflationRewards,
4897 &non_signers,
4898 &rent,
4899 ),
4900 Err(InstructionError::MissingRequiredSignature)
4901 );
4902 assert_eq!(
4903 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4904 original_inflation_collector, );
4906 assert_eq!(
4907 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4908 original_block_revenue_collector, );
4910 }
4911
4912 {
4914 let wrong_signers: HashSet<Pubkey> = vec![Pubkey::new_unique()].into_iter().collect();
4915 let transaction_context = new_transaction_context(
4916 vec![
4917 (id(), processor_account.clone()),
4918 (vote_pubkey, vote_account.clone()),
4919 (new_collector, collector_account),
4920 ],
4921 vec![
4922 InstructionAccount::new(1, false, true),
4923 InstructionAccount::new(2, false, true),
4924 ],
4925 &rent,
4926 );
4927 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4928 let mut borrowed_vote_account = instruction_context
4929 .try_borrow_instruction_account(0)
4930 .unwrap();
4931
4932 assert_eq!(
4933 update_commission_collector(
4934 &mut borrowed_vote_account,
4935 target_version,
4936 NewCommissionCollector::NewAccount(
4937 instruction_context
4938 .try_borrow_instruction_account(1)
4939 .unwrap()
4940 ),
4941 CommissionKind::InflationRewards,
4942 &wrong_signers,
4943 &rent,
4944 ),
4945 Err(InstructionError::MissingRequiredSignature)
4946 );
4947 assert_eq!(
4948 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4949 original_inflation_collector, );
4951 assert_eq!(
4952 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
4953 original_block_revenue_collector, );
4955 }
4956
4957 {
4959 let bad_collector = solana_pubkey::new_rand();
4960 let non_system_owner = solana_pubkey::new_rand();
4961 let bad_collector_account =
4962 AccountSharedData::new(collector_lamports, 0, &non_system_owner);
4963 let transaction_context = new_transaction_context(
4964 vec![
4965 (id(), processor_account.clone()),
4966 (vote_pubkey, vote_account.clone()),
4967 (bad_collector, bad_collector_account),
4968 ],
4969 vec![
4970 InstructionAccount::new(1, false, true),
4971 InstructionAccount::new(2, false, true),
4972 ],
4973 &rent,
4974 );
4975 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
4976 let mut borrowed_vote_account = instruction_context
4977 .try_borrow_instruction_account(0)
4978 .unwrap();
4979
4980 assert_eq!(
4981 update_commission_collector(
4982 &mut borrowed_vote_account,
4983 target_version,
4984 NewCommissionCollector::NewAccount(
4985 instruction_context
4986 .try_borrow_instruction_account(1)
4987 .unwrap()
4988 ),
4989 CommissionKind::InflationRewards,
4990 &signers,
4991 &rent,
4992 ),
4993 Err(InstructionError::InvalidAccountOwner)
4994 );
4995 assert_eq!(
4996 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
4997 original_inflation_collector, );
4999 assert_eq!(
5000 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5001 original_block_revenue_collector, );
5003 }
5004
5005 {
5007 let bad_collector = solana_pubkey::new_rand();
5008 let bad_collector_account = AccountSharedData::new(0, 0, &system_program::id());
5009 let transaction_context = new_transaction_context(
5010 vec![
5011 (id(), processor_account.clone()),
5012 (vote_pubkey, vote_account.clone()),
5013 (bad_collector, bad_collector_account),
5014 ],
5015 vec![
5016 InstructionAccount::new(1, false, true),
5017 InstructionAccount::new(2, false, true),
5018 ],
5019 &rent,
5020 );
5021 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5022 let mut borrowed_vote_account = instruction_context
5023 .try_borrow_instruction_account(0)
5024 .unwrap();
5025
5026 assert_eq!(
5027 update_commission_collector(
5028 &mut borrowed_vote_account,
5029 target_version,
5030 NewCommissionCollector::NewAccount(
5031 instruction_context
5032 .try_borrow_instruction_account(1)
5033 .unwrap()
5034 ),
5035 CommissionKind::InflationRewards,
5036 &signers,
5037 &rent,
5038 ),
5039 Err(InstructionError::InsufficientFunds)
5040 );
5041 assert_eq!(
5042 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
5043 original_inflation_collector, );
5045 assert_eq!(
5046 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5047 original_block_revenue_collector, );
5049 }
5050
5051 {
5053 let bad_collector = solana_pubkey::new_rand();
5054 let bad_collector_account =
5055 AccountSharedData::new(collector_lamports, 0, &system_program::id());
5056 let transaction_context = new_transaction_context(
5057 vec![
5058 (id(), processor_account),
5059 (vote_pubkey, vote_account.clone()),
5060 (bad_collector, bad_collector_account),
5061 ],
5062 vec![
5063 InstructionAccount::new(1, false, true),
5064 InstructionAccount::new(2, false, false), ],
5066 &rent,
5067 );
5068 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5069 let mut borrowed_vote_account = instruction_context
5070 .try_borrow_instruction_account(0)
5071 .unwrap();
5072
5073 assert_eq!(
5074 update_commission_collector(
5075 &mut borrowed_vote_account,
5076 target_version,
5077 NewCommissionCollector::NewAccount(
5078 instruction_context
5079 .try_borrow_instruction_account(1)
5080 .unwrap()
5081 ),
5082 CommissionKind::InflationRewards,
5083 &signers,
5084 &rent,
5085 ),
5086 Err(InstructionError::InvalidArgument)
5087 );
5088 assert_eq!(
5089 get_commission_collector(&borrowed_vote_account, CommissionKind::InflationRewards),
5090 original_inflation_collector, );
5092 assert_eq!(
5093 get_commission_collector(&borrowed_vote_account, CommissionKind::BlockRevenue),
5094 original_block_revenue_collector, );
5096 }
5097 }
5098
5099 #[test]
5100 fn test_initialize_account_v2() {
5101 let target_version = VoteStateTargetVersion::V4;
5102 let rent = Rent::default();
5103 let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
5104
5105 let vote_pubkey = solana_pubkey::new_rand();
5106 let node_pubkey = solana_pubkey::new_rand();
5107 let authorized_voter = solana_pubkey::new_rand();
5108 let authorized_withdrawer = solana_pubkey::new_rand();
5109 let inflation_collector_pubkey = solana_pubkey::new_rand();
5110 let block_revenue_collector_pubkey = solana_pubkey::new_rand();
5111
5112 let (bls_pubkey, bls_proof_of_possession) =
5113 create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
5114 let vote_init = VoteInitV2 {
5115 node_pubkey,
5116 authorized_voter,
5117 authorized_voter_bls_pubkey: bls_pubkey,
5118 authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
5119 authorized_withdrawer,
5120 inflation_rewards_commission_bps: 1_234,
5121 block_revenue_commission_bps: 5_678,
5122 };
5123
5124 let signers: HashSet<Pubkey> = vec![node_pubkey].into_iter().collect();
5125 let clock = Clock::default();
5126
5127 let v4_size = VoteStateV4::size_of();
5128 let lamports = rent.minimum_balance(v4_size);
5129 let make_uninit_vote_account = || AccountSharedData::new(lamports, v4_size, &id());
5130 let valid_collector_account =
5131 || AccountSharedData::new(rent.minimum_balance(0), 0, &system_program::id());
5132
5133 let assert_v4_fields =
5134 |vote_account: &BorrowedInstructionAccount,
5135 expected_inflation_rewards_collector: Pubkey,
5136 expected_block_revenue_collector: Pubkey| {
5137 let VoteStateVersions::V4(v4) =
5138 vote_account.get_state::<VoteStateVersions>().unwrap()
5139 else {
5140 panic!("expected v4");
5141 };
5142 assert_eq!(v4.node_pubkey, node_pubkey);
5143 assert_eq!(
5144 v4.authorized_voters.get_authorized_voter(clock.epoch),
5145 Some(authorized_voter),
5146 );
5147 assert_eq!(v4.authorized_withdrawer, authorized_withdrawer);
5148 assert_eq!(v4.bls_pubkey_compressed, Some(bls_pubkey));
5149 assert_eq!(v4.inflation_rewards_commission_bps, 1_234);
5150 assert_eq!(v4.block_revenue_commission_bps, 5_678);
5151 assert_eq!(
5152 v4.inflation_rewards_collector,
5153 expected_inflation_rewards_collector
5154 );
5155 assert_eq!(v4.block_revenue_collector, expected_block_revenue_collector);
5156 assert_eq!(v4.pending_delegator_rewards, 0);
5157 assert!(v4.votes.is_empty());
5158 assert_eq!(v4.root_slot, None);
5159 assert!(v4.epoch_credits.is_empty());
5160 };
5161
5162 let assert_still_uninitialized = |vote_account: &BorrowedInstructionAccount| {
5163 assert!(
5164 vote_account
5165 .get_state::<VoteStateVersions>()
5166 .unwrap()
5167 .is_uninitialized()
5168 );
5169 };
5170
5171 {
5173 let transaction_context = new_transaction_context(
5174 vec![
5175 (id(), processor_account.clone()),
5176 (vote_pubkey, make_uninit_vote_account()),
5177 (inflation_collector_pubkey, valid_collector_account()),
5178 (block_revenue_collector_pubkey, valid_collector_account()),
5179 ],
5180 vec![
5181 InstructionAccount::new(1, false, true),
5182 InstructionAccount::new(2, false, true),
5183 InstructionAccount::new(3, false, true),
5184 ],
5185 &rent,
5186 );
5187 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5188 let mut borrowed_vote_account = instruction_context
5189 .try_borrow_instruction_account(0)
5190 .unwrap();
5191
5192 initialize_account_v2(
5193 &mut borrowed_vote_account,
5194 target_version,
5195 &vote_init,
5196 NewCommissionCollector::NewAccount(
5197 instruction_context
5198 .try_borrow_instruction_account(1)
5199 .unwrap(),
5200 ),
5201 NewCommissionCollector::NewAccount(
5202 instruction_context
5203 .try_borrow_instruction_account(2)
5204 .unwrap(),
5205 ),
5206 &signers,
5207 &clock,
5208 &rent,
5209 || Ok(()),
5210 )
5211 .unwrap();
5212
5213 assert_v4_fields(
5214 &borrowed_vote_account,
5215 inflation_collector_pubkey,
5216 block_revenue_collector_pubkey,
5217 );
5218 }
5219
5220 {
5222 let transaction_context = new_transaction_context(
5223 vec![
5224 (id(), processor_account.clone()),
5225 (vote_pubkey, make_uninit_vote_account()),
5226 (block_revenue_collector_pubkey, valid_collector_account()),
5227 ],
5228 vec![
5229 InstructionAccount::new(1, false, true),
5230 InstructionAccount::new(2, false, true),
5231 ],
5232 &rent,
5233 );
5234 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5235 let mut borrowed_vote_account = instruction_context
5236 .try_borrow_instruction_account(0)
5237 .unwrap();
5238
5239 initialize_account_v2(
5240 &mut borrowed_vote_account,
5241 target_version,
5242 &vote_init,
5243 NewCommissionCollector::VoteAccount,
5244 NewCommissionCollector::NewAccount(
5245 instruction_context
5246 .try_borrow_instruction_account(1)
5247 .unwrap(),
5248 ),
5249 &signers,
5250 &clock,
5251 &rent,
5252 || Ok(()),
5253 )
5254 .unwrap();
5255
5256 assert_v4_fields(
5257 &borrowed_vote_account,
5258 vote_pubkey,
5259 block_revenue_collector_pubkey,
5260 );
5261 }
5262
5263 {
5265 let transaction_context = new_transaction_context(
5266 vec![
5267 (id(), processor_account.clone()),
5268 (vote_pubkey, make_uninit_vote_account()),
5269 (inflation_collector_pubkey, valid_collector_account()),
5270 ],
5271 vec![
5272 InstructionAccount::new(1, false, true),
5273 InstructionAccount::new(2, false, true),
5274 ],
5275 &rent,
5276 );
5277 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5278 let mut borrowed_vote_account = instruction_context
5279 .try_borrow_instruction_account(0)
5280 .unwrap();
5281
5282 initialize_account_v2(
5283 &mut borrowed_vote_account,
5284 target_version,
5285 &vote_init,
5286 NewCommissionCollector::NewAccount(
5287 instruction_context
5288 .try_borrow_instruction_account(1)
5289 .unwrap(),
5290 ),
5291 NewCommissionCollector::VoteAccount,
5292 &signers,
5293 &clock,
5294 &rent,
5295 || Ok(()),
5296 )
5297 .unwrap();
5298
5299 assert_v4_fields(
5300 &borrowed_vote_account,
5301 inflation_collector_pubkey,
5302 vote_pubkey,
5303 );
5304 }
5305
5306 {
5308 let transaction_context = new_transaction_context(
5309 vec![
5310 (id(), processor_account.clone()),
5311 (vote_pubkey, make_uninit_vote_account()),
5312 ],
5313 vec![InstructionAccount::new(1, false, true)],
5314 &rent,
5315 );
5316 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5317 let mut borrowed_vote_account = instruction_context
5318 .try_borrow_instruction_account(0)
5319 .unwrap();
5320
5321 initialize_account_v2(
5322 &mut borrowed_vote_account,
5323 target_version,
5324 &vote_init,
5325 NewCommissionCollector::VoteAccount,
5326 NewCommissionCollector::VoteAccount,
5327 &signers,
5328 &clock,
5329 &rent,
5330 || Ok(()),
5331 )
5332 .unwrap();
5333
5334 assert_v4_fields(&borrowed_vote_account, vote_pubkey, vote_pubkey);
5335 }
5336
5337 {
5339 let oversized_vote_account =
5340 AccountSharedData::new(rent.minimum_balance(2 * v4_size), 2 * v4_size, &id());
5341 let transaction_context = new_transaction_context(
5342 vec![
5343 (id(), processor_account.clone()),
5344 (vote_pubkey, oversized_vote_account),
5345 (inflation_collector_pubkey, valid_collector_account()),
5346 (block_revenue_collector_pubkey, valid_collector_account()),
5347 ],
5348 vec![
5349 InstructionAccount::new(1, false, true),
5350 InstructionAccount::new(2, false, true),
5351 InstructionAccount::new(3, false, true),
5352 ],
5353 &rent,
5354 );
5355 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5356 let mut borrowed_vote_account = instruction_context
5357 .try_borrow_instruction_account(0)
5358 .unwrap();
5359
5360 assert_eq!(
5361 initialize_account_v2(
5362 &mut borrowed_vote_account,
5363 target_version,
5364 &vote_init,
5365 NewCommissionCollector::NewAccount(
5366 instruction_context
5367 .try_borrow_instruction_account(1)
5368 .unwrap(),
5369 ),
5370 NewCommissionCollector::NewAccount(
5371 instruction_context
5372 .try_borrow_instruction_account(2)
5373 .unwrap(),
5374 ),
5375 &signers,
5376 &clock,
5377 &rent,
5378 || Ok(()),
5379 ),
5380 Err(InstructionError::InvalidAccountData),
5381 );
5382 }
5383
5384 {
5386 let mut invalid_vote_account = AccountSharedData::new(lamports, v4_size, &id());
5387 invalid_vote_account.set_data_from_slice(&vec![0xFFu8; v4_size]);
5388
5389 let transaction_context = new_transaction_context(
5390 vec![
5391 (id(), processor_account.clone()),
5392 (vote_pubkey, invalid_vote_account),
5393 (inflation_collector_pubkey, valid_collector_account()),
5394 (block_revenue_collector_pubkey, valid_collector_account()),
5395 ],
5396 vec![
5397 InstructionAccount::new(1, false, true),
5398 InstructionAccount::new(2, false, true),
5399 InstructionAccount::new(3, false, true),
5400 ],
5401 &rent,
5402 );
5403 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5404 let mut borrowed_vote_account = instruction_context
5405 .try_borrow_instruction_account(0)
5406 .unwrap();
5407
5408 assert_eq!(
5409 initialize_account_v2(
5410 &mut borrowed_vote_account,
5411 target_version,
5412 &vote_init,
5413 NewCommissionCollector::NewAccount(
5414 instruction_context
5415 .try_borrow_instruction_account(1)
5416 .unwrap(),
5417 ),
5418 NewCommissionCollector::NewAccount(
5419 instruction_context
5420 .try_borrow_instruction_account(2)
5421 .unwrap(),
5422 ),
5423 &signers,
5424 &clock,
5425 &rent,
5426 || Ok(()),
5427 ),
5428 Err(InstructionError::InvalidAccountData),
5429 );
5430 }
5431
5432 {
5434 let preexisting_handler = vote_state_new_for_test(&vote_pubkey, target_version);
5435 let preexisting_state = preexisting_handler.as_ref_v4().clone();
5436 let serialized = preexisting_handler.serialize();
5437 let serialized_len = serialized.len();
5438 let mut initialized_vote_account =
5439 AccountSharedData::new(rent.minimum_balance(serialized_len), serialized_len, &id());
5440 initialized_vote_account.set_data_from_slice(&serialized);
5441
5442 let transaction_context = new_transaction_context(
5443 vec![
5444 (id(), processor_account.clone()),
5445 (vote_pubkey, initialized_vote_account),
5446 (inflation_collector_pubkey, valid_collector_account()),
5447 (block_revenue_collector_pubkey, valid_collector_account()),
5448 ],
5449 vec![
5450 InstructionAccount::new(1, false, true),
5451 InstructionAccount::new(2, false, true),
5452 InstructionAccount::new(3, false, true),
5453 ],
5454 &rent,
5455 );
5456 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5457 let mut borrowed_vote_account = instruction_context
5458 .try_borrow_instruction_account(0)
5459 .unwrap();
5460
5461 assert_eq!(
5462 initialize_account_v2(
5463 &mut borrowed_vote_account,
5464 target_version,
5465 &vote_init,
5466 NewCommissionCollector::NewAccount(
5467 instruction_context
5468 .try_borrow_instruction_account(1)
5469 .unwrap(),
5470 ),
5471 NewCommissionCollector::NewAccount(
5472 instruction_context
5473 .try_borrow_instruction_account(2)
5474 .unwrap(),
5475 ),
5476 &signers,
5477 &clock,
5478 &rent,
5479 || Ok(()),
5480 ),
5481 Err(InstructionError::AccountAlreadyInitialized),
5482 );
5483
5484 let handler =
5487 get_vote_state_handler_checked(&borrowed_vote_account, target_version).unwrap();
5488 assert_eq!(*handler.as_ref_v4(), preexisting_state);
5489 }
5490
5491 {
5493 let non_signers: HashSet<Pubkey> = HashSet::new();
5494 let transaction_context = new_transaction_context(
5495 vec![
5496 (id(), processor_account.clone()),
5497 (vote_pubkey, make_uninit_vote_account()),
5498 (inflation_collector_pubkey, valid_collector_account()),
5499 (block_revenue_collector_pubkey, valid_collector_account()),
5500 ],
5501 vec![
5502 InstructionAccount::new(1, false, true),
5503 InstructionAccount::new(2, false, true),
5504 InstructionAccount::new(3, false, true),
5505 ],
5506 &rent,
5507 );
5508 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5509 let mut borrowed_vote_account = instruction_context
5510 .try_borrow_instruction_account(0)
5511 .unwrap();
5512
5513 assert_eq!(
5514 initialize_account_v2(
5515 &mut borrowed_vote_account,
5516 target_version,
5517 &vote_init,
5518 NewCommissionCollector::NewAccount(
5519 instruction_context
5520 .try_borrow_instruction_account(1)
5521 .unwrap(),
5522 ),
5523 NewCommissionCollector::NewAccount(
5524 instruction_context
5525 .try_borrow_instruction_account(2)
5526 .unwrap(),
5527 ),
5528 &non_signers,
5529 &clock,
5530 &rent,
5531 || Ok(()),
5532 ),
5533 Err(InstructionError::MissingRequiredSignature),
5534 );
5535 assert_still_uninitialized(&borrowed_vote_account);
5536 }
5537
5538 {
5541 #[derive(Clone, Copy)]
5542 enum CollectorSlot {
5543 Inflation,
5544 BlockRevenue,
5545 }
5546
5547 let test_bad_collector =
5548 |slot: CollectorSlot,
5549 bad_collector: AccountSharedData,
5550 bad_collector_is_writable: bool,
5551 expected_error: InstructionError| {
5552 let (
5553 inflation_account,
5554 inflation_writable,
5555 block_revenue_account,
5556 block_revenue_writable,
5557 ) = match slot {
5558 CollectorSlot::Inflation => (
5559 bad_collector,
5560 bad_collector_is_writable,
5561 valid_collector_account(),
5562 true,
5563 ),
5564 CollectorSlot::BlockRevenue => (
5565 valid_collector_account(),
5566 true,
5567 bad_collector,
5568 bad_collector_is_writable,
5569 ),
5570 };
5571
5572 let transaction_context = new_transaction_context(
5573 vec![
5574 (id(), processor_account.clone()),
5575 (vote_pubkey, make_uninit_vote_account()),
5576 (inflation_collector_pubkey, inflation_account),
5577 (block_revenue_collector_pubkey, block_revenue_account),
5578 ],
5579 vec![
5580 InstructionAccount::new(1, false, true),
5581 InstructionAccount::new(2, false, inflation_writable),
5582 InstructionAccount::new(3, false, block_revenue_writable),
5583 ],
5584 &rent,
5585 );
5586 let instruction_context =
5587 transaction_context.get_next_instruction_context().unwrap();
5588 let mut borrowed_vote_account = instruction_context
5589 .try_borrow_instruction_account(0)
5590 .unwrap();
5591
5592 assert_eq!(
5593 initialize_account_v2(
5594 &mut borrowed_vote_account,
5595 target_version,
5596 &vote_init,
5597 NewCommissionCollector::NewAccount(
5598 instruction_context
5599 .try_borrow_instruction_account(1)
5600 .unwrap(),
5601 ),
5602 NewCommissionCollector::NewAccount(
5603 instruction_context
5604 .try_borrow_instruction_account(2)
5605 .unwrap(),
5606 ),
5607 &signers,
5608 &clock,
5609 &rent,
5610 || Ok(()),
5611 ),
5612 Err(expected_error),
5613 );
5614 assert_still_uninitialized(&borrowed_vote_account);
5615 };
5616
5617 for slot in [CollectorSlot::Inflation, CollectorSlot::BlockRevenue] {
5618 test_bad_collector(
5620 slot,
5621 AccountSharedData::new(rent.minimum_balance(0), 0, &solana_pubkey::new_rand()),
5622 true,
5623 InstructionError::InvalidAccountOwner,
5624 );
5625
5626 test_bad_collector(
5628 slot,
5629 AccountSharedData::new(0, 0, &system_program::id()),
5630 true,
5631 InstructionError::InsufficientFunds,
5632 );
5633
5634 test_bad_collector(
5636 slot,
5637 valid_collector_account(),
5638 false,
5639 InstructionError::InvalidArgument,
5640 );
5641 }
5642 }
5643
5644 {
5646 let bad_vote_init = VoteInitV2 {
5647 authorized_voter_bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
5648 authorized_voter_bls_proof_of_possession: [2u8;
5649 BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
5650 ..vote_init
5651 };
5652 let transaction_context = new_transaction_context(
5653 vec![
5654 (id(), processor_account),
5655 (vote_pubkey, make_uninit_vote_account()),
5656 (inflation_collector_pubkey, valid_collector_account()),
5657 (block_revenue_collector_pubkey, valid_collector_account()),
5658 ],
5659 vec![
5660 InstructionAccount::new(1, false, true),
5661 InstructionAccount::new(2, false, true),
5662 InstructionAccount::new(3, false, true),
5663 ],
5664 &rent,
5665 );
5666 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
5667 let mut borrowed_vote_account = instruction_context
5668 .try_borrow_instruction_account(0)
5669 .unwrap();
5670
5671 assert_eq!(
5672 initialize_account_v2(
5673 &mut borrowed_vote_account,
5674 target_version,
5675 &bad_vote_init,
5676 NewCommissionCollector::NewAccount(
5677 instruction_context
5678 .try_borrow_instruction_account(1)
5679 .unwrap(),
5680 ),
5681 NewCommissionCollector::NewAccount(
5682 instruction_context
5683 .try_borrow_instruction_account(2)
5684 .unwrap(),
5685 ),
5686 &signers,
5687 &clock,
5688 &rent,
5689 || Ok(()),
5690 ),
5691 Err(InstructionError::InvalidArgument),
5692 );
5693 assert_still_uninitialized(&borrowed_vote_account);
5694 }
5695 }
5696
5697 fn setup_withdraw_context(
5699 vote_pubkey: Pubkey,
5700 vote_account: AccountSharedData,
5701 ) -> TransactionContext<'static> {
5702 let rent = Rent::default();
5703 let recipient = solana_pubkey::new_rand();
5704 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
5705 let mut transaction_context = TransactionContext::new(
5706 vec![
5707 (id(), program_account),
5708 (vote_pubkey, vote_account),
5709 (recipient, AccountSharedData::default()),
5710 ],
5711 rent,
5712 0,
5713 0,
5714 1,
5715 );
5716 transaction_context
5717 .configure_top_level_instruction_for_tests(
5718 0,
5719 vec![
5720 InstructionAccount::new(1, false, true),
5721 InstructionAccount::new(2, false, true),
5722 ],
5723 vec![],
5724 )
5725 .unwrap();
5726 transaction_context
5727 }
5728
5729 #[test_case(VoteStateTargetVersion::V4 ; "VoteStateV4")]
5730 fn test_withdraw(target_version: VoteStateTargetVersion) {
5731 let vote_pubkey = solana_pubkey::new_rand();
5734 let vote_state = vote_state_new_for_test(&vote_pubkey, target_version);
5735 let withdrawer = *vote_state.authorized_withdrawer();
5736 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5737 let rent = Rent::default();
5738 let serialized = vote_state.clone().serialize();
5739 let serialized_len = serialized.len();
5740 let min_balance = rent.minimum_balance(serialized_len);
5741 let clock = Clock {
5742 epoch: 100,
5743 ..Clock::default()
5744 };
5745
5746 {
5748 let mut acct = AccountSharedData::new(min_balance, serialized_len, &id());
5749 acct.set_data_from_slice(&serialized);
5750 let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5751 let ix = transaction_context.get_next_instruction_context().unwrap();
5752 assert_eq!(
5753 withdraw(&ix, 0, target_version, 1, 1, &signers, &rent, &clock),
5754 Err(InstructionError::InsufficientFunds)
5755 );
5756 }
5757
5758 {
5760 let mut acct = AccountSharedData::new(min_balance, serialized_len, &id());
5761 acct.set_data_from_slice(&serialized);
5762 let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5763 let ix = transaction_context.get_next_instruction_context().unwrap();
5764 withdraw(
5765 &ix,
5766 0,
5767 target_version,
5768 min_balance,
5769 1,
5770 &signers,
5771 &rent,
5772 &clock,
5773 )
5774 .unwrap();
5775 }
5776
5777 {
5779 let mut acct = AccountSharedData::new(min_balance + 100, serialized_len, &id());
5780 acct.set_data_from_slice(&serialized);
5781 let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5782 let ix = transaction_context.get_next_instruction_context().unwrap();
5783 withdraw(&ix, 0, target_version, 100, 1, &signers, &rent, &clock).unwrap();
5784 }
5785
5786 {
5788 let mut acct = AccountSharedData::new(min_balance + 100, serialized_len, &id());
5789 acct.set_data_from_slice(&serialized);
5790 let transaction_context = setup_withdraw_context(vote_pubkey, acct);
5791 let ix = transaction_context.get_next_instruction_context().unwrap();
5792 assert_eq!(
5793 withdraw(&ix, 0, target_version, 101, 1, &signers, &rent, &clock),
5794 Err(InstructionError::InsufficientFunds)
5795 );
5796 }
5797 }
5798
5799 fn make_v4_account_with_pending(
5802 vote_pubkey: &Pubkey,
5803 pending: u64,
5804 extra_lamports: u64,
5805 ) -> (VoteStateHandler, AccountSharedData) {
5806 let vote_state = vote_state_new_for_test(vote_pubkey, VoteStateTargetVersion::V4);
5807 let mut v4 = vote_state.as_ref_v4().clone();
5808 v4.pending_delegator_rewards = pending;
5809 let handler = VoteStateHandler::new_v4(v4);
5810 let serialized = handler.clone().serialize();
5811 let rent = Rent::default();
5812 let lamports = rent.minimum_balance(serialized.len()) + extra_lamports;
5813 let mut account = AccountSharedData::new(lamports, serialized.len(), &id());
5814 account.set_data_from_slice(&serialized);
5815 (handler, account)
5816 }
5817
5818 #[test]
5819 fn test_withdraw_with_pending_delegator_rewards() {
5820 let vote_pubkey = solana_pubkey::new_rand();
5824 let rent = Rent::default();
5825 let clock = Clock {
5826 epoch: 100,
5827 ..Clock::default()
5828 };
5829
5830 {
5832 let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1000);
5833 let withdrawer = *handler.authorized_withdrawer();
5834 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5835 let tx = setup_withdraw_context(vote_pubkey, account);
5836 let ix = tx.get_next_instruction_context().unwrap();
5837
5838 assert_eq!(
5840 withdraw(
5841 &ix,
5842 0,
5843 VoteStateTargetVersion::V4,
5844 1,
5845 1,
5846 &signers,
5847 &rent,
5848 &clock
5849 ),
5850 Err(InstructionError::InsufficientFunds)
5851 );
5852 }
5853
5854 {
5856 let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1001);
5857 let withdrawer = *handler.authorized_withdrawer();
5858 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5859 let tx = setup_withdraw_context(vote_pubkey, account);
5860 let ix = tx.get_next_instruction_context().unwrap();
5861
5862 withdraw(
5864 &ix,
5865 0,
5866 VoteStateTargetVersion::V4,
5867 1,
5868 1,
5869 &signers,
5870 &rent,
5871 &clock,
5872 )
5873 .unwrap();
5874 }
5875
5876 {
5878 let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1000, 1001);
5879 let withdrawer = *handler.authorized_withdrawer();
5880 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5881 let tx = setup_withdraw_context(vote_pubkey, account);
5882 let ix = tx.get_next_instruction_context().unwrap();
5883
5884 assert_eq!(
5885 withdraw(
5886 &ix,
5887 0,
5888 VoteStateTargetVersion::V4,
5889 2,
5890 1,
5891 &signers,
5892 &rent,
5893 &clock
5894 ),
5895 Err(InstructionError::InsufficientFunds)
5896 );
5897 }
5898
5899 {
5901 let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 1, 1_000_000);
5902 let withdrawer = *handler.authorized_withdrawer();
5903 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5904 let lamports = rent.minimum_balance(VoteStateV4::size_of()) + 1_000_000;
5905 let tx = setup_withdraw_context(vote_pubkey, account);
5906 let ix = tx.get_next_instruction_context().unwrap();
5907
5908 assert_eq!(
5909 withdraw(
5910 &ix,
5911 0,
5912 VoteStateTargetVersion::V4,
5913 lamports,
5914 1,
5915 &signers,
5916 &rent,
5917 &clock
5918 ),
5919 Err(InstructionError::InsufficientFunds)
5920 );
5921 }
5922
5923 {
5925 let (handler, account) = make_v4_account_with_pending(&vote_pubkey, 0, 100);
5926 let withdrawer = *handler.authorized_withdrawer();
5927 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
5928 let lamports = rent.minimum_balance(VoteStateV4::size_of()) + 100;
5929 let tx = setup_withdraw_context(vote_pubkey, account);
5930 let ix = tx.get_next_instruction_context().unwrap();
5931
5932 withdraw(
5933 &ix,
5934 0,
5935 VoteStateTargetVersion::V4,
5936 lamports,
5937 1,
5938 &signers,
5939 &rent,
5940 &clock,
5941 )
5942 .unwrap();
5943 }
5944 }
5945
5946 fn get_max_sized_vote_state_v3() -> VoteStateV3 {
5949 let root_slot = 42u64;
5950 let votes: VecDeque<LandedVote> = (0..MAX_LOCKOUT_HISTORY)
5951 .map(|i| LandedVote {
5952 latency: i as u8,
5953 lockout: Lockout::new_with_confirmation_count(
5954 root_slot + i as u64 + 1,
5955 (MAX_LOCKOUT_HISTORY - i) as u32,
5956 ),
5957 })
5958 .collect();
5959 let epoch_credits: Vec<(u64, u64, u64)> = (0..MAX_EPOCH_CREDITS_HISTORY)
5960 .map(|i| (i as u64, (i as u64 + 1) * 100, i as u64 * 100))
5961 .collect();
5962 let mut authorized_voters = AuthorizedVoters::default();
5963 for i in 0..=solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
5964 authorized_voters.insert(i, solana_pubkey::new_rand());
5965 }
5966
5967 VoteStateV3 {
5968 node_pubkey: solana_pubkey::new_rand(),
5969 authorized_withdrawer: solana_pubkey::new_rand(),
5970 commission: 42,
5971 votes,
5972 root_slot: Some(root_slot),
5973 epoch_credits,
5974 authorized_voters,
5975 ..Default::default()
5976 }
5977 }
5978
5979 #[test]
5980 fn test_v3_to_v4_stale_trailing_bytes() {
5981 let vote_pubkey = solana_pubkey::new_rand();
5990 let v3 = get_max_sized_vote_state_v3();
5991 let node_pubkey = v3.node_pubkey;
5992 let authorized_withdrawer = v3.authorized_withdrawer;
5993 let commission = v3.commission;
5994 let root_slot = v3.root_slot;
5995 let votes = v3.votes.clone();
5996 let epoch_credits = v3.epoch_credits.clone();
5997 let authorized_voters = v3.authorized_voters.clone();
5998 let last_timestamp = v3.last_timestamp.clone();
5999
6000 let buf_size = VoteStateV3::size_of();
6002 let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6003 let v3_serialized_len = bincode::serialized_size(&v3_versioned).unwrap() as usize;
6004 let mut vote_account_data = vec![0u8; buf_size];
6005 bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6006
6007 let rent = Rent::default();
6009 let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6010 let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6011 vote_account.set_data_from_slice(&vote_account_data);
6012 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6013 let transaction_context = new_transaction_context(
6014 vec![(id(), program_account), (vote_pubkey, vote_account)],
6015 vec![InstructionAccount::new(1, false, true)],
6016 &rent,
6017 );
6018 let ix = transaction_context.get_next_instruction_context().unwrap();
6019 let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6020
6021 let vote_state =
6024 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6025 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6026
6027 let account_data = borrowed.get_data();
6029 let v4_serialized_len = {
6030 let v4 = VoteStateV4::deserialize(account_data, &vote_pubkey).unwrap();
6031 bincode::serialized_size(&VoteStateVersions::new_v4(v4)).unwrap() as usize
6032 };
6033 assert!(
6034 v4_serialized_len < v3_serialized_len,
6035 "v4 ({v4_serialized_len}) should be smaller than v3 ({v3_serialized_len})",
6036 );
6037
6038 let deserialized = VoteStateV4::deserialize(account_data, &vote_pubkey).unwrap();
6041 assert_eq!(deserialized.node_pubkey, node_pubkey);
6042 assert_eq!(deserialized.authorized_withdrawer, authorized_withdrawer);
6043 assert_eq!(deserialized.root_slot, root_slot);
6044 assert_eq!(deserialized.votes, votes);
6045 assert_eq!(deserialized.epoch_credits, epoch_credits);
6046 assert_eq!(deserialized.authorized_voters, authorized_voters);
6047 assert_eq!(
6048 deserialized.inflation_rewards_commission_bps,
6049 commission as u16 * 100
6050 );
6051 assert_eq!(deserialized.last_timestamp, last_timestamp);
6052
6053 borrowed.get_data_mut().unwrap()[v4_serialized_len..].fill(0xDE);
6056
6057 let vote_state =
6058 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6059 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6060
6061 let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6062 assert_eq!(deserialized.node_pubkey, node_pubkey);
6063 assert_eq!(deserialized.authorized_withdrawer, authorized_withdrawer);
6064 assert_eq!(deserialized.root_slot, root_slot);
6065 assert_eq!(deserialized.votes, votes);
6066 assert_eq!(deserialized.epoch_credits, epoch_credits);
6067 assert_eq!(deserialized.authorized_voters, authorized_voters);
6068 assert_eq!(
6069 deserialized.inflation_rewards_commission_bps,
6070 commission as u16 * 100
6071 );
6072 assert_eq!(deserialized.last_timestamp, last_timestamp);
6073 }
6074
6075 #[test]
6076 fn test_v3_to_v4_trailing_bytes_shrink_and_regrow() {
6077 let vote_pubkey = solana_pubkey::new_rand();
6091 let v3 = get_max_sized_vote_state_v3();
6092 let node_pubkey = v3.node_pubkey;
6093 let authorized_withdrawer = v3.authorized_withdrawer;
6094 let commission = v3.commission;
6095 let root_slot = v3.root_slot;
6096 let votes = v3.votes.clone();
6097 let epoch_credits = v3.epoch_credits.clone();
6098 let authorized_voters = v3.authorized_voters.clone();
6099 let last_timestamp = v3.last_timestamp.clone();
6100
6101 let buf_size = VoteStateV3::size_of();
6103 let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6104 let v3_serialized_len = bincode::serialized_size(&v3_versioned).unwrap() as usize;
6105 let mut vote_account_data = vec![0u8; buf_size];
6106 bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6107
6108 let rent = Rent::default();
6109 let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6110 let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6111 vote_account.set_data_from_slice(&vote_account_data);
6112 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6113 let transaction_context = new_transaction_context(
6114 vec![(id(), program_account), (vote_pubkey, vote_account)],
6115 vec![InstructionAccount::new(1, false, true)],
6116 &rent,
6117 );
6118 let ix = transaction_context.get_next_instruction_context().unwrap();
6119 let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6120
6121 let vote_state =
6123 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6124 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6125
6126 let v4_after_convert = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6127 let v4_serialized_len =
6128 bincode::serialized_size(&VoteStateVersions::new_v4(v4_after_convert.clone())).unwrap()
6129 as usize;
6130
6131 assert!(
6132 v4_serialized_len < v3_serialized_len,
6133 "v4 ({v4_serialized_len}) should be smaller than v3 ({v3_serialized_len})",
6134 );
6135 let trailing_len_after_convert = buf_size - v4_serialized_len;
6136 assert!(
6137 trailing_len_after_convert > 0,
6138 "expected trailing bytes after v3 -> v4 conversion"
6139 );
6140
6141 assert_eq!(v4_after_convert.node_pubkey, node_pubkey);
6143 assert_eq!(
6144 v4_after_convert.authorized_withdrawer,
6145 authorized_withdrawer
6146 );
6147 assert_eq!(v4_after_convert.root_slot, root_slot);
6148 assert_eq!(v4_after_convert.votes, votes);
6149 assert_eq!(v4_after_convert.epoch_credits, epoch_credits);
6150 assert_eq!(v4_after_convert.authorized_voters, authorized_voters);
6151 assert_eq!(
6152 v4_after_convert.inflation_rewards_commission_bps,
6153 commission as u16 * 100,
6154 );
6155 assert_eq!(v4_after_convert.last_timestamp, last_timestamp);
6156
6157 let mut v4_empty_votes = v4_after_convert.clone();
6159 v4_empty_votes.votes.clear();
6160 let v4_empty_serialized_len =
6161 bincode::serialized_size(&VoteStateVersions::new_v4(v4_empty_votes.clone())).unwrap()
6162 as usize;
6163 assert!(
6164 v4_empty_serialized_len < v4_serialized_len,
6165 "empty-votes v4 ({v4_empty_serialized_len}) should be smaller than full v4 \
6166 ({v4_serialized_len})",
6167 );
6168
6169 borrowed
6172 .set_state(&VoteStateVersions::new_v4(v4_empty_votes.clone()))
6173 .unwrap();
6174 let trailing_len_after_clear = buf_size - v4_empty_serialized_len;
6175 assert!(
6176 trailing_len_after_clear > trailing_len_after_convert,
6177 "trailing region should grow after clearing votes: {trailing_len_after_clear} vs \
6178 {trailing_len_after_convert}",
6179 );
6180
6181 let vote_state =
6183 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6184 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6185
6186 let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6187 assert!(deserialized.votes.is_empty(),);
6188 assert_eq!(deserialized.epoch_credits.len(), MAX_EPOCH_CREDITS_HISTORY,);
6189 assert_eq!(deserialized.authorized_voters, authorized_voters,);
6190 assert_eq!(deserialized.last_timestamp, last_timestamp);
6191
6192 borrowed.get_data_mut().unwrap()[v4_empty_serialized_len..].fill(0xCD);
6197
6198 let vote_state =
6199 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6200 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6201
6202 let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6203 assert!(deserialized.votes.is_empty());
6204 assert_eq!(deserialized.epoch_credits.len(), MAX_EPOCH_CREDITS_HISTORY);
6205 assert_eq!(deserialized.authorized_voters, authorized_voters);
6206 assert_eq!(deserialized.last_timestamp, last_timestamp);
6207
6208 let mut v4_regrowing = v4_empty_votes;
6214 for i in 0..MAX_LOCKOUT_HISTORY {
6215 v4_regrowing.votes.push_back(LandedVote {
6216 latency: (i % 256) as u8,
6217 lockout: Lockout::new_with_confirmation_count(
6218 root_slot.unwrap() + 1000 + i as u64,
6219 (MAX_LOCKOUT_HISTORY - i) as u32,
6220 ),
6221 });
6222
6223 borrowed
6225 .set_state(&VoteStateVersions::new_v4(v4_regrowing.clone()))
6226 .unwrap();
6227 let current_serialized_len =
6228 bincode::serialized_size(&VoteStateVersions::new_v4(v4_regrowing.clone())).unwrap()
6229 as usize;
6230 let current_trailing = buf_size - current_serialized_len;
6231 assert!(
6232 current_trailing < trailing_len_after_clear,
6233 "trailing region should shrink as votes are added"
6234 );
6235 if current_serialized_len < buf_size {
6236 borrowed.get_data_mut().unwrap()[current_serialized_len..].fill(0xEF);
6237 }
6238
6239 let vote_state =
6241 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6242 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6243
6244 let deserialized = VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6245 assert_eq!(
6246 deserialized.votes.len(),
6247 i + 1,
6248 "expected {} votes after re-adding",
6249 i + 1,
6250 );
6251 assert_eq!(deserialized, v4_regrowing);
6252 }
6253
6254 let final_deserialized =
6256 VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap();
6257 assert_eq!(final_deserialized.votes.len(), MAX_LOCKOUT_HISTORY);
6258 assert_eq!(final_deserialized.epoch_credits, epoch_credits);
6259 assert_eq!(final_deserialized.authorized_voters, authorized_voters);
6260 assert_eq!(final_deserialized.last_timestamp, last_timestamp);
6261 }
6262
6263 #[test]
6264 fn test_bls_absent_after_v3_to_v4_migration() {
6265 let vote_pubkey = solana_pubkey::new_rand();
6268 let v3 = VoteStateV3::new(
6269 &VoteInit {
6270 node_pubkey: solana_pubkey::new_rand(),
6271 authorized_voter: solana_pubkey::new_rand(),
6272 authorized_withdrawer: solana_pubkey::new_rand(),
6273 commission: 10,
6274 },
6275 &Clock::default(),
6276 );
6277 let last_timestamp = v3.last_timestamp.clone();
6278
6279 let buf_size = VoteStateV3::size_of();
6281 let v3_versioned = VoteStateVersions::V3(Box::new(v3));
6282 let mut vote_account_data = vec![0u8; buf_size];
6283 bincode::serialize_into(&mut vote_account_data[..], &v3_versioned).unwrap();
6284
6285 let rent = Rent::default();
6286 let lamports = rent.minimum_balance(buf_size) + 1_000_000;
6287 let mut vote_account = AccountSharedData::new(lamports, buf_size, &id());
6288 vote_account.set_data_from_slice(&vote_account_data);
6289 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6290 let transaction_context = new_transaction_context(
6291 vec![(id(), program_account), (vote_pubkey, vote_account)],
6292 vec![InstructionAccount::new(1, false, true)],
6293 &rent,
6294 );
6295 let ix = transaction_context.get_next_instruction_context().unwrap();
6296 let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6297
6298 let vote_state =
6300 get_vote_state_handler_checked(&borrowed, VoteStateTargetVersion::V4).unwrap();
6301 vote_state.set_vote_account_state(&mut borrowed).unwrap();
6302
6303 let v4 = VoteStateHandler::new_v4(
6304 VoteStateV4::deserialize(borrowed.get_data(), &vote_pubkey).unwrap(),
6305 );
6306 assert_eq!(v4.as_ref_v4().bls_pubkey_compressed, None);
6307 assert!(!v4.has_bls_pubkey());
6308 assert_eq!(v4.as_ref_v4().last_timestamp, last_timestamp);
6309 }
6310
6311 #[test]
6312 fn test_bls_overwrite_via_voter_with_bls() {
6313 let vote_pubkey = Pubkey::new_unique();
6315 let (bls_a, pop_a) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
6316 let (bls_b, pop_b) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
6317
6318 let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6319 let withdrawer = *vote_state.authorized_withdrawer();
6320 let node_pubkey = *vote_state.node_pubkey();
6321 let serialized = vote_state.serialize();
6322 let rent = Rent::default();
6323 let lamports = rent.minimum_balance(serialized.len());
6324 let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6325 vote_account.set_data_from_slice(&serialized);
6326 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6327 let transaction_context = new_transaction_context(
6328 vec![(id(), program_account), (vote_pubkey, vote_account)],
6329 vec![InstructionAccount::new(1, false, true)],
6330 &rent,
6331 );
6332 let ix = transaction_context.get_next_instruction_context().unwrap();
6333 let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6334
6335 let signers: HashSet<Pubkey> = [withdrawer, Pubkey::new_unique()].into_iter().collect();
6336
6337 authorize(
6339 &mut borrowed,
6340 VoteStateTargetVersion::V4,
6341 &Pubkey::new_unique(),
6342 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6343 bls_pubkey: bls_a,
6344 bls_proof_of_possession: pop_a,
6345 }),
6346 &signers,
6347 &Clock::default(),
6348 true,
6349 || Ok(()),
6350 )
6351 .unwrap();
6352
6353 let v4 = VoteStateV4::deserialize(borrowed.get_data(), &node_pubkey).unwrap();
6354 assert_eq!(v4.bls_pubkey_compressed, Some(bls_a));
6355
6356 let clock = Clock {
6358 epoch: 3,
6359 ..Clock::default()
6360 };
6361 authorize(
6362 &mut borrowed,
6363 VoteStateTargetVersion::V4,
6364 &Pubkey::new_unique(),
6365 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6366 bls_pubkey: bls_b,
6367 bls_proof_of_possession: pop_b,
6368 }),
6369 &signers,
6370 &clock,
6371 true,
6372 || Ok(()),
6373 )
6374 .unwrap();
6375
6376 let v4 = VoteStateV4::deserialize(borrowed.get_data(), &node_pubkey).unwrap();
6377 assert_eq!(v4.bls_pubkey_compressed, Some(bls_b));
6378 }
6379
6380 #[test]
6381 fn test_bls_pop_cryptographic_failures() {
6382 let vote_pubkey = Pubkey::new_unique();
6385 let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6386 let withdrawer = *vote_state.authorized_withdrawer();
6387 let serialized = vote_state.serialize();
6388 let rent = Rent::default();
6389 let lamports = rent.minimum_balance(serialized.len());
6390 let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6391 vote_account.set_data_from_slice(&serialized);
6392 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6393 let transaction_context = new_transaction_context(
6394 vec![(id(), program_account), (vote_pubkey, vote_account)],
6395 vec![InstructionAccount::new(1, false, true)],
6396 &rent,
6397 );
6398 let ix = transaction_context.get_next_instruction_context().unwrap();
6399 let mut borrowed = ix.try_borrow_instruction_account(0).unwrap();
6400 let signers: HashSet<Pubkey> = [withdrawer, Pubkey::new_unique()].into_iter().collect();
6401 let clock = Clock::default();
6402
6403 assert_eq!(
6405 authorize(
6406 &mut borrowed,
6407 VoteStateTargetVersion::V4,
6408 &Pubkey::new_unique(),
6409 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6410 bls_pubkey: [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
6411 bls_proof_of_possession: [0u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
6412 }),
6413 &signers,
6414 &clock,
6415 true,
6416 || Ok(()),
6417 ),
6418 Err(InstructionError::InvalidArgument)
6419 );
6420
6421 assert_eq!(
6423 authorize(
6424 &mut borrowed,
6425 VoteStateTargetVersion::V4,
6426 &Pubkey::new_unique(),
6427 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6428 bls_pubkey: [0xAB; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
6429 bls_proof_of_possession: [0xCD; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
6430 }),
6431 &signers,
6432 &clock,
6433 true,
6434 || Ok(()),
6435 ),
6436 Err(InstructionError::InvalidArgument)
6437 );
6438
6439 let other_vote = Pubkey::new_unique();
6441 let (bls_for_other, pop_for_other) = create_bls_pubkey_and_proof_of_possession(&other_vote);
6442 assert_eq!(
6443 authorize(
6444 &mut borrowed,
6445 VoteStateTargetVersion::V4,
6446 &Pubkey::new_unique(),
6447 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
6448 bls_pubkey: bls_for_other,
6449 bls_proof_of_possession: pop_for_other,
6450 }),
6451 &signers,
6452 &clock,
6453 true,
6454 || Ok(()),
6455 ),
6456 Err(InstructionError::InvalidArgument)
6457 );
6458 }
6459
6460 #[test]
6461 fn test_collector_fields_immutable_in_v4_only_scope() {
6462 let vote_pubkey = solana_pubkey::new_rand();
6465 let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6466 let withdrawer = *vote_state.authorized_withdrawer();
6467 let original_inflation_collector = vote_state.as_ref_v4().inflation_rewards_collector;
6468 let original_block_revenue_collector = vote_state.as_ref_v4().block_revenue_collector;
6469
6470 let serialized = vote_state.clone().serialize();
6471 let rent = Rent::default();
6472 let lamports = rent.minimum_balance(serialized.len());
6473 let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6474 vote_account.set_data_from_slice(&serialized);
6475 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6476 let transaction_context = new_transaction_context(
6477 vec![(id(), program_account), (vote_pubkey, vote_account)],
6478 vec![InstructionAccount::new(1, false, true)],
6479 &rent,
6480 );
6481 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
6482 let mut borrowed = instruction_context
6483 .try_borrow_instruction_account(0)
6484 .unwrap();
6485
6486 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
6487 let clock = Clock::default();
6488
6489 authorize(
6491 &mut borrowed,
6492 VoteStateTargetVersion::V4,
6493 &solana_pubkey::new_rand(),
6494 VoteAuthorize::Voter,
6495 &signers,
6496 &clock,
6497 false,
6498 || Ok(()),
6499 )
6500 .unwrap();
6501
6502 let v4 = VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6503 .unwrap();
6504 assert_eq!(v4.inflation_rewards_collector, original_inflation_collector);
6505 assert_eq!(v4.block_revenue_collector, original_block_revenue_collector);
6506
6507 update_commission(
6509 &mut borrowed,
6510 VoteStateTargetVersion::V4,
6511 50,
6512 &signers,
6513 &solana_epoch_schedule::EpochSchedule::without_warmup(),
6514 &clock,
6515 false,
6516 )
6517 .unwrap();
6518
6519 let v4 = VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6520 .unwrap();
6521 assert_eq!(v4.inflation_rewards_collector, original_inflation_collector);
6522 assert_eq!(v4.block_revenue_collector, original_block_revenue_collector);
6523 }
6524
6525 #[test]
6526 fn test_pending_delegator_rewards_zero_in_v4_only_scope() {
6527 let vote_pubkey = solana_pubkey::new_rand();
6530 let vote_state = vote_state_new_for_test(&vote_pubkey, VoteStateTargetVersion::V4);
6531 let withdrawer = *vote_state.authorized_withdrawer();
6532
6533 let serialized = vote_state.clone().serialize();
6534 let rent = Rent::default();
6535 let lamports = rent.minimum_balance(serialized.len());
6536 let mut vote_account = AccountSharedData::new(lamports, serialized.len(), &id());
6537 vote_account.set_data_from_slice(&serialized);
6538 let program_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id());
6539 let transaction_context = new_transaction_context(
6540 vec![(id(), program_account), (vote_pubkey, vote_account)],
6541 vec![InstructionAccount::new(1, false, true)],
6542 &rent,
6543 );
6544 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
6545 let mut borrowed = instruction_context
6546 .try_borrow_instruction_account(0)
6547 .unwrap();
6548
6549 assert_eq!(
6550 VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6551 .unwrap()
6552 .pending_delegator_rewards,
6553 0
6554 );
6555
6556 let signers: HashSet<Pubkey> = [withdrawer].into_iter().collect();
6558 authorize(
6559 &mut borrowed,
6560 VoteStateTargetVersion::V4,
6561 &solana_pubkey::new_rand(),
6562 VoteAuthorize::Voter,
6563 &signers,
6564 &Clock::default(),
6565 false,
6566 || Ok(()),
6567 )
6568 .unwrap();
6569
6570 assert_eq!(
6571 VoteStateV4::deserialize(borrowed.get_data(), &vote_state.as_ref_v4().node_pubkey)
6572 .unwrap()
6573 .pending_delegator_rewards,
6574 0
6575 );
6576 }
6577}