Skip to main content

solana_vote_interface/state/
vote_state_v4.rs

1#[cfg(feature = "bincode")]
2use super::VoteStateVersions;
3#[cfg(feature = "dev-context-only-utils")]
4use arbitrary::Arbitrary;
5#[cfg(feature = "serde")]
6use serde_derive::{Deserialize, Serialize};
7#[cfg(feature = "serde")]
8use serde_with::serde_as;
9#[cfg(feature = "frozen-abi")]
10use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
11#[cfg(any(target_os = "solana", feature = "bincode"))]
12use solana_instruction::error::InstructionError;
13use {
14    super::{BlockTimestamp, LandedVote, VoteInit, VoteInitV2, BLS_PUBLIC_KEY_COMPRESSED_SIZE},
15    crate::authorized_voters::AuthorizedVoters,
16    solana_clock::{Clock, Epoch, Slot},
17    solana_pubkey::Pubkey,
18    std::{collections::VecDeque, fmt::Debug},
19};
20
21#[cfg_attr(
22    feature = "frozen-abi",
23    frozen_abi(
24        api_digest = "ALZS4x22Ga8M6KkLVgdEJu3ZQUUSBkFHAkErmSvFLzUM",
25        abi_digest = "DqcDSgyayprZqMjBEHzUuB6afr3rGckVqHHBWdvVZefU",
26        abi_serializer = ["bincode", "wincode"]
27    ),
28    derive(AbiExample, StableAbi, StableAbiSample)
29)]
30#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)]
31#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
32#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
33#[derive(Debug, Default, PartialEq, Eq, Clone)]
34#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
35pub struct VoteStateV4 {
36    /// The node that votes in this account.
37    pub node_pubkey: Pubkey,
38    /// The signer for withdrawals.
39    pub authorized_withdrawer: Pubkey,
40
41    /// The collector account for inflation rewards.
42    pub inflation_rewards_collector: Pubkey,
43    /// The collector account for block revenue.
44    pub block_revenue_collector: Pubkey,
45
46    /// Basis points (0-10,000) that represent how much of the inflation
47    /// rewards should be given to this vote account.
48    pub inflation_rewards_commission_bps: u16,
49    /// Basis points (0-10,000) that represent how much of the block revenue
50    /// should be given to this vote account.
51    pub block_revenue_commission_bps: u16,
52
53    /// Reward amount pending distribution to stake delegators.
54    pub pending_delegator_rewards: u64,
55
56    /// Compressed BLS pubkey for Alpenglow.
57    #[cfg_attr(
58        feature = "serde",
59        serde_as(as = "Option<[_; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>")
60    )]
61    pub bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
62
63    pub votes: VecDeque<LandedVote>,
64    pub root_slot: Option<Slot>,
65
66    /// The signer for vote transactions.
67    /// Contains entries for the current epoch and the previous epoch.
68    pub authorized_voters: AuthorizedVoters,
69
70    /// History of credits earned by the end of each epoch.
71    /// Each tuple is (Epoch, credits, prev_credits).
72    pub epoch_credits: Vec<(Epoch, u64, u64)>,
73
74    /// Most recent timestamp submitted with a vote.
75    pub last_timestamp: BlockTimestamp,
76}
77
78impl VoteStateV4 {
79    /// Upper limit on the size of the Vote State
80    /// when votes.len() is MAX_LOCKOUT_HISTORY.
81    pub const fn size_of() -> usize {
82        3762 // Same size as V3 to avoid account resizing
83    }
84
85    pub fn new_with_defaults(vote_pubkey: &Pubkey, vote_init: &VoteInit, clock: &Clock) -> Self {
86        Self {
87            node_pubkey: vote_init.node_pubkey,
88            authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
89            authorized_withdrawer: vote_init.authorized_withdrawer,
90            // SAFETY: u16::MAX > u8::MAX * 100
91            inflation_rewards_commission_bps: (vote_init.commission as u16).saturating_mul(100),
92            // Per SIMD-0185, set default collectors and commission.
93            inflation_rewards_collector: *vote_pubkey,
94            block_revenue_collector: vote_init.node_pubkey,
95            block_revenue_commission_bps: 10_000, // 100%
96            ..Self::default()
97        }
98    }
99
100    /// Creates a new `VoteStateV4` from a `VoteInitV2` and the collector
101    /// account addresses.
102    pub fn new(
103        vote_init: &VoteInitV2,
104        inflation_rewards_collector: &Pubkey,
105        block_revenue_collector: &Pubkey,
106        clock: &Clock,
107    ) -> Self {
108        Self {
109            node_pubkey: vote_init.node_pubkey,
110            authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
111            bls_pubkey_compressed: Some(vote_init.authorized_voter_bls_pubkey),
112            authorized_withdrawer: vote_init.authorized_withdrawer,
113            inflation_rewards_commission_bps: vote_init.inflation_rewards_commission_bps,
114            inflation_rewards_collector: *inflation_rewards_collector,
115            block_revenue_commission_bps: vote_init.block_revenue_commission_bps,
116            block_revenue_collector: *block_revenue_collector,
117            ..Self::default()
118        }
119    }
120
121    #[cfg(any(target_os = "solana", feature = "bincode"))]
122    pub fn deserialize(input: &[u8], vote_pubkey: &Pubkey) -> Result<Self, InstructionError> {
123        let mut vote_state = Self::default();
124        Self::deserialize_into(input, &mut vote_state, vote_pubkey)?;
125        Ok(vote_state)
126    }
127
128    /// Deserializes the input `VoteStateVersions` buffer directly into the provided `VoteStateV4`.
129    ///
130    /// V0_23_5 is not supported. Supported versions: V1_14_11, V3, V4.
131    ///
132    /// On success, `vote_state` reflects the state of the input data. On failure, `vote_state` is
133    /// reset to `VoteStateV4::default()`.
134    #[cfg(any(target_os = "solana", feature = "bincode"))]
135    pub fn deserialize_into(
136        input: &[u8],
137        vote_state: &mut VoteStateV4,
138        vote_pubkey: &Pubkey,
139    ) -> Result<(), InstructionError> {
140        use super::vote_state_deserialize;
141        vote_state_deserialize::deserialize_into(input, vote_state, |input, vote_state| {
142            Self::deserialize_into_ptr(input, vote_state, vote_pubkey)
143        })
144    }
145
146    /// Deserializes the input `VoteStateVersions` buffer directly into the provided
147    /// `MaybeUninit<VoteStateV4>`.
148    ///
149    /// V0_23_5 is not supported. Supported versions: V1_14_11, V3, V4.
150    ///
151    /// On success, `vote_state` is fully initialized and can be converted to
152    /// `VoteStateV4` using
153    /// [`MaybeUninit::assume_init`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.assume_init).
154    /// On failure, `vote_state` may still be uninitialized and must not be
155    /// converted to `VoteStateV4`.
156    #[cfg(any(target_os = "solana", feature = "bincode"))]
157    pub fn deserialize_into_uninit(
158        input: &[u8],
159        vote_state: &mut std::mem::MaybeUninit<VoteStateV4>,
160        vote_pubkey: &Pubkey,
161    ) -> Result<(), InstructionError> {
162        Self::deserialize_into_ptr(input, vote_state.as_mut_ptr(), vote_pubkey)
163    }
164
165    #[cfg(any(target_os = "solana", feature = "bincode"))]
166    fn deserialize_into_ptr(
167        input: &[u8],
168        vote_state: *mut VoteStateV4,
169        vote_pubkey: &Pubkey,
170    ) -> Result<(), InstructionError> {
171        use super::vote_state_deserialize::{deserialize_vote_state_into_v4, SourceVersion};
172
173        let mut cursor = std::io::Cursor::new(input);
174
175        let variant = solana_serialize_utils::cursor::read_u32(&mut cursor)?;
176        match variant {
177            // Variant 0 is not a valid vote state.
178            0 => Err(InstructionError::InvalidAccountData),
179            // V1_14_11
180            1 => deserialize_vote_state_into_v4(
181                &mut cursor,
182                vote_state,
183                SourceVersion::V1_14_11 { vote_pubkey },
184            ),
185            // V3
186            2 => deserialize_vote_state_into_v4(
187                &mut cursor,
188                vote_state,
189                SourceVersion::V3 { vote_pubkey },
190            ),
191            // V4
192            3 => deserialize_vote_state_into_v4(&mut cursor, vote_state, SourceVersion::V4),
193            _ => Err(InstructionError::InvalidAccountData),
194        }?;
195
196        Ok(())
197    }
198
199    #[cfg(feature = "bincode")]
200    pub fn serialize(
201        versioned: &VoteStateVersions,
202        output: &mut [u8],
203    ) -> Result<(), InstructionError> {
204        bincode::serialize_into(output, versioned).map_err(|err| match *err {
205            bincode::ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
206            _ => InstructionError::GenericError,
207        })
208    }
209
210    pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
211        data.len() == VoteStateV4::size_of() && data[..4] == [3, 0, 0, 0] // little-endian 3u32
212                                                                          // Always initialized
213    }
214
215    /// Number of credits owed to this account.
216    pub fn credits(&self) -> u64 {
217        self.epoch_credits.last().map_or(0, |v| v.1)
218    }
219
220    #[cfg(test)]
221    pub(crate) fn get_max_sized_vote_state() -> Self {
222        use super::{MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY};
223
224        // V4 stores a maximum of 4 authorized voter entries.
225        const MAX_AUTHORIZED_VOTERS: usize = 4;
226
227        let mut authorized_voters = AuthorizedVoters::default();
228        for i in 0..MAX_AUTHORIZED_VOTERS as u64 {
229            authorized_voters.insert(i, Pubkey::new_unique());
230        }
231
232        Self {
233            votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
234            root_slot: Some(u64::MAX),
235            epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
236            authorized_voters,
237            ..Self::default()
238        }
239    }
240
241    #[cfg(test)]
242    fn new_rand_for_tests(node_pubkey: Pubkey, root_slot: Slot) -> Self {
243        let votes = (1..32)
244            .map(|x| LandedVote {
245                latency: 0,
246                lockout: super::Lockout::new_with_confirmation_count(
247                    u64::from(x).saturating_add(root_slot),
248                    32_u32.saturating_sub(x),
249                ),
250            })
251            .collect();
252        Self {
253            node_pubkey,
254            root_slot: Some(root_slot),
255            votes,
256            ..Self::default()
257        }
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use {
264        super::{
265            super::{
266                CircBuf, Lockout, VoteState1_14_11, VoteStateV3, VoteStateVersions,
267                BLS_PUBLIC_KEY_COMPRESSED_SIZE, MAX_LOCKOUT_HISTORY,
268            },
269            *,
270        },
271        arbitrary::Unstructured,
272        bincode::serialized_size,
273        core::mem::MaybeUninit,
274        rand::Rng,
275        solana_instruction::error::InstructionError,
276        test_case::test_matrix,
277    };
278
279    #[test]
280    fn test_size_of() {
281        let vote_state = VoteStateV4::get_max_sized_vote_state();
282        let vote_state = VoteStateVersions::new_v4(vote_state);
283        let size = serialized_size(&vote_state).unwrap();
284        assert!(size < VoteStateV4::size_of() as u64); // v4 is smaller than the max size
285    }
286
287    #[test]
288    fn test_minimum_balance() {
289        let rent = solana_rent::Rent::default();
290        let minimum_balance = rent.minimum_balance(VoteStateV4::size_of());
291        // golden, may need updating when vote_state grows
292        assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
293    }
294
295    #[test]
296    fn test_new_with_defaults() {
297        let vote_pubkey = Pubkey::new_unique();
298        let vote_init = VoteInit {
299            node_pubkey: Pubkey::new_unique(),
300            authorized_voter: Pubkey::new_unique(),
301            authorized_withdrawer: Pubkey::new_unique(),
302            commission: 50,
303        };
304        let clock = Clock {
305            epoch: 7,
306            ..Clock::default()
307        };
308        let v4 = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock);
309
310        assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
311        assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
312        assert_eq!(v4.inflation_rewards_commission_bps, 5000);
313        assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
314        assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
315        assert_eq!(v4.block_revenue_commission_bps, 10_000);
316        assert_eq!(v4.pending_delegator_rewards, 0);
317        assert_eq!(v4.bls_pubkey_compressed, None);
318        assert!(!v4.authorized_voters.is_empty());
319    }
320
321    #[test]
322    fn test_vote_serialize() {
323        // Use two different pubkeys to demonstrate that v4 ignores the
324        // `vote_pubkey` parameter.
325        let vote_pubkey_for_deserialize = Pubkey::new_unique();
326        let vote_pubkey_for_convert = Pubkey::new_unique();
327
328        let mut buffer: Vec<u8> = vec![0; VoteStateV4::size_of()];
329        let mut vote_state = VoteStateV4::default();
330        vote_state
331            .votes
332            .resize(MAX_LOCKOUT_HISTORY, LandedVote::default());
333        vote_state.root_slot = Some(1);
334        let versioned = VoteStateVersions::new_v4(vote_state);
335        assert!(VoteStateV4::serialize(&versioned, &mut buffer[0..4]).is_err());
336        VoteStateV4::serialize(&versioned, &mut buffer).unwrap();
337        assert_eq!(
338            VoteStateV4::deserialize(&buffer, &vote_pubkey_for_deserialize).unwrap(),
339            versioned
340                .try_convert_to_v4(&vote_pubkey_for_convert)
341                .unwrap()
342        );
343    }
344
345    #[test]
346    fn test_vote_deserialize_into() {
347        let vote_pubkey = Pubkey::new_unique();
348
349        // base case
350        let target_vote_state = VoteStateV4::default();
351        let vote_state_buf =
352            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
353
354        let mut test_vote_state = VoteStateV4::default();
355        VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey).unwrap();
356
357        assert_eq!(target_vote_state, test_vote_state);
358
359        // variant
360        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
361        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
362        for _ in 0..1000 {
363            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
364            let mut unstructured = Unstructured::new(&raw_data);
365
366            let target_vote_state_versions =
367                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
368            let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
369            let target_vote_state = target_vote_state_versions
370                .try_convert_to_v4(&vote_pubkey)
371                .unwrap();
372
373            let mut test_vote_state = VoteStateV4::default();
374            VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
375                .unwrap();
376
377            assert_eq!(target_vote_state, test_vote_state);
378        }
379    }
380
381    #[test]
382    fn test_deserialize_into_reuse_across_success_and_failure() {
383        let vote_pubkey = Pubkey::new_unique();
384        let mut vote_state = VoteStateV4::default();
385
386        // 1. Valid V4 data.
387        let v4_a = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 100);
388        let buf_a = bincode::serialize(&VoteStateVersions::new_v4(v4_a.clone())).unwrap();
389        VoteStateV4::deserialize_into(&buf_a, &mut vote_state, &vote_pubkey).unwrap();
390        assert_eq!(vote_state, v4_a);
391
392        // 2. Different valid V4 data.
393        let v4_b = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 200);
394        let buf_b = bincode::serialize(&VoteStateVersions::new_v4(v4_b.clone())).unwrap();
395        VoteStateV4::deserialize_into(&buf_b, &mut vote_state, &vote_pubkey).unwrap();
396        assert_eq!(vote_state, v4_b);
397
398        // 3. Invalid data — resets to default.
399        let mut bad_buf = buf_b.clone();
400        bad_buf.truncate(bad_buf.len() - 1);
401        VoteStateV4::deserialize_into(&bad_buf, &mut vote_state, &vote_pubkey).unwrap_err();
402        assert_eq!(vote_state, VoteStateV4::default());
403
404        // 4. Valid again after error.
405        VoteStateV4::deserialize_into(&buf_a, &mut vote_state, &vote_pubkey).unwrap();
406        assert_eq!(vote_state, v4_a);
407    }
408
409    #[test]
410    fn test_vote_deserialize_into_trailing_data() {
411        let vote_pubkey = Pubkey::new_unique();
412        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
413        let vote_state_buf =
414            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
415
416        // Trailing garbage data is ignored.
417        let mut buf_with_garbage = vote_state_buf.clone();
418        buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
419        let mut test_vote_state = VoteStateV4::default();
420        VoteStateV4::deserialize_into(&buf_with_garbage, &mut test_vote_state, &vote_pubkey)
421            .unwrap();
422        assert_eq!(target_vote_state, test_vote_state);
423
424        // Trailing zeroes are ignored.
425        let mut buf_with_zeroes = vote_state_buf;
426        buf_with_zeroes.extend_from_slice(&[0u8; 64]);
427        let mut test_vote_state = VoteStateV4::default();
428        VoteStateV4::deserialize_into(&buf_with_zeroes, &mut test_vote_state, &vote_pubkey)
429            .unwrap();
430        assert_eq!(target_vote_state, test_vote_state);
431    }
432
433    #[test]
434    fn test_vote_deserialize_into_error() {
435        let vote_pubkey = Pubkey::new_unique();
436
437        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
438        let mut vote_state_buf =
439            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
440        let len = vote_state_buf.len();
441        vote_state_buf.truncate(len - 1);
442
443        let mut test_vote_state = VoteStateV4::default();
444        VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
445            .unwrap_err();
446        assert_eq!(test_vote_state, VoteStateV4::default());
447    }
448
449    #[test]
450    fn test_vote_deserialize_into_error_with_pre_state() {
451        let vote_pubkey = Pubkey::new_unique();
452
453        // Start with a fully-populated state with heap allocations.
454        let mut vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
455        vote_state.epoch_credits = vec![(0, 100, 0), (1, 200, 100), (2, 300, 200)];
456
457        // Deserialize truncated buffer — triggers error + DropGuard.
458        let mut buf =
459            bincode::serialize(&VoteStateVersions::new_v4(VoteStateV4::default())).unwrap();
460        buf.truncate(buf.len() - 1);
461
462        VoteStateV4::deserialize_into(&buf, &mut vote_state, &vote_pubkey).unwrap_err();
463        // DropGuard should have reset to default despite pre-existing heap data.
464        assert_eq!(vote_state, VoteStateV4::default());
465    }
466
467    #[test]
468    fn test_deserialize_into_uninit_no_reset_on_error() {
469        // Contrast with `test_vote_deserialize_into_error` which verifies
470        // that `deserialize_into` resets to `T::default()` via DropGuard.
471        // `deserialize_into_uninit` does NOT reset — the MaybeUninit may
472        // remain partially written and must not be assumed initialized.
473        let vote_pubkey = Pubkey::new_unique();
474        let target = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
475        let mut buf = bincode::serialize(&VoteStateVersions::new_v4(target)).unwrap();
476        buf.truncate(buf.len() - 1);
477
478        let mut test_vote_state = MaybeUninit::uninit();
479        let err = VoteStateV4::deserialize_into_uninit(&buf, &mut test_vote_state, &vote_pubkey);
480        assert_eq!(err, Err(InstructionError::InvalidAccountData));
481        // test_vote_state is NOT guaranteed initialized — must not assume_init.
482    }
483
484    #[test]
485    fn test_vote_deserialize_into_uninit() {
486        let vote_pubkey = Pubkey::new_unique();
487
488        // base case
489        let target_vote_state = VoteStateV4::default();
490        let vote_state_buf =
491            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
492
493        let mut test_vote_state = MaybeUninit::uninit();
494        VoteStateV4::deserialize_into_uninit(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
495            .unwrap();
496        let test_vote_state = unsafe { test_vote_state.assume_init() };
497
498        assert_eq!(target_vote_state, test_vote_state);
499
500        // variant
501        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
502        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
503        for _ in 0..1000 {
504            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
505            let mut unstructured = Unstructured::new(&raw_data);
506
507            let target_vote_state_versions =
508                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
509            let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
510            let target_vote_state = target_vote_state_versions
511                .try_convert_to_v4(&Pubkey::default())
512                .unwrap();
513
514            let mut test_vote_state = MaybeUninit::uninit();
515            VoteStateV4::deserialize_into_uninit(
516                &vote_state_buf,
517                &mut test_vote_state,
518                &Pubkey::default(),
519            )
520            .unwrap();
521            let test_vote_state = unsafe { test_vote_state.assume_init() };
522
523            assert_eq!(target_vote_state, test_vote_state);
524        }
525    }
526
527    #[test]
528    fn test_vote_deserialize_into_uninit_trailing_data() {
529        let vote_pubkey = Pubkey::new_unique();
530        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
531        let vote_state_buf =
532            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
533
534        // Trailing garbage data is ignored.
535        let mut buf_with_garbage = vote_state_buf.clone();
536        buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
537        let mut test_vote_state = MaybeUninit::uninit();
538        VoteStateV4::deserialize_into_uninit(&buf_with_garbage, &mut test_vote_state, &vote_pubkey)
539            .unwrap();
540        let test_vote_state = unsafe { test_vote_state.assume_init() };
541        assert_eq!(target_vote_state, test_vote_state);
542
543        // Trailing zeroes are ignored.
544        let mut buf_with_zeroes = vote_state_buf;
545        buf_with_zeroes.extend_from_slice(&[0u8; 64]);
546        let mut test_vote_state = MaybeUninit::uninit();
547        VoteStateV4::deserialize_into_uninit(&buf_with_zeroes, &mut test_vote_state, &vote_pubkey)
548            .unwrap();
549        let test_vote_state = unsafe { test_vote_state.assume_init() };
550        assert_eq!(target_vote_state, test_vote_state);
551    }
552
553    #[test]
554    fn test_vote_deserialize_into_uninit_nopanic() {
555        let vote_pubkey = Pubkey::new_unique();
556
557        // base case
558        let mut test_vote_state = MaybeUninit::uninit();
559        let e = VoteStateV4::deserialize_into_uninit(&[], &mut test_vote_state, &vote_pubkey)
560            .unwrap_err();
561        assert_eq!(e, InstructionError::InvalidAccountData);
562
563        // variant
564        let serialized_len_x4 = serialized_size(&VoteStateV4::default()).unwrap() * 4;
565        let mut rng = rand::rng();
566        for _ in 0..1000 {
567            let raw_data_length = rng.random_range(1..serialized_len_x4);
568            let mut raw_data: Vec<u8> = (0..raw_data_length).map(|_| rng.random::<u8>()).collect();
569
570            // pure random data will ~never have a valid enum tag, so lets help it out
571            if raw_data_length >= 4 && rng.random::<bool>() {
572                let tag = rng.random_range(1u8..=3);
573                raw_data[0] = tag;
574                raw_data[1] = 0;
575                raw_data[2] = 0;
576                raw_data[3] = 0;
577            }
578
579            // it is extremely improbable, though theoretically possible, for random bytes to be syntactically valid
580            // so we only check that the parser does not panic and that it succeeds or fails exactly in line with bincode
581            let mut test_vote_state = MaybeUninit::uninit();
582            let test_res =
583                VoteStateV4::deserialize_into_uninit(&raw_data, &mut test_vote_state, &vote_pubkey);
584            let bincode_res = bincode::deserialize::<VoteStateVersions>(&raw_data)
585                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
586
587            if test_res.is_err() {
588                assert!(bincode_res.is_err());
589            } else {
590                let test_vote_state = unsafe { test_vote_state.assume_init() };
591                assert_eq!(test_vote_state, bincode_res.unwrap());
592            }
593        }
594    }
595
596    #[test]
597    fn test_vote_deserialize_into_uninit_ill_sized() {
598        let vote_pubkey = Pubkey::new_unique();
599
600        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
601        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
602        for _ in 0..1000 {
603            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
604            let mut unstructured = Unstructured::new(&raw_data);
605
606            let original_vote_state_versions =
607                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
608            let original_buf = bincode::serialize(&original_vote_state_versions).unwrap();
609
610            let mut truncated_buf = original_buf.clone();
611            let mut expanded_buf = original_buf.clone();
612
613            truncated_buf.resize(original_buf.len() - 8, 0);
614            expanded_buf.resize(original_buf.len() + 8, 0);
615
616            // truncated fails
617            let mut test_vote_state = MaybeUninit::uninit();
618            let test_res = VoteStateV4::deserialize_into_uninit(
619                &truncated_buf,
620                &mut test_vote_state,
621                &vote_pubkey,
622            );
623            let bincode_res = bincode::deserialize::<VoteStateVersions>(&truncated_buf)
624                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
625
626            assert!(test_res.is_err());
627            assert!(bincode_res.is_err());
628
629            // expanded succeeds
630            let mut test_vote_state = MaybeUninit::uninit();
631            VoteStateV4::deserialize_into_uninit(&expanded_buf, &mut test_vote_state, &vote_pubkey)
632                .unwrap();
633            let bincode_res = bincode::deserialize::<VoteStateVersions>(&expanded_buf)
634                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
635
636            let test_vote_state = unsafe { test_vote_state.assume_init() };
637            assert_eq!(test_vote_state, bincode_res.unwrap());
638        }
639    }
640
641    #[test]
642    fn test_bls_pubkey_compressed() {
643        let vote_pubkey = Pubkey::new_unique();
644
645        let run_test = |start, expected| {
646            let versioned = VoteStateVersions::new_v4(start);
647            let serialized = bincode::serialize(&versioned).unwrap();
648            let deserialized = VoteStateV4::deserialize(&serialized, &vote_pubkey).unwrap();
649            assert_eq!(deserialized.bls_pubkey_compressed, expected);
650        };
651
652        // First try `None`.
653        let vote_state_none = VoteStateV4::default();
654        assert_eq!(vote_state_none.bls_pubkey_compressed, None);
655        run_test(vote_state_none, None);
656
657        // Now try `Some`.
658        let test_bls_key = [42u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
659        let vote_state_some = VoteStateV4 {
660            bls_pubkey_compressed: Some(test_bls_key),
661            ..VoteStateV4::default()
662        };
663        assert_eq!(vote_state_some.bls_pubkey_compressed, Some(test_bls_key));
664        run_test(vote_state_some, Some(test_bls_key));
665    }
666
667    #[test]
668    fn test_deserialize_invalid_variant_tags() {
669        let vote_pubkey = Pubkey::new_unique();
670        let mut buf = vec![0u8; VoteStateV4::size_of()];
671
672        // Tag 0 (V0_23_5 — rejected).
673        let mut vs = VoteStateV4::default();
674        assert_eq!(
675            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
676            Err(InstructionError::InvalidAccountData)
677        );
678
679        // Tag 4 (unknown).
680        buf[..4].copy_from_slice(&4u32.to_le_bytes());
681        assert_eq!(
682            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
683            Err(InstructionError::InvalidAccountData)
684        );
685
686        // Tag u32::MAX.
687        buf[..4].copy_from_slice(&u32::MAX.to_le_bytes());
688        assert_eq!(
689            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
690            Err(InstructionError::InvalidAccountData)
691        );
692    }
693
694    #[test]
695    fn test_invalid_option_discriminants() {
696        let vote_pubkey = Pubkey::new_unique();
697        let vote_state = VoteStateV4 {
698            root_slot: Some(42),
699            ..VoteStateV4::default()
700        };
701        let valid_buf = bincode::serialize(&VoteStateVersions::new_v4(vote_state)).unwrap();
702
703        // bls_pubkey_compressed Option discriminant.
704        // tag(4) + node_pubkey(32) + authorized_withdrawer(32) +
705        // inflation_rewards_collector(32) + block_revenue_collector(32) +
706        // commission_bps(2) + block_revenue_bps(2) + pending_rewards(8)
707        let bls_offset = 4 + 32 + 32 + 32 + 32 + 2 + 2 + 8;
708        assert_eq!(valid_buf[bls_offset], 0); // None
709
710        {
711            let mut buf = valid_buf.clone();
712            buf[bls_offset] = 2;
713            let mut vs = VoteStateV4::default();
714            assert_eq!(
715                VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
716                Err(InstructionError::InvalidAccountData)
717            );
718        }
719
720        // root_slot Option discriminant.
721        // bls(1 for None) + votes_count(8)
722        let root_slot_offset = bls_offset + 1 + 8;
723        assert_eq!(valid_buf[root_slot_offset], 1); // Some
724
725        {
726            let mut buf = valid_buf.clone();
727            buf[root_slot_offset] = 2;
728            let mut vs = VoteStateV4::default();
729            assert_eq!(
730                VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
731                Err(InstructionError::InvalidAccountData)
732            );
733        }
734    }
735
736    #[allow(clippy::arithmetic_side_effects)]
737    #[test_matrix(
738        [0u8, 50, 100, 255],
739        [0usize, 1, 2]
740    )]
741    fn test_deserialize_prior_versions_simd_0185(commission: u8, prior_voters_count: usize) {
742        let vote_pubkey = Pubkey::new_unique();
743        let node_pubkey = Pubkey::new_unique();
744        let root_slot = Some(42);
745        let epoch_credits = vec![(0, 100, 0), (1, 200, 100)];
746        let last_timestamp = BlockTimestamp {
747            slot: 999,
748            timestamp: 12345,
749        };
750        let mut prior_voters = CircBuf::default();
751        for i in 0..prior_voters_count {
752            prior_voters.append((Pubkey::new_unique(), i as u64 * 5, (i as u64 + 1) * 5));
753        }
754
755        // SIMD-0185 specifies the following defaults when converting older
756        // vote state versions to V4:
757        //
758        //   inflation_rewards_collector:      vote_pubkey
759        //   block_revenue_collector:          old_vote_state.node_pubkey
760        //   inflation_rewards_commission_bps: 100 * (old_vote_state.commission as u16)
761        //   block_revenue_commission_bps:     10_000 (100%)
762        //   pending_delegator_rewards:        0
763        //   bls_pubkey_compressed:            None
764        let assert_simd_0185_defaults = |v4: &VoteStateV4| {
765            assert_eq!(
766                v4.inflation_rewards_commission_bps,
767                u16::from(commission) * 100
768            );
769            assert_eq!(v4.block_revenue_commission_bps, 10_000);
770            assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
771            assert_eq!(v4.block_revenue_collector, node_pubkey);
772            assert_eq!(v4.pending_delegator_rewards, 0);
773            assert_eq!(v4.bls_pubkey_compressed, None);
774
775            // Assert fields after `prior_voters` survived `skip_prior_voters`.
776            assert_eq!(v4.epoch_credits, epoch_credits);
777            assert_eq!(v4.last_timestamp, last_timestamp);
778        };
779
780        // V1_14_11 → V4
781        let v1_state = VoteState1_14_11 {
782            node_pubkey,
783            commission,
784            root_slot,
785            epoch_credits: epoch_credits.clone(),
786            last_timestamp: last_timestamp.clone(),
787            prior_voters: prior_voters.clone(),
788            ..VoteState1_14_11::default()
789        };
790        let buf = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(v1_state))).unwrap();
791        let v4 = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
792        assert_simd_0185_defaults(&v4);
793
794        // V3 → V4
795        let v3_state = VoteStateV3 {
796            node_pubkey,
797            commission,
798            root_slot,
799            epoch_credits: epoch_credits.clone(),
800            last_timestamp: last_timestamp.clone(),
801            prior_voters: prior_voters.clone(),
802            ..VoteStateV3::default()
803        };
804        let buf = bincode::serialize(&VoteStateVersions::new_v3(v3_state)).unwrap();
805        let v4 = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
806        assert_simd_0185_defaults(&v4);
807    }
808
809    #[test]
810    fn test_has_latency() {
811        let vote_pubkey = Pubkey::new_unique();
812
813        // V1_14_11 → V4: all latencies should be 0.
814        let mut v1_state = VoteState1_14_11::default();
815        v1_state.votes.push_back(Lockout::new(100));
816        v1_state.votes.push_back(Lockout::new(200));
817        let buf = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(v1_state))).unwrap();
818        let deserialized = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
819        assert_eq!(deserialized.votes.len(), 2);
820        for vote in &deserialized.votes {
821            assert_eq!(vote.latency, 0);
822        }
823
824        // V4 with non-zero latency: preserved.
825        let mut v4_state = VoteStateV4::default();
826        v4_state.votes.push_back(LandedVote {
827            latency: 42,
828            lockout: Lockout::new(100),
829        });
830        let buf = bincode::serialize(&VoteStateVersions::new_v4(v4_state)).unwrap();
831        let deserialized = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
832        assert_eq!(deserialized.votes[0].latency, 42);
833    }
834}