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