1#[cfg(feature = "dev-context-only-utils")]
14use qualifier_attr::qualifiers;
15#[cfg(test)]
16use solana_vote_interface::authorized_voters::AuthorizedVoters;
17use {
18 solana_clock::{Clock, Epoch, Slot, UnixTimestamp},
19 solana_instruction::error::InstructionError,
20 solana_pubkey::Pubkey,
21 solana_transaction_context::instruction_accounts::BorrowedInstructionAccount,
22 solana_vote_interface::{
23 error::VoteError,
24 state::{
25 BLS_PUBLIC_KEY_COMPRESSED_SIZE, BlockTimestamp, LandedVote, Lockout,
26 MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, VOTE_CREDITS_GRACE_SLOTS,
27 VOTE_CREDITS_MAXIMUM_PER_SLOT, VoteInit, VoteInitV2, VoteStateV4, VoteStateVersions,
28 },
29 },
30 std::collections::VecDeque,
31};
32
33#[derive(Clone, Copy, Debug, PartialEq)]
35pub enum VoteStateTargetVersion {
36 V4,
37 }
39
40#[allow(clippy::large_enum_variant)]
41#[derive(Clone, Debug, PartialEq)]
42enum TargetVoteState {
43 V4(VoteStateV4),
44 }
46
47#[derive(Clone, Debug, PartialEq)]
53pub struct VoteStateHandler {
54 target_state: TargetVoteState,
55}
56
57impl VoteStateHandler {
58 pub(crate) fn authorized_withdrawer(&self) -> &Pubkey {
59 match &self.target_state {
60 TargetVoteState::V4(v4) => &v4.authorized_withdrawer,
61 }
62 }
63
64 pub(crate) fn set_authorized_withdrawer(&mut self, authorized_withdrawer: Pubkey) {
65 match &mut self.target_state {
66 TargetVoteState::V4(v4) => v4.authorized_withdrawer = authorized_withdrawer,
67 }
68 }
69
70 #[cfg(test)]
71 pub(crate) fn authorized_voters(&self) -> &AuthorizedVoters {
72 match &self.target_state {
73 TargetVoteState::V4(v4) => &v4.authorized_voters,
74 }
75 }
76
77 pub(crate) fn set_new_authorized_voter<F>(
78 &mut self,
79 authorized_pubkey: &Pubkey,
80 current_epoch: Epoch,
81 target_epoch: Epoch,
82 bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
83 verify: F,
84 ) -> Result<(), InstructionError>
85 where
86 F: Fn(Pubkey) -> Result<(), InstructionError>,
87 {
88 let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?;
89 verify(epoch_authorized_voter)?;
90
91 match &mut self.target_state {
92 TargetVoteState::V4(v4) => {
93 if v4.authorized_voters.contains(target_epoch) {
99 return Err(VoteError::TooSoonToReauthorize.into());
100 }
101
102 v4.authorized_voters
103 .insert(target_epoch, *authorized_pubkey);
104
105 if bls_pubkey.is_some() {
106 v4.bls_pubkey_compressed = bls_pubkey.copied();
107 }
108
109 Ok(())
110 }
111 }
112 }
113
114 pub(crate) fn get_and_update_authorized_voter(
115 &mut self,
116 current_epoch: Epoch,
117 ) -> Result<Pubkey, InstructionError> {
118 match &mut self.target_state {
119 TargetVoteState::V4(v4) => {
120 let pubkey = v4
121 .authorized_voters
122 .get_and_cache_authorized_voter_for_epoch(current_epoch)
123 .ok_or(InstructionError::InvalidAccountData)?;
124 v4.authorized_voters
128 .purge_authorized_voters(current_epoch.saturating_sub(1));
129 Ok(pubkey)
130 }
131 }
132 }
133
134 pub(crate) fn commission(&self) -> u8 {
135 match &self.target_state {
136 TargetVoteState::V4(v4) => {
137 (v4.inflation_rewards_commission_bps / 100).min(u8::MAX as u16) as u8
138 }
139 }
140 }
141
142 #[allow(clippy::arithmetic_side_effects)]
143 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
144 pub(crate) fn set_commission(&mut self, commission: u8) {
145 match &mut self.target_state {
146 TargetVoteState::V4(v4) => {
147 v4.inflation_rewards_commission_bps = (commission as u16) * 100;
149 }
150 }
151 }
152
153 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
154 pub(crate) fn set_inflation_rewards_commission_bps(&mut self, commission_bps: u16) {
155 match &mut self.target_state {
156 TargetVoteState::V4(v4) => v4.inflation_rewards_commission_bps = commission_bps,
157 }
158 }
159
160 pub(crate) fn set_block_revenue_commission_bps(&mut self, commission_bps: u16) {
161 match &mut self.target_state {
162 TargetVoteState::V4(v4) => v4.block_revenue_commission_bps = commission_bps,
163 }
164 }
165
166 pub fn node_pubkey(&self) -> &Pubkey {
167 match &self.target_state {
168 TargetVoteState::V4(v4) => &v4.node_pubkey,
169 }
170 }
171
172 pub(crate) fn set_node_pubkey(&mut self, node_pubkey: Pubkey) {
173 match &mut self.target_state {
174 TargetVoteState::V4(v4) => v4.node_pubkey = node_pubkey,
175 }
176 }
177
178 pub(crate) fn set_inflation_rewards_collector(&mut self, collector: Pubkey) {
179 match &mut self.target_state {
180 TargetVoteState::V4(v4) => v4.inflation_rewards_collector = collector,
181 }
182 }
183
184 pub(crate) fn set_block_revenue_collector(&mut self, collector: Pubkey) {
185 match &mut self.target_state {
186 TargetVoteState::V4(v4) => v4.block_revenue_collector = collector,
187 }
188 }
189
190 pub(crate) fn pending_delegator_rewards(&self) -> u64 {
191 match &self.target_state {
192 TargetVoteState::V4(v4) => v4.pending_delegator_rewards,
193 }
194 }
195
196 pub(crate) fn add_pending_delegator_rewards(
197 &mut self,
198 amount: u64,
199 ) -> Result<(), InstructionError> {
200 match &mut self.target_state {
201 TargetVoteState::V4(v4) => {
202 v4.pending_delegator_rewards = v4
203 .pending_delegator_rewards
204 .checked_add(amount)
205 .ok_or(InstructionError::ArithmeticOverflow)?;
206 Ok(())
207 }
208 }
209 }
210
211 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
212 pub(crate) fn votes(&self) -> &VecDeque<LandedVote> {
213 match &self.target_state {
214 TargetVoteState::V4(v4) => &v4.votes,
215 }
216 }
217
218 pub(crate) fn votes_mut(&mut self) -> &mut VecDeque<LandedVote> {
219 match &mut self.target_state {
220 TargetVoteState::V4(v4) => &mut v4.votes,
221 }
222 }
223
224 pub fn set_votes(&mut self, votes: VecDeque<LandedVote>) {
225 match &mut self.target_state {
226 TargetVoteState::V4(v4) => v4.votes = votes,
227 }
228 }
229
230 pub(crate) fn contains_slot(&self, candidate_slot: Slot) -> bool {
231 match &self.target_state {
232 TargetVoteState::V4(v4) => v4
233 .votes
234 .binary_search_by(|vote| vote.slot().cmp(&candidate_slot))
235 .is_ok(),
236 }
237 }
238
239 pub(crate) fn last_lockout(&self) -> Option<&Lockout> {
240 match &self.target_state {
241 TargetVoteState::V4(v4) => v4.votes.back().map(|vote| &vote.lockout),
242 }
243 }
244
245 pub fn last_voted_slot(&self) -> Option<Slot> {
246 self.last_lockout().map(|v| v.slot())
247 }
248
249 pub fn root_slot(&self) -> Option<Slot> {
250 match &self.target_state {
251 TargetVoteState::V4(v4) => v4.root_slot,
252 }
253 }
254
255 pub fn set_root_slot(&mut self, root_slot: Option<Slot>) {
256 match &mut self.target_state {
257 TargetVoteState::V4(v4) => v4.root_slot = root_slot,
258 }
259 }
260
261 pub(crate) fn current_epoch(&self) -> Epoch {
262 match &self.target_state {
263 TargetVoteState::V4(v4) => {
264 if v4.epoch_credits.is_empty() {
265 0
266 } else {
267 v4.epoch_credits.last().unwrap().0
268 }
269 }
270 }
271 }
272
273 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
274 pub(crate) fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
275 match &self.target_state {
276 TargetVoteState::V4(v4) => &v4.epoch_credits,
277 }
278 }
279
280 pub fn epoch_credits_mut(&mut self) -> &mut Vec<(Epoch, u64, u64)> {
281 match &mut self.target_state {
282 TargetVoteState::V4(v4) => &mut v4.epoch_credits,
283 }
284 }
285
286 pub fn last_timestamp(&self) -> &BlockTimestamp {
287 match &self.target_state {
288 TargetVoteState::V4(v4) => &v4.last_timestamp,
289 }
290 }
291
292 pub fn set_last_timestamp(&mut self, timestamp: BlockTimestamp) {
293 match &mut self.target_state {
294 TargetVoteState::V4(v4) => v4.last_timestamp = timestamp,
295 }
296 }
297
298 pub(crate) fn set_vote_account_state(
299 self,
300 vote_account: &mut BorrowedInstructionAccount,
301 ) -> Result<(), InstructionError> {
302 match self.target_state {
303 TargetVoteState::V4(v4) => {
304 if (vote_account.get_data().len() < VoteStateV4::size_of())
307 && (!vote_account.is_rent_exempt_at_data_length(VoteStateV4::size_of())
308 || vote_account
309 .set_data_length(VoteStateV4::size_of())
310 .is_err())
311 {
312 return Err(InstructionError::AccountNotRentExempt);
315 }
316 vote_account.set_state(&VoteStateVersions::V4(Box::new(v4)))
318 }
319 }
320 }
321
322 pub(crate) fn has_bls_pubkey(&self) -> bool {
323 match &self.target_state {
324 TargetVoteState::V4(v4) => v4.bls_pubkey_compressed.is_some(),
325 }
326 }
327
328 pub fn init_vote_account_state(
329 vote_account: &mut BorrowedInstructionAccount,
330 vote_init: &VoteInit,
331 clock: &Clock,
332 target_version: VoteStateTargetVersion,
333 ) -> Result<(), InstructionError> {
334 let handler = match target_version {
335 VoteStateTargetVersion::V4 => {
336 let vote_state =
337 VoteStateV4::new_with_defaults(vote_account.get_key(), vote_init, clock);
338 Self::new_v4(vote_state)
339 }
340 };
341 handler.set_vote_account_state(vote_account)
342 }
343
344 pub fn init_vote_account_state_v2(
345 vote_account: &mut BorrowedInstructionAccount,
346 vote_init: &VoteInitV2,
347 inflation_rewards_collector: &Pubkey,
348 block_revenue_collector: &Pubkey,
349 clock: &Clock,
350 target_version: VoteStateTargetVersion,
351 ) -> Result<(), InstructionError> {
352 let handler = match target_version {
353 VoteStateTargetVersion::V4 => {
354 let vote_state = VoteStateV4::new(
355 vote_init,
356 inflation_rewards_collector,
357 block_revenue_collector,
358 clock,
359 );
360 Self::new_v4(vote_state)
361 }
362 };
363 handler.set_vote_account_state(vote_account)
364 }
365
366 pub fn deinitialize_vote_account_state(
367 vote_account: &mut BorrowedInstructionAccount,
368 target_version: VoteStateTargetVersion,
369 ) -> Result<(), InstructionError> {
370 match target_version {
371 VoteStateTargetVersion::V4 => {
372 vote_account.get_data_mut()?.fill(0);
374 Ok(())
375 }
376 }
377 }
378
379 pub fn check_vote_account_length(
380 vote_account: &mut BorrowedInstructionAccount,
381 target_version: VoteStateTargetVersion,
382 ) -> Result<(), InstructionError> {
383 let length = vote_account.get_data().len();
384 let expected = match target_version {
385 VoteStateTargetVersion::V4 => VoteStateV4::size_of(),
386 };
387 if length != expected {
388 Err(InstructionError::InvalidAccountData)
389 } else {
390 Ok(())
391 }
392 }
393
394 pub(crate) fn credits_for_vote_at_index(&self, index: usize) -> u64 {
395 let latency = self
396 .votes()
397 .get(index)
398 .map_or(0, |landed_vote| landed_vote.latency);
399
400 if latency == 0 {
403 1
404 } else {
405 match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) {
406 None | Some(0) => {
407 VOTE_CREDITS_MAXIMUM_PER_SLOT as u64
409 }
410
411 Some(diff) => {
412 match VOTE_CREDITS_MAXIMUM_PER_SLOT.checked_sub(diff) {
415 None | Some(0) => 1,
417
418 Some(credits) => credits as u64,
419 }
420 }
421 }
422 }
423 }
424
425 pub fn increment_credits(&mut self, epoch: Epoch, credits: u64) {
426 if self.epoch_credits().is_empty() {
430 self.epoch_credits_mut().push((epoch, 0, 0));
431 } else if epoch != self.epoch_credits().last().unwrap().0 {
432 let (_, credits, prev_credits) = *self.epoch_credits().last().unwrap();
433
434 if credits != prev_credits {
435 self.epoch_credits_mut().push((epoch, credits, credits));
438 } else {
439 self.epoch_credits_mut().last_mut().unwrap().0 = epoch;
441 }
442
443 if self.epoch_credits().len() > MAX_EPOCH_CREDITS_HISTORY {
445 self.epoch_credits_mut().remove(0);
446 }
447 }
448
449 self.epoch_credits_mut().last_mut().unwrap().1 = self
450 .epoch_credits()
451 .last()
452 .unwrap()
453 .1
454 .saturating_add(credits);
455 }
456
457 pub(crate) fn process_timestamp(
458 &mut self,
459 slot: Slot,
460 timestamp: UnixTimestamp,
461 ) -> Result<(), VoteError> {
462 let last_timestamp = self.last_timestamp();
463 if (slot < last_timestamp.slot || timestamp < last_timestamp.timestamp)
464 || (slot == last_timestamp.slot
465 && &BlockTimestamp { slot, timestamp } != last_timestamp
466 && last_timestamp.slot != 0)
467 {
468 return Err(VoteError::TimestampTooOld);
469 }
470 self.set_last_timestamp(BlockTimestamp { slot, timestamp });
471 Ok(())
472 }
473
474 fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
475 while let Some(vote) = self.last_lockout() {
476 if !vote.is_locked_out_at_slot(next_vote_slot) {
477 self.votes_mut().pop_back();
478 } else {
479 break;
480 }
481 }
482 }
483
484 fn double_lockouts(&mut self) {
485 let stack_depth = self.votes().len();
486 for (i, v) in self.votes_mut().iter_mut().enumerate() {
487 if stack_depth
490 > i.checked_add(v.confirmation_count() as usize).expect(
491 "`confirmation_count` and tower_size should be bounded by \
492 `MAX_LOCKOUT_HISTORY`",
493 )
494 {
495 v.lockout.increase_confirmation_count(1);
496 }
497 }
498 }
499
500 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
501 pub(crate) fn process_next_vote_slot(
502 &mut self,
503 next_vote_slot: Slot,
504 epoch: Epoch,
505 current_slot: Slot,
506 ) {
507 if self
509 .last_voted_slot()
510 .is_some_and(|last_voted_slot| next_vote_slot <= last_voted_slot)
511 {
512 return;
513 }
514
515 self.pop_expired_votes(next_vote_slot);
516
517 let landed_vote = LandedVote {
518 latency: compute_vote_latency(next_vote_slot, current_slot),
519 lockout: Lockout::new(next_vote_slot),
520 };
521
522 if self.votes().len() == MAX_LOCKOUT_HISTORY {
524 let credits = self.credits_for_vote_at_index(0);
525 let landed_vote = self.votes_mut().pop_front().unwrap();
526 self.set_root_slot(Some(landed_vote.slot()));
527
528 self.increment_credits(epoch, credits);
529 }
530 self.votes_mut().push_back(landed_vote);
531 self.double_lockouts();
532 }
533
534 #[cfg(test)]
535 pub(crate) fn credits(&self) -> u64 {
536 if self.epoch_credits().is_empty() {
537 0
538 } else {
539 self.epoch_credits().last().unwrap().1
540 }
541 }
542
543 #[cfg(test)]
544 pub(crate) fn nth_recent_lockout(&self, position: usize) -> Option<&Lockout> {
545 if position < self.votes().len() {
546 let pos = self
547 .votes()
548 .len()
549 .checked_sub(position)
550 .and_then(|pos| pos.checked_sub(1))?;
551 self.votes().get(pos).map(|vote| &vote.lockout)
552 } else {
553 None
554 }
555 }
556
557 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
558 pub(crate) fn new_v4(vote_state: VoteStateV4) -> Self {
559 Self {
560 target_state: TargetVoteState::V4(vote_state),
561 }
562 }
563
564 pub fn try_new_from_vote_state_versions(
569 versions: VoteStateVersions,
570 ) -> Result<Self, InstructionError> {
571 match versions {
572 VoteStateVersions::V4(state) => Ok(Self::new_v4(*state)),
573 VoteStateVersions::V3(_)
574 | VoteStateVersions::V1_14_11(_)
575 | VoteStateVersions::Uninitialized => Err(InstructionError::InvalidAccountData),
576 }
577 }
578
579 #[cfg(any(test, feature = "dev-context-only-utils"))]
580 pub fn default_v4() -> Self {
581 Self::new_v4(VoteStateV4::default())
582 }
583
584 #[cfg(any(test, feature = "dev-context-only-utils"))]
585 pub fn as_ref_v4(&self) -> &VoteStateV4 {
586 match &self.target_state {
587 TargetVoteState::V4(v4) => v4,
588 }
589 }
590
591 #[cfg(any(test, feature = "dev-context-only-utils"))]
592 pub fn unwrap_v4(self) -> VoteStateV4 {
593 match self.target_state {
594 TargetVoteState::V4(v4) => v4,
595 }
596 }
597
598 pub fn serialize_into(self, data: &mut [u8]) -> Result<(), InstructionError> {
602 match self.target_state {
603 TargetVoteState::V4(v4) => {
604 let versioned = VoteStateVersions::V4(Box::new(v4));
605 bincode::serialize_into(data, &versioned)
606 .map_err(|_e| InstructionError::InvalidAccountData)
607 }
608 }
609 }
610
611 #[cfg(test)]
612 pub fn serialize(self) -> Vec<u8> {
613 let mut data = vec![0; VoteStateV4::size_of()];
614 self.serialize_into(&mut data).unwrap();
615 data
616 }
617}
618
619pub(crate) fn compute_vote_latency(voted_for_slot: Slot, current_slot: Slot) -> u8 {
621 std::cmp::min(current_slot.saturating_sub(voted_for_slot), u8::MAX as u64) as u8
622}
623
624pub(crate) fn try_convert_to_vote_state_v4(
625 versioned: VoteStateVersions,
626 vote_pubkey: &Pubkey,
627) -> Result<VoteStateV4, InstructionError> {
628 match versioned {
629 VoteStateVersions::Uninitialized => Err(InstructionError::UninitializedAccount),
630 VoteStateVersions::V1_14_11(state) => Ok(VoteStateV4 {
631 node_pubkey: state.node_pubkey,
632 authorized_withdrawer: state.authorized_withdrawer,
633 inflation_rewards_collector: *vote_pubkey,
634 block_revenue_collector: state.node_pubkey,
635 inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
636 block_revenue_commission_bps: 10_000u16,
637 pending_delegator_rewards: 0,
638 bls_pubkey_compressed: None,
639 votes: landed_votes_from_lockouts(state.votes),
640 root_slot: state.root_slot,
641 authorized_voters: state.authorized_voters.clone(),
642 epoch_credits: state.epoch_credits,
643 last_timestamp: state.last_timestamp,
644 }),
645 VoteStateVersions::V3(state) => Ok(VoteStateV4 {
646 node_pubkey: state.node_pubkey,
647 authorized_withdrawer: state.authorized_withdrawer,
648 inflation_rewards_collector: *vote_pubkey,
649 block_revenue_collector: state.node_pubkey,
650 inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
651 block_revenue_commission_bps: 10_000u16,
652 pending_delegator_rewards: 0,
653 bls_pubkey_compressed: None,
654 votes: state.votes,
655 root_slot: state.root_slot,
656 authorized_voters: state.authorized_voters,
657 epoch_credits: state.epoch_credits,
658 last_timestamp: state.last_timestamp,
659 }),
660 VoteStateVersions::V4(state) => Ok(*state),
661 }
662}
663
664fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
665 lockouts.into_iter().map(|lockout| lockout.into()).collect()
666}
667
668#[allow(clippy::arithmetic_side_effects)]
669#[allow(clippy::type_complexity)]
670#[cfg(test)]
671mod tests {
672 use {
673 super::*,
674 crate::id,
675 solana_account::AccountSharedData,
676 solana_clock::Clock,
677 solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
678 solana_pubkey::Pubkey,
679 solana_rent::Rent,
680 solana_sdk_ids::native_loader,
681 solana_transaction_context::{
682 instruction_accounts::InstructionAccount, transaction::TransactionContext,
683 },
684 solana_vote_interface::{
685 authorized_voters::AuthorizedVoters,
686 state::{
687 BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BlockTimestamp, MAX_EPOCH_CREDITS_HISTORY,
688 MAX_LOCKOUT_HISTORY, VoteInit, VoteState1_14_11, VoteStateV3,
689 },
690 },
691 std::collections::VecDeque,
692 test_case::test_case,
693 };
694
695 const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000;
697
698 fn mock_transaction_context(
699 vote_pubkey: Pubkey,
700 vote_account: AccountSharedData,
701 rent: Rent,
702 ) -> TransactionContext<'static> {
703 let program_account = AccountSharedData::new(0, 0, &native_loader::id());
704 let mut transaction_context = TransactionContext::new(
705 vec![(id(), program_account), (vote_pubkey, vote_account)],
706 rent,
707 0,
708 0,
709 1,
710 );
711 transaction_context
712 .configure_top_level_instruction_for_tests(
713 0,
714 vec![InstructionAccount::new(1, false, true)],
715 vec![],
716 )
717 .unwrap();
718 transaction_context
719 }
720
721 fn get_max_sized_vote_state_v4() -> VoteStateV4 {
722 let mut authorized_voters = AuthorizedVoters::default();
723 for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
724 authorized_voters.insert(i, Pubkey::new_unique());
725 }
726
727 VoteStateV4 {
728 votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
729 root_slot: Some(u64::MAX),
730 epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
731 authorized_voters,
732 bls_pubkey_compressed: Some([255; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
733 ..Default::default()
734 }
735 }
736
737 fn set_new_authorized_voter_and_assert(
738 vote_state: &mut VoteStateHandler,
739 original_voter: Pubkey,
740 epoch_offset: Epoch,
741 ) {
742 let new_voter = Pubkey::new_unique();
743 vote_state
745 .set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(()))
746 .unwrap();
747
748 assert_eq!(
750 vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())),
751 Err(VoteError::TooSoonToReauthorize.into())
752 );
753
754 vote_state
756 .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, None, |_| Ok(()))
757 .unwrap();
758
759 let new_voter2 = Pubkey::new_unique();
761 vote_state
762 .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, None, |_| Ok(()))
763 .unwrap();
764
765 let new_voter3 = Pubkey::new_unique();
766 vote_state
767 .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, None, |_| Ok(()))
768 .unwrap();
769
770 vote_state
772 .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, None, |_| Ok(()))
773 .unwrap();
774
775 for i in 9..epoch_offset {
778 assert_eq!(
779 vote_state.get_and_update_authorized_voter(i).unwrap(),
780 original_voter
781 );
782 }
783 for i in epoch_offset..3 + epoch_offset {
784 assert_eq!(
785 vote_state.get_and_update_authorized_voter(i).unwrap(),
786 new_voter
787 );
788 }
789 for i in 3 + epoch_offset..6 + epoch_offset {
790 assert_eq!(
791 vote_state.get_and_update_authorized_voter(i).unwrap(),
792 new_voter2
793 );
794 }
795 for i in 6 + epoch_offset..9 + epoch_offset {
796 assert_eq!(
797 vote_state.get_and_update_authorized_voter(i).unwrap(),
798 new_voter3
799 );
800 }
801 for i in 9 + epoch_offset..=10 + epoch_offset {
802 assert_eq!(
803 vote_state.get_and_update_authorized_voter(i).unwrap(),
804 original_voter
805 );
806 }
807 }
808
809 #[test]
810 fn test_set_new_authorized_voter() {
811 let vote_pubkey = Pubkey::new_unique();
812 let original_voter = Pubkey::new_unique();
813 let epoch_offset = 15;
814
815 let vote_init = VoteInit {
816 node_pubkey: original_voter,
817 authorized_voter: original_voter,
818 authorized_withdrawer: original_voter,
819 commission: 0,
820 };
821 let clock = Clock::default();
822
823 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
824 &vote_pubkey,
825 &vote_init,
826 &clock,
827 ));
828
829 set_new_authorized_voter_and_assert(&mut vote_state, original_voter, epoch_offset);
830
831 {
832 let voter_a = Pubkey::new_unique();
833 let voter_b = Pubkey::new_unique();
834
835 let v4 = VoteStateV4 {
836 authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
837 ..Default::default()
838 };
839 let mut handler = VoteStateHandler::new_v4(v4);
840 handler
841 .set_new_authorized_voter(&voter_a, 0, 10, None, |_| Ok(()))
842 .unwrap();
843 handler
844 .set_new_authorized_voter(&voter_b, 0, 5, None, |_| Ok(()))
845 .unwrap();
846 }
847 }
848
849 fn assert_authorized_voter_is_locked_within_epoch(
850 vote_state: &mut VoteStateHandler,
851 original_voter: &Pubkey,
852 ) {
853 let new_voter = Pubkey::new_unique();
857 assert_eq!(
858 vote_state.set_new_authorized_voter(&new_voter, 1, 1, None, |_| Ok(())),
859 Err(VoteError::TooSoonToReauthorize.into())
860 );
861 assert_eq!(
862 vote_state.authorized_voters().get_authorized_voter(1),
863 Some(*original_voter)
864 );
865 assert_eq!(
867 vote_state.set_new_authorized_voter(&new_voter, 1, 2, None, |_| Ok(())),
868 Ok(())
869 );
870 assert_eq!(
874 vote_state.set_new_authorized_voter(original_voter, 3, 3, None, |_| Ok(())),
875 Err(VoteError::TooSoonToReauthorize.into())
876 );
877 assert_eq!(
878 vote_state.authorized_voters().get_authorized_voter(3),
879 Some(new_voter)
880 );
881 }
882
883 #[test]
884 fn test_authorized_voter_is_locked_within_epoch() {
885 let vote_pubkey = Pubkey::new_unique();
886 let original_voter = Pubkey::new_unique();
887
888 let vote_init = VoteInit {
889 node_pubkey: original_voter,
890 authorized_voter: original_voter,
891 authorized_withdrawer: original_voter,
892 commission: 0,
893 };
894 let clock = Clock::default();
895
896 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
897 &vote_pubkey,
898 &vote_init,
899 &clock,
900 ));
901 assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter);
902 }
903
904 #[test]
905 fn test_get_and_update_authorized_voter() {
906 let vote_pubkey = Pubkey::new_unique();
907 let original_voter = Pubkey::new_unique();
908 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
909 &vote_pubkey,
910 &VoteInit {
911 node_pubkey: original_voter,
912 authorized_voter: original_voter,
913 authorized_withdrawer: original_voter,
914 commission: 0,
915 },
916 &Clock::default(),
917 ));
918
919 assert_eq!(vote_state.authorized_voters().len(), 1);
922 assert_eq!(
923 *vote_state.authorized_voters().first().unwrap().1,
924 original_voter
925 );
926
927 assert_eq!(
930 vote_state.get_and_update_authorized_voter(1).unwrap(),
931 original_voter
932 );
933
934 assert_eq!(
937 vote_state.get_and_update_authorized_voter(5).unwrap(),
938 original_voter
939 );
940
941 assert_eq!(vote_state.authorized_voters().len(), 1);
944 for i in 0..5 {
945 assert!(
946 vote_state
947 .authorized_voters()
948 .get_authorized_voter(i)
949 .is_none()
950 );
951 }
952
953 assert_eq!(
955 vote_state.get_and_update_authorized_voter(6).unwrap(),
956 original_voter
957 );
958 assert_eq!(
959 vote_state.get_and_update_authorized_voter(7).unwrap(),
960 original_voter
961 );
962
963 assert_eq!(vote_state.authorized_voters().len(), 2);
965
966 for i in 0..=5 {
968 assert!(
969 vote_state
970 .authorized_voters()
971 .get_authorized_voter(i)
972 .is_none()
973 );
974 }
975
976 let new_authorized_voter = Pubkey::new_unique();
978 vote_state
979 .set_new_authorized_voter(&new_authorized_voter, 7, 9, None, |_| Ok(()))
980 .unwrap();
981
982 assert_eq!(
984 vote_state.get_and_update_authorized_voter(8).unwrap(),
985 original_voter
986 );
987
988 for i in 9..12 {
991 assert_eq!(
992 vote_state.get_and_update_authorized_voter(i).unwrap(),
993 new_authorized_voter
994 );
995 }
996 assert_eq!(vote_state.authorized_voters().len(), 2);
997
998 assert_eq!(
1000 vote_state.get_and_update_authorized_voter(15).unwrap(),
1001 new_authorized_voter
1002 );
1003 assert_eq!(vote_state.authorized_voters().len(), 1);
1004
1005 {
1008 let voter_10 = Pubkey::new_unique();
1009 let voter_15 = Pubkey::new_unique();
1010 let mut v4 = VoteStateV4 {
1011 authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
1012 ..Default::default()
1013 };
1014 v4.authorized_voters.insert(10, voter_10);
1015 v4.authorized_voters.insert(15, voter_15);
1016 let mut handler = VoteStateHandler::new_v4(v4);
1017 let v4_voter = handler.get_and_update_authorized_voter(11).unwrap();
1018 assert_eq!(v4_voter, voter_10);
1019 assert!(
1021 handler
1022 .authorized_voters()
1023 .get_authorized_voter(10)
1024 .is_some()
1025 );
1026 }
1027
1028 {
1030 let voter = Pubkey::new_unique();
1031 let v4 = VoteStateV4 {
1032 authorized_voters: AuthorizedVoters::new(0, voter),
1033 ..Default::default()
1034 };
1035 let mut handler = VoteStateHandler::new_v4(v4);
1036 let result = handler.get_and_update_authorized_voter(0).unwrap();
1037 assert_eq!(result, voter);
1038 assert!(!handler.authorized_voters().is_empty());
1039 }
1040 }
1041
1042 #[test_case(
1043 VoteStateV4::size_of(),
1044 VoteStateHandler::new_v4(get_max_sized_vote_state_v4()),
1045 |vote_state, data| {
1046 let versioned = VoteStateVersions::new_v4(vote_state.unwrap_v4());
1047 VoteStateV4::serialize(&versioned, data).unwrap();
1048 };
1049 "VoteStateV4"
1050 )]
1051 fn test_vote_state_max_size(
1052 max_size: usize,
1053 mut vote_state: VoteStateHandler,
1054 verify_serialize: fn(VoteStateHandler, &mut [u8]),
1055 ) {
1056 let mut max_sized_data = vec![0; max_size];
1057 let (start_leader_schedule_epoch, _) = vote_state.authorized_voters().last().unwrap();
1058 let start_current_epoch =
1059 start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1;
1060
1061 for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
1062 vote_state
1063 .set_new_authorized_voter(
1064 &Pubkey::new_unique(),
1065 i,
1066 i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
1067 None,
1068 |_| Ok(()),
1069 )
1070 .unwrap();
1071
1072 verify_serialize(vote_state.clone(), &mut max_sized_data);
1073 }
1074 }
1075
1076 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1077 fn test_vote_state_epoch_credits(mut vote_state: VoteStateHandler) {
1078 assert_eq!(vote_state.credits(), 0);
1079 assert_eq!(vote_state.epoch_credits().clone(), vec![]);
1080
1081 let mut expected = vec![];
1082 let mut credits = 0;
1083 let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1084 for epoch in 0..epochs {
1085 for _j in 0..epoch {
1086 vote_state.increment_credits(epoch, 1);
1087 credits += 1;
1088 }
1089 expected.push((epoch, credits, credits - epoch));
1090 }
1091
1092 while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
1093 expected.remove(0);
1094 }
1095
1096 assert_eq!(vote_state.credits(), credits);
1097 assert_eq!(vote_state.epoch_credits().clone(), expected);
1098 }
1099
1100 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1101 fn test_vote_state_epoch0_no_credits(mut vote_state: VoteStateHandler) {
1102 assert_eq!(vote_state.epoch_credits().len(), 0);
1103 vote_state.increment_credits(1, 1);
1104 assert_eq!(vote_state.epoch_credits().len(), 1);
1105
1106 vote_state.increment_credits(2, 1);
1107 assert_eq!(vote_state.epoch_credits().len(), 2);
1108 }
1109
1110 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1111 fn test_vote_state_increment_credits(mut vote_state: VoteStateHandler) {
1112 let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1113 for i in 0..credits {
1114 vote_state.increment_credits(i, 1);
1115 }
1116 assert_eq!(vote_state.credits(), credits);
1117 assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
1118 }
1119
1120 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1121 fn test_vote_process_timestamp(mut vote_state: VoteStateHandler) {
1122 let (slot, timestamp) = (15, 1_575_412_285);
1123 vote_state.set_last_timestamp(BlockTimestamp { slot, timestamp });
1124
1125 assert_eq!(
1126 vote_state.process_timestamp(slot - 1, timestamp + 1),
1127 Err(VoteError::TimestampTooOld)
1128 );
1129 assert_eq!(
1130 vote_state.last_timestamp(),
1131 &BlockTimestamp { slot, timestamp }
1132 );
1133 assert_eq!(
1134 vote_state.process_timestamp(slot + 1, timestamp - 1),
1135 Err(VoteError::TimestampTooOld)
1136 );
1137 assert_eq!(
1138 vote_state.process_timestamp(slot, timestamp + 1),
1139 Err(VoteError::TimestampTooOld)
1140 );
1141 assert_eq!(vote_state.process_timestamp(slot, timestamp), Ok(()));
1142 assert_eq!(
1143 vote_state.last_timestamp(),
1144 &BlockTimestamp { slot, timestamp }
1145 );
1146 assert_eq!(vote_state.process_timestamp(slot + 1, timestamp), Ok(()));
1147 assert_eq!(
1148 vote_state.last_timestamp(),
1149 &BlockTimestamp {
1150 slot: slot + 1,
1151 timestamp
1152 }
1153 );
1154 assert_eq!(
1155 vote_state.process_timestamp(slot + 2, timestamp + 1),
1156 Ok(())
1157 );
1158 assert_eq!(
1159 vote_state.last_timestamp(),
1160 &BlockTimestamp {
1161 slot: slot + 2,
1162 timestamp: timestamp + 1
1163 }
1164 );
1165
1166 vote_state.set_last_timestamp(BlockTimestamp::default());
1168 assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
1169 }
1170
1171 fn init_vote_account_state_v4_and_assert(
1172 vote_pubkey: Pubkey,
1173 vote_account: AccountSharedData,
1174 vote_init: &VoteInit,
1175 clock: &Clock,
1176 rent: Rent,
1177 expected_result: Result<(), InstructionError>,
1178 ) {
1179 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1180 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1181 let mut vote_account = instruction_context
1182 .try_borrow_instruction_account(0)
1183 .unwrap();
1184
1185 let result = VoteStateHandler::init_vote_account_state(
1187 &mut vote_account,
1188 vote_init,
1189 clock,
1190 VoteStateTargetVersion::V4,
1191 );
1192 assert_eq!(result, expected_result);
1193
1194 if result.is_ok() {
1195 let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1196 assert!(matches!(vote_state_versions, VoteStateVersions::V4(_)));
1197 assert!(!vote_state_versions.is_uninitialized());
1198
1199 if let VoteStateVersions::V4(v4) = vote_state_versions {
1201 assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1202 assert_eq!(
1203 v4.authorized_voters.get_authorized_voter(clock.epoch),
1204 Some(vote_init.authorized_voter)
1205 );
1206 assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1207 assert_eq!(
1208 v4.inflation_rewards_commission_bps,
1209 (vote_init.commission as u16) * 100
1210 );
1211
1212 assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
1214 assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
1215 assert_eq!(
1216 v4.block_revenue_commission_bps,
1217 DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1218 );
1219
1220 assert_eq!(v4.pending_delegator_rewards, 0);
1222 assert_eq!(v4.bls_pubkey_compressed, None);
1223 assert!(v4.votes.is_empty());
1224 assert_eq!(v4.root_slot, None);
1225 assert!(v4.epoch_credits.is_empty());
1226 assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1227 } else {
1228 panic!("should be v4");
1229 }
1230 }
1231 }
1232
1233 fn deinit_vote_account_state_v4_and_assert(
1234 vote_pubkey: Pubkey,
1235 vote_account: AccountSharedData,
1236 rent: Rent,
1237 ) {
1238 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1239 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1240 let mut vote_account = instruction_context
1241 .try_borrow_instruction_account(0)
1242 .unwrap();
1243
1244 VoteStateHandler::deinitialize_vote_account_state(
1246 &mut vote_account,
1247 VoteStateTargetVersion::V4,
1248 )
1249 .unwrap();
1250
1251 let account_data = vote_account.get_data();
1253 assert!(account_data.iter().all(|&b| b == 0),);
1254
1255 let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1258 assert!(matches!(
1259 vote_state_versions,
1260 VoteStateVersions::Uninitialized
1261 ));
1262 assert!(vote_state_versions.is_uninitialized());
1263 }
1264
1265 #[test]
1266 fn test_init_vote_account_state_v4() {
1267 let vote_pubkey = Pubkey::new_unique();
1268 let vote_init = VoteInit {
1269 node_pubkey: Pubkey::new_unique(),
1270 authorized_voter: Pubkey::new_unique(),
1271 authorized_withdrawer: Pubkey::new_unique(),
1272 commission: 5,
1273 };
1274 let clock = Clock::default();
1275 let rent = Rent::default();
1276
1277 let v1_14_11_size = VoteState1_14_11::size_of();
1279 let lamports = rent.minimum_balance(v1_14_11_size);
1280 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1281
1282 init_vote_account_state_v4_and_assert(
1284 vote_pubkey,
1285 vote_account,
1286 &vote_init,
1287 &clock,
1288 rent.clone(),
1289 Err(InstructionError::AccountNotRentExempt),
1290 );
1291
1292 let v4_size = VoteStateV4::size_of();
1295 let lamports = rent.minimum_balance(v4_size);
1296 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1297
1298 init_vote_account_state_v4_and_assert(
1300 vote_pubkey,
1301 vote_account,
1302 &vote_init,
1303 &clock,
1304 rent.clone(),
1305 Ok(()),
1306 );
1307
1308 let lamports = rent.minimum_balance(v4_size);
1310 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1311
1312 init_vote_account_state_v4_and_assert(
1314 vote_pubkey,
1315 vote_account,
1316 &vote_init,
1317 &clock,
1318 rent,
1319 Ok(()),
1320 );
1321 }
1322
1323 #[test]
1324 fn test_deinitialize_vote_account_state_v4() {
1325 let vote_pubkey = Pubkey::new_unique();
1326 let rent = Rent::default();
1327
1328 let v1_14_11_size = VoteState1_14_11::size_of();
1330 let lamports = rent.minimum_balance(v1_14_11_size);
1331 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1332
1333 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1335
1336 let v4_size = VoteStateV4::size_of();
1339 let lamports = rent.minimum_balance(v4_size);
1340 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1341
1342 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1344
1345 let lamports = rent.minimum_balance(v4_size);
1347 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1348
1349 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent);
1351 }
1352
1353 #[test]
1354 fn test_v4_commission_basis_points() {
1355 let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1358
1359 for (input, expected) in [
1360 (0, 0),
1361 (1, 100),
1362 (5, 500),
1363 (10, 1_000),
1364 (25, 2_500),
1365 (50, 5_000),
1366 (75, 7_500),
1367 (100, 10_000),
1368 (255, 25_500),
1369 ] {
1370 handler.set_commission(input);
1371 assert_eq!(handler.commission(), input);
1372
1373 let vote_state_v4 = handler.as_ref_v4();
1375 assert_eq!(vote_state_v4.inflation_rewards_commission_bps, expected);
1376 }
1377 }
1378
1379 #[test]
1380 fn test_v4_commission_getter_clamps_extreme_bps() {
1381 for (bps, expected) in [
1386 (10_000, 100), (25_500, 255), (25_501, 255), (25_600, 255), (u16::MAX, 255), ] {
1392 let vote_state = VoteStateV4 {
1393 inflation_rewards_commission_bps: bps,
1394 ..Default::default()
1395 };
1396 let handler = VoteStateHandler::new_v4(vote_state);
1397 assert_eq!(handler.commission(), expected);
1398 }
1399 }
1400
1401 #[test]
1402 fn test_commission_v3_v4_preserved() {
1403 let vote_pubkey = Pubkey::new_unique();
1405 for n in [0u8, 1, 50, 100, 101, 255] {
1406 let v3 = VoteStateV3 {
1407 commission: n,
1408 ..Default::default()
1409 };
1410
1411 let versioned = VoteStateVersions::V3(Box::new(v3));
1412 let migrated = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1413 let migrated_handler = VoteStateHandler::new_v4(migrated);
1414 assert_eq!(migrated_handler.commission(), n);
1415 assert_eq!(
1416 migrated_handler
1417 .as_ref_v4()
1418 .inflation_rewards_commission_bps,
1419 n as u16 * 100
1420 );
1421 }
1422 }
1423
1424 #[test]
1425 fn test_v4_conversion_from_all_versions() {
1426 let vote_pubkey = Pubkey::new_unique();
1428 let node_pubkey = Pubkey::new_unique();
1429 let authorized_voter = Pubkey::new_unique();
1430 let authorized_withdrawer = Pubkey::new_unique();
1431 let commission = 42u8;
1432 let votes = vec![Lockout::new(100)];
1433 let votes_deque: VecDeque<Lockout> = votes.iter().cloned().collect();
1434 let epoch_credits = vec![(5, 200, 100)];
1435 let root_slot = Some(50);
1436
1437 let verify_v4_defaults = |v4_state: &VoteStateV4, expected_commission_bps: u16| {
1439 assert_eq!(
1440 v4_state.inflation_rewards_commission_bps,
1441 expected_commission_bps
1442 );
1443 assert_eq!(v4_state.inflation_rewards_collector, vote_pubkey);
1444 assert_eq!(v4_state.block_revenue_collector, node_pubkey);
1445 assert_eq!(
1446 v4_state.block_revenue_commission_bps,
1447 DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1448 );
1449 assert_eq!(v4_state.pending_delegator_rewards, 0);
1450 assert_eq!(v4_state.bls_pubkey_compressed, None);
1451 };
1452
1453 {
1460 let vote_state_v2 = VoteState1_14_11 {
1461 node_pubkey,
1462 authorized_withdrawer,
1463 commission,
1464 authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1465 votes: votes_deque,
1466 epoch_credits: epoch_credits.clone(),
1467 root_slot,
1468 ..VoteState1_14_11::default()
1469 };
1470
1471 let versioned = VoteStateVersions::V1_14_11(Box::new(vote_state_v2.clone()));
1472 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1473
1474 assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1476 assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1477 assert_eq!(
1478 vote_state_v4.authorized_voters,
1479 vote_state_v2.authorized_voters
1480 );
1481 assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1482 assert_eq!(vote_state_v4.root_slot, root_slot);
1483 assert_eq!(vote_state_v4.votes.len(), vote_state_v2.votes.len());
1484 for (v4_vote, v2_vote) in vote_state_v4.votes.iter().zip(vote_state_v2.votes.iter()) {
1485 assert_eq!(v4_vote.lockout, *v2_vote);
1486 }
1487
1488 verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1490 }
1491
1492 {
1494 let vote_init = VoteInit {
1495 node_pubkey,
1496 authorized_voter,
1497 authorized_withdrawer,
1498 commission,
1499 };
1500 let mut vote_state_v3 = VoteStateV3::new(&vote_init, &Clock::default());
1501 for lockout in &votes {
1502 vote_state_v3.votes.push_back(LandedVote {
1503 latency: 1,
1504 lockout: *lockout,
1505 });
1506 }
1507 vote_state_v3.epoch_credits = epoch_credits.clone();
1508 vote_state_v3.root_slot = root_slot;
1509
1510 let versioned = VoteStateVersions::V3(Box::new(vote_state_v3.clone()));
1511 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1512
1513 assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1515 assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1516 assert_eq!(
1517 vote_state_v4.authorized_voters,
1518 vote_state_v3.authorized_voters
1519 );
1520 assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1521 assert_eq!(vote_state_v4.root_slot, root_slot);
1522 assert_eq!(vote_state_v4.last_timestamp, vote_state_v3.last_timestamp);
1523 assert_eq!(vote_state_v4.votes, vote_state_v3.votes);
1524
1525 verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1527 }
1528
1529 {
1531 let mut initial_vote_state_v4 = VoteStateV4 {
1532 node_pubkey,
1533 authorized_withdrawer,
1534 inflation_rewards_commission_bps: 1234,
1535 block_revenue_commission_bps: 5678,
1536 inflation_rewards_collector: Pubkey::new_unique(),
1537 block_revenue_collector: Pubkey::new_unique(),
1538 pending_delegator_rewards: 999,
1539 bls_pubkey_compressed: Some([42; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
1540 authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1541 epoch_credits,
1542 root_slot,
1543 ..VoteStateV4::default()
1544 };
1545 for lockout in &votes {
1546 initial_vote_state_v4.votes.push_back(LandedVote {
1547 latency: 1,
1548 lockout: *lockout,
1549 });
1550 }
1551
1552 let versioned = VoteStateVersions::V4(Box::new(initial_vote_state_v4.clone()));
1553 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1554
1555 assert_eq!(vote_state_v4, initial_vote_state_v4);
1557 }
1558 }
1559
1560 #[test]
1561 fn test_v4_migration_with_pre_existing_voters() {
1562 let vote_pubkey = Pubkey::new_unique();
1565 let voter_5 = Pubkey::new_unique();
1566 let voter_7 = Pubkey::new_unique();
1567 let voter_9 = Pubkey::new_unique();
1568
1569 let build_voters = || {
1570 let mut voters = AuthorizedVoters::new(0, Pubkey::new_unique());
1571 voters.insert(5, voter_5);
1572 voters.insert(7, voter_7);
1573 voters.insert(9, voter_9);
1574 voters
1575 };
1576
1577 let assert_purge = |handler: &mut VoteStateHandler| {
1578 let voter = handler.get_and_update_authorized_voter(10).unwrap();
1580 assert_eq!(voter, voter_9);
1581 assert!(
1582 handler
1583 .authorized_voters()
1584 .get_authorized_voter(9)
1585 .is_some()
1586 );
1587 assert!(
1588 handler
1589 .authorized_voters()
1590 .get_authorized_voter(7)
1591 .is_none()
1592 );
1593 assert!(
1594 handler
1595 .authorized_voters()
1596 .get_authorized_voter(5)
1597 .is_none()
1598 );
1599 };
1600
1601 {
1603 let v1 = VoteState1_14_11 {
1604 authorized_voters: build_voters(),
1605 ..Default::default()
1606 };
1607 let versioned = VoteStateVersions::V1_14_11(Box::new(v1));
1608 let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1609 let mut handler = VoteStateHandler::new_v4(v4);
1610 assert_purge(&mut handler);
1611 }
1612
1613 {
1615 let v3 = VoteStateV3 {
1616 authorized_voters: build_voters(),
1617 ..Default::default()
1618 };
1619 let versioned = VoteStateVersions::V3(Box::new(v3));
1620 let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1621 let mut handler = VoteStateHandler::new_v4(v4);
1622 assert_purge(&mut handler);
1623 }
1624 }
1625
1626 #[test]
1627 fn test_v3_v4_size_equality() {
1628 let v3_size = VoteStateV3::size_of();
1629 let v4_size = VoteStateV4::size_of();
1630 assert_eq!(v3_size, v4_size, "V3 and V4 should have the same size");
1631 }
1632
1633 #[test_case(
1634 VoteState1_14_11::size_of(),
1635 false,
1636 Err(InstructionError::AccountNotRentExempt);
1637 "v1_14_11 size insufficient lamports - no fallback"
1638 )]
1639 #[test_case(
1640 VoteState1_14_11::size_of(),
1641 true,
1642 Ok(());
1643 "v1_14_11 size sufficient lamports - resize and succeed"
1644 )]
1645 #[test_case(
1646 VoteStateV4::size_of(),
1647 true,
1648 Ok(());
1649 "v4 properly sized with rent-exempt lamports - succeed"
1650 )]
1651 fn test_v4_resize_behavior(
1652 account_size: usize,
1653 sufficient_lamports: bool,
1654 expected_result: Result<(), InstructionError>,
1655 ) {
1656 let vote_pubkey = Pubkey::new_unique();
1657 let node_pubkey = Pubkey::new_unique();
1658 let authorized_voter = Pubkey::new_unique();
1659 let authorized_withdrawer = Pubkey::new_unique();
1660 let rent = Rent::default();
1661
1662 let vote_init = VoteInit {
1663 node_pubkey,
1664 authorized_voter,
1665 authorized_withdrawer,
1666 commission: 42,
1667 };
1668
1669 let lamports = if sufficient_lamports {
1670 rent.minimum_balance(VoteStateV4::size_of())
1671 } else {
1672 rent.minimum_balance(account_size)
1673 };
1674
1675 let mut vote_account = AccountSharedData::new(lamports, account_size, &id());
1676 vote_account.set_data_from_slice(&vec![0; account_size]);
1677
1678 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1679 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1680 let mut vote_account_borrowed = instruction_context
1681 .try_borrow_instruction_account(0)
1682 .unwrap();
1683
1684 let result = VoteStateHandler::init_vote_account_state(
1685 &mut vote_account_borrowed,
1686 &vote_init,
1687 &Clock::default(),
1688 VoteStateTargetVersion::V4,
1689 );
1690
1691 assert_eq!(result, expected_result);
1692 }
1693
1694 #[test]
1695 fn test_get_and_update_authorized_voter_v4_with_bls() {
1696 let vote_pubkey = Pubkey::new_unique();
1697 let original_voter = Pubkey::new_unique();
1698 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1699 &vote_pubkey,
1700 &VoteInit {
1701 node_pubkey: original_voter,
1702 authorized_voter: original_voter,
1703 authorized_withdrawer: original_voter,
1704 commission: 0,
1705 },
1706 &Clock::default(),
1707 ));
1708
1709 assert_eq!(vote_state.authorized_voters().len(), 1);
1711 assert_eq!(
1712 *vote_state.authorized_voters().first().unwrap().1,
1713 original_voter
1714 );
1715 assert!(!vote_state.has_bls_pubkey());
1716
1717 let new_voter = Pubkey::new_unique();
1719 let bls_pubkey_compressed = [3u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1720 vote_state
1721 .set_new_authorized_voter(&new_voter, 0, 1, Some(&bls_pubkey_compressed), |_| Ok(()))
1722 .unwrap();
1723 assert_eq!(vote_state.authorized_voters().len(), 2);
1724 assert_eq!(*vote_state.authorized_voters().last().unwrap().1, new_voter);
1725 assert_eq!(
1726 vote_state.as_ref_v4().bls_pubkey_compressed,
1727 Some(bls_pubkey_compressed)
1728 );
1729 assert!(vote_state.has_bls_pubkey());
1730
1731 let newer_voter = Pubkey::new_unique();
1733 let newer_bls_pubkey_compressed = [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1734 vote_state
1735 .set_new_authorized_voter(
1736 &newer_voter,
1737 1,
1738 2,
1739 Some(&newer_bls_pubkey_compressed),
1740 |_| Ok(()),
1741 )
1742 .unwrap();
1743 assert_eq!(vote_state.authorized_voters().len(), 3);
1744 assert_eq!(
1745 *vote_state.authorized_voters().last().unwrap().1,
1746 newer_voter
1747 );
1748 assert_eq!(
1749 vote_state.as_ref_v4().bls_pubkey_compressed,
1750 Some(newer_bls_pubkey_compressed)
1751 );
1752 assert!(vote_state.has_bls_pubkey());
1753 }
1754
1755 #[test]
1756 fn test_set_inflation_rewards_commission_bps() {
1757 let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1758
1759 for bps in [0, 100, 500, 1_000, 5_000, 10_000] {
1761 handler.set_inflation_rewards_commission_bps(bps);
1762 let v4 = handler.as_ref_v4();
1763 assert_eq!(v4.inflation_rewards_commission_bps, bps);
1764 assert_eq!(handler.commission(), (bps / 100) as u8);
1766 }
1767
1768 for bps in [10_001, 15_000, u16::MAX] {
1771 handler.set_inflation_rewards_commission_bps(bps);
1772 let v4 = handler.as_ref_v4();
1773 assert_eq!(v4.inflation_rewards_commission_bps, bps);
1774 }
1775 }
1776
1777 #[test]
1778 fn test_compute_vote_latency() {
1779 for (voted_for_slot, current_slot, expected) in [
1780 (0, 0, 0), (0, 1, 1), (0, 255, 255), (0, 256, 255), (0, 1_000, 255), (u64::MAX - 1, u64::MAX, 1), (5, 5, 0), (10, 5, 0), ] {
1789 assert_eq!(compute_vote_latency(voted_for_slot, current_slot), expected);
1790 }
1791 }
1792
1793 #[test]
1794 fn test_init_vote_account_state_v2() {
1795 let vote_pubkey = Pubkey::new_unique();
1796 let inflation_rewards_collector = Pubkey::new_unique();
1797 let block_revenue_collector = Pubkey::new_unique();
1798 let vote_init = VoteInitV2 {
1799 node_pubkey: Pubkey::new_unique(),
1800 authorized_voter: Pubkey::new_unique(),
1801 authorized_voter_bls_pubkey: [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1802 authorized_voter_bls_proof_of_possession: [8u8;
1803 BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1804 authorized_withdrawer: Pubkey::new_unique(),
1805 inflation_rewards_commission_bps: 1_234,
1806 block_revenue_commission_bps: 5_678,
1807 };
1808 let clock = Clock::default();
1809 let rent = Rent::default();
1810
1811 let v4_size = VoteStateV4::size_of();
1812 let lamports = rent.minimum_balance(v4_size);
1813 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1814
1815 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1816 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1817 let mut vote_account = instruction_context
1818 .try_borrow_instruction_account(0)
1819 .unwrap();
1820
1821 VoteStateHandler::init_vote_account_state_v2(
1822 &mut vote_account,
1823 &vote_init,
1824 &inflation_rewards_collector,
1825 &block_revenue_collector,
1826 &clock,
1827 VoteStateTargetVersion::V4,
1828 )
1829 .unwrap();
1830
1831 let VoteStateVersions::V4(v4) = vote_account.get_state::<VoteStateVersions>().unwrap()
1832 else {
1833 panic!("should be v4");
1834 };
1835
1836 assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1837 assert_eq!(
1838 v4.authorized_voters.get_authorized_voter(clock.epoch),
1839 Some(vote_init.authorized_voter),
1840 );
1841 assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1842 assert_eq!(
1843 v4.bls_pubkey_compressed,
1844 Some(vote_init.authorized_voter_bls_pubkey),
1845 );
1846 assert_eq!(
1847 v4.inflation_rewards_commission_bps,
1848 vote_init.inflation_rewards_commission_bps,
1849 );
1850 assert_eq!(
1851 v4.block_revenue_commission_bps,
1852 vote_init.block_revenue_commission_bps,
1853 );
1854 assert_eq!(v4.inflation_rewards_collector, inflation_rewards_collector);
1855 assert_eq!(v4.block_revenue_collector, block_revenue_collector);
1856 assert_eq!(v4.pending_delegator_rewards, 0);
1857 assert!(v4.votes.is_empty());
1858 assert_eq!(v4.root_slot, None);
1859 assert!(v4.epoch_credits.is_empty());
1860 assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1861 }
1862}