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