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