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 pub fn serialize_padded(&self, len: usize) -> Result<Vec<u8>, InstructionError> {
665 let mut data = Vec::with_capacity(len);
666 match &self.target_state {
667 TargetVoteState::V4(v4) => {
668 let versioned = VoteStateVersionsRef::V4(v4);
669 wincode::serialize_into(&mut data, &versioned)
670 .map_err(|_e| InstructionError::InvalidAccountData)?;
671 }
672 }
673 if data.len() > len {
674 return Err(InstructionError::InvalidAccountData);
675 }
676 data.resize(len, 0);
677 Ok(data)
678 }
679
680 #[cfg(test)]
681 pub fn serialize(self) -> Vec<u8> {
682 self.serialize_padded(VoteStateV4::size_of()).unwrap()
683 }
684}
685
686pub(crate) fn compute_vote_latency(voted_for_slot: Slot, current_slot: Slot) -> u8 {
688 std::cmp::min(current_slot.saturating_sub(voted_for_slot), u8::MAX as u64) as u8
689}
690
691pub(crate) fn try_convert_to_vote_state_v4(
692 versioned: VoteStateVersionsInline,
693 vote_pubkey: &Pubkey,
694) -> Result<VoteStateV4, InstructionError> {
695 match versioned {
696 VoteStateVersionsInline::Uninitialized => Err(InstructionError::UninitializedAccount),
697 VoteStateVersionsInline::V1_14_11(state) => Ok(VoteStateV4 {
698 node_pubkey: state.node_pubkey,
699 authorized_withdrawer: state.authorized_withdrawer,
700 inflation_rewards_collector: *vote_pubkey,
701 block_revenue_collector: state.node_pubkey,
702 inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
703 block_revenue_commission_bps: 10_000u16,
704 pending_delegator_rewards: 0,
705 bls_pubkey_compressed: None,
706 votes: landed_votes_from_lockouts(state.votes),
707 root_slot: state.root_slot,
708 authorized_voters: state.authorized_voters.clone(),
709 epoch_credits: state.epoch_credits,
710 last_timestamp: state.last_timestamp,
711 }),
712 VoteStateVersionsInline::V3(state) => Ok(VoteStateV4 {
713 node_pubkey: state.node_pubkey,
714 authorized_withdrawer: state.authorized_withdrawer,
715 inflation_rewards_collector: *vote_pubkey,
716 block_revenue_collector: state.node_pubkey,
717 inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
718 block_revenue_commission_bps: 10_000u16,
719 pending_delegator_rewards: 0,
720 bls_pubkey_compressed: None,
721 votes: state.votes,
722 root_slot: state.root_slot,
723 authorized_voters: state.authorized_voters,
724 epoch_credits: state.epoch_credits,
725 last_timestamp: state.last_timestamp,
726 }),
727 VoteStateVersionsInline::V4(state) => Ok(state),
728 }
729}
730
731fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
732 lockouts.into_iter().map(|lockout| lockout.into()).collect()
733}
734
735#[allow(clippy::arithmetic_side_effects)]
736#[allow(clippy::type_complexity)]
737#[cfg(test)]
738mod tests {
739 use {
740 super::*,
741 crate::id,
742 solana_account::AccountSharedData,
743 solana_clock::Clock,
744 solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
745 solana_pubkey::Pubkey,
746 solana_rent::Rent,
747 solana_sdk_ids::native_loader,
748 solana_transaction_context::{
749 instruction_accounts::InstructionAccount, transaction::TransactionContext,
750 },
751 solana_vote_interface::{
752 authorized_voters::AuthorizedVoters,
753 state::{
754 BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BlockTimestamp, MAX_EPOCH_CREDITS_HISTORY,
755 MAX_LOCKOUT_HISTORY, VoteInit, VoteState1_14_11, VoteStateV3, VoteStateVersions,
756 },
757 },
758 std::collections::VecDeque,
759 test_case::test_case,
760 };
761
762 const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000;
764
765 #[test]
766 fn test_borrowed_vote_state_versions_compatibility() {
767 let v1 = VoteState1_14_11::default();
768 let v3 = VoteStateV3::default();
769 let v4 = get_max_sized_vote_state_v4();
770 let versions = [
771 (
772 None,
773 VoteStateVersions::V1_14_11(Box::new(v1.clone())),
774 VoteStateVersionsInline::V1_14_11(v1.clone()),
775 ),
776 (
777 None,
778 VoteStateVersions::V3(Box::new(v3.clone())),
779 VoteStateVersionsInline::V3(v3.clone()),
780 ),
781 (
782 Some(VoteStateVersionsRef::V4(&v4)),
783 VoteStateVersions::V4(Box::new(v4.clone())),
784 VoteStateVersionsInline::V4(v4.clone()),
785 ),
786 ];
787 for (borrowed, orig, inline) in versions {
788 let bytes = wincode::serialize(&orig).unwrap();
789 assert_eq!(
790 VoteStateVersionsInline::deserialize(&bytes).unwrap(),
791 inline
792 );
793 if let Some(borrowed) = borrowed {
794 let borrowed_bytes = wincode::serialize(&borrowed).unwrap();
795 assert_eq!(borrowed_bytes, bytes);
796 assert_eq!(
797 VoteStateVersionsInline::deserialize(&borrowed_bytes).unwrap(),
798 inline
799 );
800 }
801 }
802 }
803
804 #[test]
805 fn test_inline_vote_state_rejects_uninitialized() {
806 let bytes = wincode::serialize(&VoteStateVersions::Uninitialized).unwrap();
807 assert_eq!(
808 VoteStateVersionsInline::deserialize(&bytes),
809 Err(InstructionError::InvalidAccountData),
810 );
811 }
812
813 #[test]
814 fn test_vote_state_handler_compatibility() {
815 let v4 = get_max_sized_vote_state_v4();
816 let expected = wincode::serialize(&VoteStateVersions::V4(Box::new(v4.clone()))).unwrap();
817 let handler = VoteStateHandler::new_v4(v4);
818 let mut data = vec![0; VoteStateV4::size_of()];
819 handler.serialize_into(&mut data).unwrap();
820 assert_eq!(&data[..expected.len()], expected);
821 let decoded = VoteStateVersionsInline::deserialize(&data).unwrap();
822 assert_eq!(
823 VoteStateHandler::try_new_from_vote_state_versions(decoded).unwrap(),
824 handler,
825 );
826
827 let mut undersized_data = vec![0; expected.len() - 1];
828 assert_eq!(
829 handler.serialize_into(&mut undersized_data),
830 Err(InstructionError::InvalidAccountData),
831 );
832
833 assert_eq!(handler.serialize_padded(data.len()), Ok(data));
834 assert_eq!(
835 handler.serialize_padded(expected.len()).as_ref(),
836 Ok(&expected)
837 );
838 assert_eq!(
839 handler.serialize_padded(expected.len() - 1),
840 Err(InstructionError::InvalidAccountData),
841 );
842 }
843
844 fn mock_transaction_context(
845 vote_pubkey: Pubkey,
846 vote_account: AccountSharedData,
847 rent: Rent,
848 ) -> TransactionContext<'static> {
849 let program_account = AccountSharedData::new(0, 0, &native_loader::id());
850 let mut transaction_context = TransactionContext::new(
851 vec![(id(), program_account), (vote_pubkey, vote_account)],
852 rent,
853 0,
854 0,
855 1,
856 );
857 transaction_context
858 .configure_top_level_instruction_for_tests(
859 0,
860 vec![InstructionAccount::new(1, false, true)],
861 vec![],
862 )
863 .unwrap();
864 transaction_context
865 }
866
867 fn get_max_sized_vote_state_v4() -> VoteStateV4 {
868 let mut authorized_voters = AuthorizedVoters::default();
869 for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
870 authorized_voters.insert(i, Pubkey::new_unique());
871 }
872
873 VoteStateV4 {
874 votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
875 root_slot: Some(u64::MAX),
876 epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
877 authorized_voters,
878 bls_pubkey_compressed: Some([255; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
879 ..Default::default()
880 }
881 }
882
883 fn set_new_authorized_voter_and_assert(
884 vote_state: &mut VoteStateHandler,
885 original_voter: Pubkey,
886 epoch_offset: Epoch,
887 ) {
888 let new_voter = Pubkey::new_unique();
889 vote_state
891 .set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(()))
892 .unwrap();
893
894 assert_eq!(
896 vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())),
897 Err(VoteError::TooSoonToReauthorize.into())
898 );
899
900 vote_state
902 .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, None, |_| Ok(()))
903 .unwrap();
904
905 let new_voter2 = Pubkey::new_unique();
907 vote_state
908 .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, None, |_| Ok(()))
909 .unwrap();
910
911 let new_voter3 = Pubkey::new_unique();
912 vote_state
913 .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, None, |_| Ok(()))
914 .unwrap();
915
916 vote_state
918 .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, None, |_| Ok(()))
919 .unwrap();
920
921 for i in 9..epoch_offset {
924 assert_eq!(
925 vote_state.get_and_update_authorized_voter(i).unwrap(),
926 original_voter
927 );
928 }
929 for i in epoch_offset..3 + epoch_offset {
930 assert_eq!(
931 vote_state.get_and_update_authorized_voter(i).unwrap(),
932 new_voter
933 );
934 }
935 for i in 3 + epoch_offset..6 + epoch_offset {
936 assert_eq!(
937 vote_state.get_and_update_authorized_voter(i).unwrap(),
938 new_voter2
939 );
940 }
941 for i in 6 + epoch_offset..9 + epoch_offset {
942 assert_eq!(
943 vote_state.get_and_update_authorized_voter(i).unwrap(),
944 new_voter3
945 );
946 }
947 for i in 9 + epoch_offset..=10 + epoch_offset {
948 assert_eq!(
949 vote_state.get_and_update_authorized_voter(i).unwrap(),
950 original_voter
951 );
952 }
953 }
954
955 #[test]
956 fn test_set_new_authorized_voter() {
957 let vote_pubkey = Pubkey::new_unique();
958 let original_voter = Pubkey::new_unique();
959 let epoch_offset = 15;
960
961 let vote_init = VoteInit {
962 node_pubkey: original_voter,
963 authorized_voter: original_voter,
964 authorized_withdrawer: original_voter,
965 commission: 0,
966 };
967 let clock = Clock::default();
968
969 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
970 &vote_pubkey,
971 &vote_init,
972 &clock,
973 ));
974
975 set_new_authorized_voter_and_assert(&mut vote_state, original_voter, epoch_offset);
976
977 {
978 let voter_a = Pubkey::new_unique();
979 let voter_b = Pubkey::new_unique();
980
981 let v4 = VoteStateV4 {
982 authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
983 ..Default::default()
984 };
985 let mut handler = VoteStateHandler::new_v4(v4);
986 handler
987 .set_new_authorized_voter(&voter_a, 0, 10, None, |_| Ok(()))
988 .unwrap();
989 handler
990 .set_new_authorized_voter(&voter_b, 0, 5, None, |_| Ok(()))
991 .unwrap();
992 }
993 }
994
995 fn assert_authorized_voter_is_locked_within_epoch(
996 vote_state: &mut VoteStateHandler,
997 original_voter: &Pubkey,
998 ) {
999 let new_voter = Pubkey::new_unique();
1003 assert_eq!(
1004 vote_state.set_new_authorized_voter(&new_voter, 1, 1, None, |_| Ok(())),
1005 Err(VoteError::TooSoonToReauthorize.into())
1006 );
1007 assert_eq!(
1008 vote_state.authorized_voters().get_authorized_voter(1),
1009 Some(*original_voter)
1010 );
1011 assert_eq!(
1013 vote_state.set_new_authorized_voter(&new_voter, 1, 2, None, |_| Ok(())),
1014 Ok(())
1015 );
1016 assert_eq!(
1020 vote_state.set_new_authorized_voter(original_voter, 3, 3, None, |_| Ok(())),
1021 Err(VoteError::TooSoonToReauthorize.into())
1022 );
1023 assert_eq!(
1024 vote_state.authorized_voters().get_authorized_voter(3),
1025 Some(new_voter)
1026 );
1027 }
1028
1029 #[test]
1030 fn test_authorized_voter_is_locked_within_epoch() {
1031 let vote_pubkey = Pubkey::new_unique();
1032 let original_voter = Pubkey::new_unique();
1033
1034 let vote_init = VoteInit {
1035 node_pubkey: original_voter,
1036 authorized_voter: original_voter,
1037 authorized_withdrawer: original_voter,
1038 commission: 0,
1039 };
1040 let clock = Clock::default();
1041
1042 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1043 &vote_pubkey,
1044 &vote_init,
1045 &clock,
1046 ));
1047 assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter);
1048 }
1049
1050 #[test]
1051 fn test_get_and_update_authorized_voter() {
1052 let vote_pubkey = Pubkey::new_unique();
1053 let original_voter = Pubkey::new_unique();
1054 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1055 &vote_pubkey,
1056 &VoteInit {
1057 node_pubkey: original_voter,
1058 authorized_voter: original_voter,
1059 authorized_withdrawer: original_voter,
1060 commission: 0,
1061 },
1062 &Clock::default(),
1063 ));
1064
1065 assert_eq!(vote_state.authorized_voters().len(), 1);
1068 assert_eq!(
1069 *vote_state.authorized_voters().first().unwrap().1,
1070 original_voter
1071 );
1072
1073 assert_eq!(
1076 vote_state.get_and_update_authorized_voter(1).unwrap(),
1077 original_voter
1078 );
1079
1080 assert_eq!(
1083 vote_state.get_and_update_authorized_voter(5).unwrap(),
1084 original_voter
1085 );
1086
1087 assert_eq!(vote_state.authorized_voters().len(), 1);
1090 for i in 0..5 {
1091 assert!(
1092 vote_state
1093 .authorized_voters()
1094 .get_authorized_voter(i)
1095 .is_none()
1096 );
1097 }
1098
1099 assert_eq!(
1101 vote_state.get_and_update_authorized_voter(6).unwrap(),
1102 original_voter
1103 );
1104 assert_eq!(
1105 vote_state.get_and_update_authorized_voter(7).unwrap(),
1106 original_voter
1107 );
1108
1109 assert_eq!(vote_state.authorized_voters().len(), 2);
1111
1112 for i in 0..=5 {
1114 assert!(
1115 vote_state
1116 .authorized_voters()
1117 .get_authorized_voter(i)
1118 .is_none()
1119 );
1120 }
1121
1122 let new_authorized_voter = Pubkey::new_unique();
1124 vote_state
1125 .set_new_authorized_voter(&new_authorized_voter, 7, 9, None, |_| Ok(()))
1126 .unwrap();
1127
1128 assert_eq!(
1130 vote_state.get_and_update_authorized_voter(8).unwrap(),
1131 original_voter
1132 );
1133
1134 for i in 9..12 {
1137 assert_eq!(
1138 vote_state.get_and_update_authorized_voter(i).unwrap(),
1139 new_authorized_voter
1140 );
1141 }
1142 assert_eq!(vote_state.authorized_voters().len(), 2);
1143
1144 assert_eq!(
1146 vote_state.get_and_update_authorized_voter(15).unwrap(),
1147 new_authorized_voter
1148 );
1149 assert_eq!(vote_state.authorized_voters().len(), 1);
1150
1151 {
1154 let voter_10 = Pubkey::new_unique();
1155 let voter_15 = Pubkey::new_unique();
1156 let mut v4 = VoteStateV4 {
1157 authorized_voters: AuthorizedVoters::new(0, Pubkey::new_unique()),
1158 ..Default::default()
1159 };
1160 v4.authorized_voters.insert(10, voter_10);
1161 v4.authorized_voters.insert(15, voter_15);
1162 let mut handler = VoteStateHandler::new_v4(v4);
1163 let v4_voter = handler.get_and_update_authorized_voter(11).unwrap();
1164 assert_eq!(v4_voter, voter_10);
1165 assert!(
1167 handler
1168 .authorized_voters()
1169 .get_authorized_voter(10)
1170 .is_some()
1171 );
1172 }
1173
1174 {
1176 let voter = Pubkey::new_unique();
1177 let v4 = VoteStateV4 {
1178 authorized_voters: AuthorizedVoters::new(0, voter),
1179 ..Default::default()
1180 };
1181 let mut handler = VoteStateHandler::new_v4(v4);
1182 let result = handler.get_and_update_authorized_voter(0).unwrap();
1183 assert_eq!(result, voter);
1184 assert!(!handler.authorized_voters().is_empty());
1185 }
1186 }
1187
1188 #[test_case(
1189 VoteStateV4::size_of(),
1190 VoteStateHandler::new_v4(get_max_sized_vote_state_v4()),
1191 |vote_state, data| {
1192 let versioned = VoteStateVersions::new_v4(vote_state.unwrap_v4());
1193 VoteStateV4::serialize(&versioned, data).unwrap();
1194 };
1195 "VoteStateV4"
1196 )]
1197 fn test_vote_state_max_size(
1198 max_size: usize,
1199 mut vote_state: VoteStateHandler,
1200 verify_serialize: fn(VoteStateHandler, &mut [u8]),
1201 ) {
1202 let mut max_sized_data = vec![0; max_size];
1203 let (start_leader_schedule_epoch, _) = vote_state.authorized_voters().last().unwrap();
1204 let start_current_epoch =
1205 start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1;
1206
1207 for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
1208 vote_state
1209 .set_new_authorized_voter(
1210 &Pubkey::new_unique(),
1211 i,
1212 i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
1213 None,
1214 |_| Ok(()),
1215 )
1216 .unwrap();
1217
1218 verify_serialize(vote_state.clone(), &mut max_sized_data);
1219 }
1220 }
1221
1222 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1223 fn test_vote_state_epoch_credits(mut vote_state: VoteStateHandler) {
1224 assert_eq!(vote_state.credits(), 0);
1225 assert_eq!(vote_state.epoch_credits().clone(), vec![]);
1226
1227 let mut expected = vec![];
1228 let mut credits = 0;
1229 let epochs = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1230 for epoch in 0..epochs {
1231 for _j in 0..epoch {
1232 vote_state.increment_credits(epoch, 1);
1233 credits += 1;
1234 }
1235 expected.push((epoch, credits, credits - epoch));
1236 }
1237
1238 while expected.len() > MAX_EPOCH_CREDITS_HISTORY {
1239 expected.remove(0);
1240 }
1241
1242 assert_eq!(vote_state.credits(), credits);
1243 assert_eq!(vote_state.epoch_credits().clone(), expected);
1244 }
1245
1246 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1247 fn test_vote_state_epoch0_no_credits(mut vote_state: VoteStateHandler) {
1248 assert_eq!(vote_state.epoch_credits().len(), 0);
1249 vote_state.increment_credits(1, 1);
1250 assert_eq!(vote_state.epoch_credits().len(), 1);
1251
1252 vote_state.increment_credits(2, 1);
1253 assert_eq!(vote_state.epoch_credits().len(), 2);
1254 }
1255
1256 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1257 fn test_vote_state_increment_credits(mut vote_state: VoteStateHandler) {
1258 let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
1259 for i in 0..credits {
1260 vote_state.increment_credits(i, 1);
1261 }
1262 assert_eq!(vote_state.credits(), credits);
1263 assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
1264 }
1265
1266 #[test_case(VoteStateHandler::new_v4(VoteStateV4::default()) ; "VoteStateV4")]
1267 fn test_vote_process_timestamp(mut vote_state: VoteStateHandler) {
1268 let (slot, timestamp) = (15, 1_575_412_285);
1269 vote_state.set_last_timestamp(BlockTimestamp { slot, timestamp });
1270
1271 assert_eq!(
1272 vote_state.process_timestamp(slot - 1, timestamp + 1),
1273 Err(VoteError::TimestampTooOld)
1274 );
1275 assert_eq!(
1276 vote_state.last_timestamp(),
1277 &BlockTimestamp { slot, timestamp }
1278 );
1279 assert_eq!(
1280 vote_state.process_timestamp(slot + 1, timestamp - 1),
1281 Err(VoteError::TimestampTooOld)
1282 );
1283 assert_eq!(
1284 vote_state.process_timestamp(slot, timestamp + 1),
1285 Err(VoteError::TimestampTooOld)
1286 );
1287 assert_eq!(vote_state.process_timestamp(slot, timestamp), Ok(()));
1288 assert_eq!(
1289 vote_state.last_timestamp(),
1290 &BlockTimestamp { slot, timestamp }
1291 );
1292 assert_eq!(vote_state.process_timestamp(slot + 1, timestamp), Ok(()));
1293 assert_eq!(
1294 vote_state.last_timestamp(),
1295 &BlockTimestamp {
1296 slot: slot + 1,
1297 timestamp
1298 }
1299 );
1300 assert_eq!(
1301 vote_state.process_timestamp(slot + 2, timestamp + 1),
1302 Ok(())
1303 );
1304 assert_eq!(
1305 vote_state.last_timestamp(),
1306 &BlockTimestamp {
1307 slot: slot + 2,
1308 timestamp: timestamp + 1
1309 }
1310 );
1311
1312 vote_state.set_last_timestamp(BlockTimestamp::default());
1314 assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
1315 }
1316
1317 fn init_vote_account_state_v4_and_assert(
1318 vote_pubkey: Pubkey,
1319 vote_account: AccountSharedData,
1320 vote_init: &VoteInit,
1321 clock: &Clock,
1322 rent: Rent,
1323 expected_result: Result<(), InstructionError>,
1324 ) {
1325 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1326 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1327 let mut vote_account = instruction_context
1328 .try_borrow_instruction_account(0)
1329 .unwrap();
1330
1331 let result = VoteStateHandler::init_vote_account_state(
1333 &mut vote_account,
1334 vote_init,
1335 clock,
1336 VoteStateTargetVersion::V4,
1337 );
1338 assert_eq!(result, expected_result);
1339
1340 if result.is_ok() {
1341 let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1342 assert!(matches!(vote_state_versions, VoteStateVersions::V4(_)));
1343 assert!(!vote_state_versions.is_uninitialized());
1344
1345 if let VoteStateVersions::V4(v4) = vote_state_versions {
1347 assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1348 assert_eq!(
1349 v4.authorized_voters.get_authorized_voter(clock.epoch),
1350 Some(vote_init.authorized_voter)
1351 );
1352 assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1353 assert_eq!(
1354 v4.inflation_rewards_commission_bps,
1355 (vote_init.commission as u16) * 100
1356 );
1357
1358 assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
1360 assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
1361 assert_eq!(
1362 v4.block_revenue_commission_bps,
1363 DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1364 );
1365
1366 assert_eq!(v4.pending_delegator_rewards, 0);
1368 assert_eq!(v4.bls_pubkey_compressed, None);
1369 assert!(v4.votes.is_empty());
1370 assert_eq!(v4.root_slot, None);
1371 assert!(v4.epoch_credits.is_empty());
1372 assert_eq!(v4.last_timestamp, BlockTimestamp::default());
1373 } else {
1374 panic!("should be v4");
1375 }
1376 }
1377 }
1378
1379 fn deinit_vote_account_state_v4_and_assert(
1380 vote_pubkey: Pubkey,
1381 vote_account: AccountSharedData,
1382 rent: Rent,
1383 ) {
1384 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1385 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1386 let mut vote_account = instruction_context
1387 .try_borrow_instruction_account(0)
1388 .unwrap();
1389
1390 VoteStateHandler::deinitialize_vote_account_state(
1392 &mut vote_account,
1393 VoteStateTargetVersion::V4,
1394 )
1395 .unwrap();
1396
1397 let account_data = vote_account.get_data();
1399 assert!(account_data.iter().all(|&b| b == 0),);
1400
1401 let vote_state_versions = vote_account.get_state::<VoteStateVersions>().unwrap();
1404 assert!(matches!(
1405 vote_state_versions,
1406 VoteStateVersions::Uninitialized
1407 ));
1408 assert!(vote_state_versions.is_uninitialized());
1409 }
1410
1411 #[test]
1412 fn test_init_vote_account_state_v4() {
1413 let vote_pubkey = Pubkey::new_unique();
1414 let vote_init = VoteInit {
1415 node_pubkey: Pubkey::new_unique(),
1416 authorized_voter: Pubkey::new_unique(),
1417 authorized_withdrawer: Pubkey::new_unique(),
1418 commission: 5,
1419 };
1420 let clock = Clock::default();
1421 let rent = Rent::default();
1422
1423 let v1_14_11_size = VoteState1_14_11::size_of();
1425 let lamports = rent.minimum_balance(v1_14_11_size);
1426 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1427
1428 init_vote_account_state_v4_and_assert(
1430 vote_pubkey,
1431 vote_account,
1432 &vote_init,
1433 &clock,
1434 rent.clone(),
1435 Err(InstructionError::AccountNotRentExempt),
1436 );
1437
1438 let v4_size = VoteStateV4::size_of();
1441 let lamports = rent.minimum_balance(v4_size);
1442 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1443
1444 init_vote_account_state_v4_and_assert(
1446 vote_pubkey,
1447 vote_account,
1448 &vote_init,
1449 &clock,
1450 rent.clone(),
1451 Ok(()),
1452 );
1453
1454 let lamports = rent.minimum_balance(v4_size);
1456 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1457
1458 init_vote_account_state_v4_and_assert(
1460 vote_pubkey,
1461 vote_account,
1462 &vote_init,
1463 &clock,
1464 rent,
1465 Ok(()),
1466 );
1467 }
1468
1469 #[test]
1470 fn test_deinitialize_vote_account_state_v4() {
1471 let vote_pubkey = Pubkey::new_unique();
1472 let rent = Rent::default();
1473
1474 let v1_14_11_size = VoteState1_14_11::size_of();
1476 let lamports = rent.minimum_balance(v1_14_11_size);
1477 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1478
1479 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1481
1482 let v4_size = VoteStateV4::size_of();
1485 let lamports = rent.minimum_balance(v4_size);
1486 let vote_account = AccountSharedData::new(lamports, v1_14_11_size, &id());
1487
1488 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent.clone());
1490
1491 let lamports = rent.minimum_balance(v4_size);
1493 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1494
1495 deinit_vote_account_state_v4_and_assert(vote_pubkey, vote_account, rent);
1497 }
1498
1499 #[test]
1500 fn test_v4_commission_basis_points() {
1501 let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1504
1505 for (input, expected) in [
1506 (0, 0),
1507 (1, 100),
1508 (5, 500),
1509 (10, 1_000),
1510 (25, 2_500),
1511 (50, 5_000),
1512 (75, 7_500),
1513 (100, 10_000),
1514 (255, 25_500),
1515 ] {
1516 handler.set_commission(input);
1517 assert_eq!(handler.commission(), input);
1518
1519 let vote_state_v4 = handler.as_ref_v4();
1521 assert_eq!(vote_state_v4.inflation_rewards_commission_bps, expected);
1522 }
1523 }
1524
1525 #[test]
1526 fn test_v4_commission_getter_clamps_extreme_bps() {
1527 for (bps, expected) in [
1532 (10_000, 100), (25_500, 255), (25_501, 255), (25_600, 255), (u16::MAX, 255), ] {
1538 let vote_state = VoteStateV4 {
1539 inflation_rewards_commission_bps: bps,
1540 ..Default::default()
1541 };
1542 let handler = VoteStateHandler::new_v4(vote_state);
1543 assert_eq!(handler.commission(), expected);
1544 }
1545 }
1546
1547 #[test]
1548 fn test_commission_v3_v4_preserved() {
1549 let vote_pubkey = Pubkey::new_unique();
1551 for n in [0u8, 1, 50, 100, 101, 255] {
1552 let v3 = VoteStateV3 {
1553 commission: n,
1554 ..Default::default()
1555 };
1556
1557 let versioned = VoteStateVersionsInline::V3(v3);
1558 let migrated = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1559 let migrated_handler = VoteStateHandler::new_v4(migrated);
1560 assert_eq!(migrated_handler.commission(), n);
1561 assert_eq!(
1562 migrated_handler
1563 .as_ref_v4()
1564 .inflation_rewards_commission_bps,
1565 n as u16 * 100
1566 );
1567 }
1568 }
1569
1570 #[test]
1571 fn test_v4_conversion_from_all_versions() {
1572 let vote_pubkey = Pubkey::new_unique();
1574 let node_pubkey = Pubkey::new_unique();
1575 let authorized_voter = Pubkey::new_unique();
1576 let authorized_withdrawer = Pubkey::new_unique();
1577 let commission = 42u8;
1578 let votes = vec![Lockout::new(100)];
1579 let votes_deque: VecDeque<Lockout> = votes.iter().cloned().collect();
1580 let epoch_credits = vec![(5, 200, 100)];
1581 let root_slot = Some(50);
1582
1583 let verify_v4_defaults = |v4_state: &VoteStateV4, expected_commission_bps: u16| {
1585 assert_eq!(
1586 v4_state.inflation_rewards_commission_bps,
1587 expected_commission_bps
1588 );
1589 assert_eq!(v4_state.inflation_rewards_collector, vote_pubkey);
1590 assert_eq!(v4_state.block_revenue_collector, node_pubkey);
1591 assert_eq!(
1592 v4_state.block_revenue_commission_bps,
1593 DEFAULT_BLOCK_REVENUE_COMMISSION_BPS
1594 );
1595 assert_eq!(v4_state.pending_delegator_rewards, 0);
1596 assert_eq!(v4_state.bls_pubkey_compressed, None);
1597 };
1598
1599 {
1606 let vote_state_v2 = VoteState1_14_11 {
1607 node_pubkey,
1608 authorized_withdrawer,
1609 commission,
1610 authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1611 votes: votes_deque,
1612 epoch_credits: epoch_credits.clone(),
1613 root_slot,
1614 ..VoteState1_14_11::default()
1615 };
1616
1617 let versioned = VoteStateVersionsInline::V1_14_11(vote_state_v2.clone());
1618 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1619
1620 assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1622 assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1623 assert_eq!(
1624 vote_state_v4.authorized_voters,
1625 vote_state_v2.authorized_voters
1626 );
1627 assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1628 assert_eq!(vote_state_v4.root_slot, root_slot);
1629 assert_eq!(vote_state_v4.votes.len(), vote_state_v2.votes.len());
1630 for (v4_vote, v2_vote) in vote_state_v4.votes.iter().zip(vote_state_v2.votes.iter()) {
1631 assert_eq!(v4_vote.lockout, *v2_vote);
1632 }
1633
1634 verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1636 }
1637
1638 {
1640 let vote_init = VoteInit {
1641 node_pubkey,
1642 authorized_voter,
1643 authorized_withdrawer,
1644 commission,
1645 };
1646 let mut vote_state_v3 = VoteStateV3::new(&vote_init, &Clock::default());
1647 for lockout in &votes {
1648 vote_state_v3.votes.push_back(LandedVote {
1649 latency: 1,
1650 lockout: *lockout,
1651 });
1652 }
1653 vote_state_v3.epoch_credits = epoch_credits.clone();
1654 vote_state_v3.root_slot = root_slot;
1655
1656 let versioned = VoteStateVersionsInline::V3(vote_state_v3.clone());
1657 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1658
1659 assert_eq!(vote_state_v4.node_pubkey, node_pubkey);
1661 assert_eq!(vote_state_v4.authorized_withdrawer, authorized_withdrawer);
1662 assert_eq!(
1663 vote_state_v4.authorized_voters,
1664 vote_state_v3.authorized_voters
1665 );
1666 assert_eq!(vote_state_v4.epoch_credits, epoch_credits);
1667 assert_eq!(vote_state_v4.root_slot, root_slot);
1668 assert_eq!(vote_state_v4.last_timestamp, vote_state_v3.last_timestamp);
1669 assert_eq!(vote_state_v4.votes, vote_state_v3.votes);
1670
1671 verify_v4_defaults(&vote_state_v4, (commission as u16) * 100);
1673 }
1674
1675 {
1677 let mut initial_vote_state_v4 = VoteStateV4 {
1678 node_pubkey,
1679 authorized_withdrawer,
1680 inflation_rewards_commission_bps: 1234,
1681 block_revenue_commission_bps: 5678,
1682 inflation_rewards_collector: Pubkey::new_unique(),
1683 block_revenue_collector: Pubkey::new_unique(),
1684 pending_delegator_rewards: 999,
1685 bls_pubkey_compressed: Some([42; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
1686 authorized_voters: AuthorizedVoters::new(0, authorized_voter),
1687 epoch_credits,
1688 root_slot,
1689 ..VoteStateV4::default()
1690 };
1691 for lockout in &votes {
1692 initial_vote_state_v4.votes.push_back(LandedVote {
1693 latency: 1,
1694 lockout: *lockout,
1695 });
1696 }
1697
1698 let versioned = VoteStateVersionsInline::V4(initial_vote_state_v4.clone());
1699 let vote_state_v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1700
1701 assert_eq!(vote_state_v4, initial_vote_state_v4);
1703 }
1704 }
1705
1706 #[test]
1707 fn test_v4_migration_with_pre_existing_voters() {
1708 let vote_pubkey = Pubkey::new_unique();
1711 let voter_5 = Pubkey::new_unique();
1712 let voter_7 = Pubkey::new_unique();
1713 let voter_9 = Pubkey::new_unique();
1714
1715 let build_voters = || {
1716 let mut voters = AuthorizedVoters::new(0, Pubkey::new_unique());
1717 voters.insert(5, voter_5);
1718 voters.insert(7, voter_7);
1719 voters.insert(9, voter_9);
1720 voters
1721 };
1722
1723 let assert_purge = |handler: &mut VoteStateHandler| {
1724 let voter = handler.get_and_update_authorized_voter(10).unwrap();
1726 assert_eq!(voter, voter_9);
1727 assert!(
1728 handler
1729 .authorized_voters()
1730 .get_authorized_voter(9)
1731 .is_some()
1732 );
1733 assert!(
1734 handler
1735 .authorized_voters()
1736 .get_authorized_voter(7)
1737 .is_none()
1738 );
1739 assert!(
1740 handler
1741 .authorized_voters()
1742 .get_authorized_voter(5)
1743 .is_none()
1744 );
1745 };
1746
1747 {
1749 let v1 = VoteState1_14_11 {
1750 authorized_voters: build_voters(),
1751 ..Default::default()
1752 };
1753 let versioned = VoteStateVersionsInline::V1_14_11(v1);
1754 let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1755 let mut handler = VoteStateHandler::new_v4(v4);
1756 assert_purge(&mut handler);
1757 }
1758
1759 {
1761 let v3 = VoteStateV3 {
1762 authorized_voters: build_voters(),
1763 ..Default::default()
1764 };
1765 let versioned = VoteStateVersionsInline::V3(v3);
1766 let v4 = try_convert_to_vote_state_v4(versioned, &vote_pubkey).unwrap();
1767 let mut handler = VoteStateHandler::new_v4(v4);
1768 assert_purge(&mut handler);
1769 }
1770 }
1771
1772 #[test]
1773 fn test_v3_v4_size_equality() {
1774 let v3_size = VoteStateV3::size_of();
1775 let v4_size = VoteStateV4::size_of();
1776 assert_eq!(v3_size, v4_size, "V3 and V4 should have the same size");
1777 }
1778
1779 #[test_case(
1780 VoteState1_14_11::size_of(),
1781 false,
1782 Err(InstructionError::AccountNotRentExempt);
1783 "v1_14_11 size insufficient lamports - no fallback"
1784 )]
1785 #[test_case(
1786 VoteState1_14_11::size_of(),
1787 true,
1788 Ok(());
1789 "v1_14_11 size sufficient lamports - resize and succeed"
1790 )]
1791 #[test_case(
1792 VoteStateV4::size_of(),
1793 true,
1794 Ok(());
1795 "v4 properly sized with rent-exempt lamports - succeed"
1796 )]
1797 fn test_v4_resize_behavior(
1798 account_size: usize,
1799 sufficient_lamports: bool,
1800 expected_result: Result<(), InstructionError>,
1801 ) {
1802 let vote_pubkey = Pubkey::new_unique();
1803 let node_pubkey = Pubkey::new_unique();
1804 let authorized_voter = Pubkey::new_unique();
1805 let authorized_withdrawer = Pubkey::new_unique();
1806 let rent = Rent::default();
1807
1808 let vote_init = VoteInit {
1809 node_pubkey,
1810 authorized_voter,
1811 authorized_withdrawer,
1812 commission: 42,
1813 };
1814
1815 let lamports = if sufficient_lamports {
1816 rent.minimum_balance(VoteStateV4::size_of())
1817 } else {
1818 rent.minimum_balance(account_size)
1819 };
1820
1821 let mut vote_account = AccountSharedData::new(lamports, account_size, &id());
1822 vote_account.set_data_from_slice(&vec![0; account_size]);
1823
1824 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1825 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1826 let mut vote_account_borrowed = instruction_context
1827 .try_borrow_instruction_account(0)
1828 .unwrap();
1829
1830 let result = VoteStateHandler::init_vote_account_state(
1831 &mut vote_account_borrowed,
1832 &vote_init,
1833 &Clock::default(),
1834 VoteStateTargetVersion::V4,
1835 );
1836
1837 assert_eq!(result, expected_result);
1838 }
1839
1840 #[test]
1841 fn test_get_and_update_authorized_voter_v4_with_bls() {
1842 let vote_pubkey = Pubkey::new_unique();
1843 let original_voter = Pubkey::new_unique();
1844 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::new_with_defaults(
1845 &vote_pubkey,
1846 &VoteInit {
1847 node_pubkey: original_voter,
1848 authorized_voter: original_voter,
1849 authorized_withdrawer: original_voter,
1850 commission: 0,
1851 },
1852 &Clock::default(),
1853 ));
1854
1855 assert_eq!(vote_state.authorized_voters().len(), 1);
1857 assert_eq!(
1858 *vote_state.authorized_voters().first().unwrap().1,
1859 original_voter
1860 );
1861 assert!(!vote_state.has_bls_pubkey());
1862
1863 let new_voter = Pubkey::new_unique();
1865 let bls_pubkey_compressed = [3u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1866 vote_state
1867 .set_new_authorized_voter(&new_voter, 0, 1, Some(&bls_pubkey_compressed), |_| Ok(()))
1868 .unwrap();
1869 assert_eq!(vote_state.authorized_voters().len(), 2);
1870 assert_eq!(*vote_state.authorized_voters().last().unwrap().1, new_voter);
1871 assert_eq!(
1872 vote_state.as_ref_v4().bls_pubkey_compressed,
1873 Some(bls_pubkey_compressed)
1874 );
1875 assert!(vote_state.has_bls_pubkey());
1876
1877 let newer_voter = Pubkey::new_unique();
1879 let newer_bls_pubkey_compressed = [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
1880 vote_state
1881 .set_new_authorized_voter(
1882 &newer_voter,
1883 1,
1884 2,
1885 Some(&newer_bls_pubkey_compressed),
1886 |_| Ok(()),
1887 )
1888 .unwrap();
1889 assert_eq!(vote_state.authorized_voters().len(), 3);
1890 assert_eq!(
1891 *vote_state.authorized_voters().last().unwrap().1,
1892 newer_voter
1893 );
1894 assert_eq!(
1895 vote_state.as_ref_v4().bls_pubkey_compressed,
1896 Some(newer_bls_pubkey_compressed)
1897 );
1898 assert!(vote_state.has_bls_pubkey());
1899 }
1900
1901 #[test]
1902 fn test_set_inflation_rewards_commission_bps() {
1903 let mut handler = VoteStateHandler::new_v4(VoteStateV4::default());
1904
1905 for bps in [0, 100, 500, 1_000, 5_000, 10_000] {
1907 handler.set_inflation_rewards_commission_bps(bps);
1908 let v4 = handler.as_ref_v4();
1909 assert_eq!(v4.inflation_rewards_commission_bps, bps);
1910 assert_eq!(handler.commission(), (bps / 100) as u8);
1912 }
1913
1914 for bps in [10_001, 15_000, u16::MAX] {
1917 handler.set_inflation_rewards_commission_bps(bps);
1918 let v4 = handler.as_ref_v4();
1919 assert_eq!(v4.inflation_rewards_commission_bps, bps);
1920 }
1921 }
1922
1923 #[test]
1924 fn test_compute_vote_latency() {
1925 for (voted_for_slot, current_slot, expected) in [
1926 (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), ] {
1935 assert_eq!(compute_vote_latency(voted_for_slot, current_slot), expected);
1936 }
1937 }
1938
1939 #[test]
1940 fn test_init_vote_account_state_v2() {
1941 let vote_pubkey = Pubkey::new_unique();
1942 let inflation_rewards_collector = Pubkey::new_unique();
1943 let block_revenue_collector = Pubkey::new_unique();
1944 let vote_init = VoteInitV2 {
1945 node_pubkey: Pubkey::new_unique(),
1946 authorized_voter: Pubkey::new_unique(),
1947 authorized_voter_bls_pubkey: [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1948 authorized_voter_bls_proof_of_possession: [8u8;
1949 BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1950 authorized_withdrawer: Pubkey::new_unique(),
1951 inflation_rewards_commission_bps: 1_234,
1952 block_revenue_commission_bps: 5_678,
1953 };
1954 let clock = Clock::default();
1955 let rent = Rent::default();
1956
1957 let v4_size = VoteStateV4::size_of();
1958 let lamports = rent.minimum_balance(v4_size);
1959 let vote_account = AccountSharedData::new(lamports, v4_size, &id());
1960
1961 let transaction_context = mock_transaction_context(vote_pubkey, vote_account, rent);
1962 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1963 let mut vote_account = instruction_context
1964 .try_borrow_instruction_account(0)
1965 .unwrap();
1966
1967 VoteStateHandler::init_vote_account_state_v2(
1968 &mut vote_account,
1969 &vote_init,
1970 &inflation_rewards_collector,
1971 &block_revenue_collector,
1972 &clock,
1973 VoteStateTargetVersion::V4,
1974 )
1975 .unwrap();
1976
1977 let VoteStateVersions::V4(v4) = vote_account.get_state::<VoteStateVersions>().unwrap()
1978 else {
1979 panic!("should be v4");
1980 };
1981
1982 assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
1983 assert_eq!(
1984 v4.authorized_voters.get_authorized_voter(clock.epoch),
1985 Some(vote_init.authorized_voter),
1986 );
1987 assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
1988 assert_eq!(
1989 v4.bls_pubkey_compressed,
1990 Some(vote_init.authorized_voter_bls_pubkey),
1991 );
1992 assert_eq!(
1993 v4.inflation_rewards_commission_bps,
1994 vote_init.inflation_rewards_commission_bps,
1995 );
1996 assert_eq!(
1997 v4.block_revenue_commission_bps,
1998 vote_init.block_revenue_commission_bps,
1999 );
2000 assert_eq!(v4.inflation_rewards_collector, inflation_rewards_collector);
2001 assert_eq!(v4.block_revenue_collector, block_revenue_collector);
2002 assert_eq!(v4.pending_delegator_rewards, 0);
2003 assert!(v4.votes.is_empty());
2004 assert_eq!(v4.root_slot, None);
2005 assert!(v4.epoch_credits.is_empty());
2006 assert_eq!(v4.last_timestamp, BlockTimestamp::default());
2007 }
2008}