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        test_case::test_matrix,
276    };
277
278    #[test]
279    fn test_size_of() {
280        let vote_state = VoteStateV4::get_max_sized_vote_state();
281        let vote_state = VoteStateVersions::new_v4(vote_state);
282        let size = serialized_size(&vote_state).unwrap();
283        assert!(size < VoteStateV4::size_of() as u64); // v4 is smaller than the max size
284    }
285
286    #[test]
287    fn test_minimum_balance() {
288        let rent = solana_rent::Rent::default();
289        let minimum_balance = rent.minimum_balance(VoteStateV4::size_of());
290        // golden, may need updating when vote_state grows
291        assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
292    }
293
294    #[test]
295    fn test_new_with_defaults() {
296        let vote_pubkey = Pubkey::new_unique();
297        let vote_init = VoteInit {
298            node_pubkey: Pubkey::new_unique(),
299            authorized_voter: Pubkey::new_unique(),
300            authorized_withdrawer: Pubkey::new_unique(),
301            commission: 50,
302        };
303        let clock = Clock {
304            epoch: 7,
305            ..Clock::default()
306        };
307        let v4 = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock);
308
309        assert_eq!(v4.node_pubkey, vote_init.node_pubkey);
310        assert_eq!(v4.authorized_withdrawer, vote_init.authorized_withdrawer);
311        assert_eq!(v4.inflation_rewards_commission_bps, 5000);
312        assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
313        assert_eq!(v4.block_revenue_collector, vote_init.node_pubkey);
314        assert_eq!(v4.block_revenue_commission_bps, 10_000);
315        assert_eq!(v4.pending_delegator_rewards, 0);
316        assert_eq!(v4.bls_pubkey_compressed, None);
317        assert!(!v4.authorized_voters.is_empty());
318    }
319
320    #[test]
321    fn test_vote_serialize() {
322        // Use two different pubkeys to demonstrate that v4 ignores the
323        // `vote_pubkey` parameter.
324        let vote_pubkey_for_deserialize = Pubkey::new_unique();
325        let vote_pubkey_for_convert = Pubkey::new_unique();
326
327        let mut buffer: Vec<u8> = vec![0; VoteStateV4::size_of()];
328        let mut vote_state = VoteStateV4::default();
329        vote_state
330            .votes
331            .resize(MAX_LOCKOUT_HISTORY, LandedVote::default());
332        vote_state.root_slot = Some(1);
333        let versioned = VoteStateVersions::new_v4(vote_state);
334        assert!(VoteStateV4::serialize(&versioned, &mut buffer[0..4]).is_err());
335        VoteStateV4::serialize(&versioned, &mut buffer).unwrap();
336        assert_eq!(
337            VoteStateV4::deserialize(&buffer, &vote_pubkey_for_deserialize).unwrap(),
338            versioned
339                .try_convert_to_v4(&vote_pubkey_for_convert)
340                .unwrap()
341        );
342    }
343
344    #[test]
345    fn test_vote_deserialize_into() {
346        let vote_pubkey = Pubkey::new_unique();
347
348        // base case
349        let target_vote_state = VoteStateV4::default();
350        let vote_state_buf =
351            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
352
353        let mut test_vote_state = VoteStateV4::default();
354        VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey).unwrap();
355
356        assert_eq!(target_vote_state, test_vote_state);
357
358        // variant
359        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
360        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
361        for _ in 0..1000 {
362            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
363            let mut unstructured = Unstructured::new(&raw_data);
364
365            let target_vote_state_versions =
366                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
367            let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
368            let target_vote_state = target_vote_state_versions
369                .try_convert_to_v4(&vote_pubkey)
370                .unwrap();
371
372            let mut test_vote_state = VoteStateV4::default();
373            VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
374                .unwrap();
375
376            assert_eq!(target_vote_state, test_vote_state);
377        }
378    }
379
380    #[test]
381    fn test_deserialize_into_reuse_across_success_and_failure() {
382        let vote_pubkey = Pubkey::new_unique();
383        let mut vote_state = VoteStateV4::default();
384
385        // 1. Valid V4 data.
386        let v4_a = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 100);
387        let buf_a = bincode::serialize(&VoteStateVersions::new_v4(v4_a.clone())).unwrap();
388        VoteStateV4::deserialize_into(&buf_a, &mut vote_state, &vote_pubkey).unwrap();
389        assert_eq!(vote_state, v4_a);
390
391        // 2. Different valid V4 data.
392        let v4_b = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 200);
393        let buf_b = bincode::serialize(&VoteStateVersions::new_v4(v4_b.clone())).unwrap();
394        VoteStateV4::deserialize_into(&buf_b, &mut vote_state, &vote_pubkey).unwrap();
395        assert_eq!(vote_state, v4_b);
396
397        // 3. Invalid data — resets to default.
398        let mut bad_buf = buf_b.clone();
399        bad_buf.truncate(bad_buf.len() - 1);
400        VoteStateV4::deserialize_into(&bad_buf, &mut vote_state, &vote_pubkey).unwrap_err();
401        assert_eq!(vote_state, VoteStateV4::default());
402
403        // 4. Valid again after error.
404        VoteStateV4::deserialize_into(&buf_a, &mut vote_state, &vote_pubkey).unwrap();
405        assert_eq!(vote_state, v4_a);
406    }
407
408    #[test]
409    fn test_vote_deserialize_into_trailing_data() {
410        let vote_pubkey = Pubkey::new_unique();
411        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
412        let vote_state_buf =
413            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
414
415        // Trailing garbage data is ignored.
416        let mut buf_with_garbage = vote_state_buf.clone();
417        buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
418        let mut test_vote_state = VoteStateV4::default();
419        VoteStateV4::deserialize_into(&buf_with_garbage, &mut test_vote_state, &vote_pubkey)
420            .unwrap();
421        assert_eq!(target_vote_state, test_vote_state);
422
423        // Trailing zeroes are ignored.
424        let mut buf_with_zeroes = vote_state_buf;
425        buf_with_zeroes.extend_from_slice(&[0u8; 64]);
426        let mut test_vote_state = VoteStateV4::default();
427        VoteStateV4::deserialize_into(&buf_with_zeroes, &mut test_vote_state, &vote_pubkey)
428            .unwrap();
429        assert_eq!(target_vote_state, test_vote_state);
430    }
431
432    #[test]
433    fn test_vote_deserialize_into_error() {
434        let vote_pubkey = Pubkey::new_unique();
435
436        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
437        let mut vote_state_buf =
438            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
439        let len = vote_state_buf.len();
440        vote_state_buf.truncate(len - 1);
441
442        let mut test_vote_state = VoteStateV4::default();
443        VoteStateV4::deserialize_into(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
444            .unwrap_err();
445        assert_eq!(test_vote_state, VoteStateV4::default());
446    }
447
448    #[test]
449    fn test_vote_deserialize_into_error_with_pre_state() {
450        let vote_pubkey = Pubkey::new_unique();
451
452        // Start with a fully-populated state with heap allocations.
453        let mut vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
454        vote_state.epoch_credits = vec![(0, 100, 0), (1, 200, 100), (2, 300, 200)];
455
456        // Deserialize truncated buffer — triggers error + DropGuard.
457        let mut buf =
458            bincode::serialize(&VoteStateVersions::new_v4(VoteStateV4::default())).unwrap();
459        buf.truncate(buf.len() - 1);
460
461        VoteStateV4::deserialize_into(&buf, &mut vote_state, &vote_pubkey).unwrap_err();
462        // DropGuard should have reset to default despite pre-existing heap data.
463        assert_eq!(vote_state, VoteStateV4::default());
464    }
465
466    #[test]
467    fn test_deserialize_into_uninit_no_reset_on_error() {
468        // Contrast with `test_vote_deserialize_into_error` which verifies
469        // that `deserialize_into` resets to `T::default()` via DropGuard.
470        // `deserialize_into_uninit` does NOT reset — the MaybeUninit may
471        // remain partially written and must not be assumed initialized.
472        let vote_pubkey = Pubkey::new_unique();
473        let target = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
474        let mut buf = bincode::serialize(&VoteStateVersions::new_v4(target)).unwrap();
475        buf.truncate(buf.len() - 1);
476
477        let mut test_vote_state = MaybeUninit::uninit();
478        let err = VoteStateV4::deserialize_into_uninit(&buf, &mut test_vote_state, &vote_pubkey);
479        assert_eq!(err, Err(InstructionError::InvalidAccountData));
480        // test_vote_state is NOT guaranteed initialized — must not assume_init.
481    }
482
483    #[test]
484    fn test_vote_deserialize_into_uninit() {
485        let vote_pubkey = Pubkey::new_unique();
486
487        // base case
488        let target_vote_state = VoteStateV4::default();
489        let vote_state_buf =
490            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
491
492        let mut test_vote_state = MaybeUninit::uninit();
493        VoteStateV4::deserialize_into_uninit(&vote_state_buf, &mut test_vote_state, &vote_pubkey)
494            .unwrap();
495        let test_vote_state = unsafe { test_vote_state.assume_init() };
496
497        assert_eq!(target_vote_state, test_vote_state);
498
499        // variant
500        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
501        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
502        for _ in 0..1000 {
503            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
504            let mut unstructured = Unstructured::new(&raw_data);
505
506            let target_vote_state_versions =
507                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
508            let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
509            let target_vote_state = target_vote_state_versions
510                .try_convert_to_v4(&Pubkey::default())
511                .unwrap();
512
513            let mut test_vote_state = MaybeUninit::uninit();
514            VoteStateV4::deserialize_into_uninit(
515                &vote_state_buf,
516                &mut test_vote_state,
517                &Pubkey::default(),
518            )
519            .unwrap();
520            let test_vote_state = unsafe { test_vote_state.assume_init() };
521
522            assert_eq!(target_vote_state, test_vote_state);
523        }
524    }
525
526    #[test]
527    fn test_vote_deserialize_into_uninit_trailing_data() {
528        let vote_pubkey = Pubkey::new_unique();
529        let target_vote_state = VoteStateV4::new_rand_for_tests(Pubkey::new_unique(), 42);
530        let vote_state_buf =
531            bincode::serialize(&VoteStateVersions::new_v4(target_vote_state.clone())).unwrap();
532
533        // Trailing garbage data is ignored.
534        let mut buf_with_garbage = vote_state_buf.clone();
535        buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
536        let mut test_vote_state = MaybeUninit::uninit();
537        VoteStateV4::deserialize_into_uninit(&buf_with_garbage, &mut test_vote_state, &vote_pubkey)
538            .unwrap();
539        let test_vote_state = unsafe { test_vote_state.assume_init() };
540        assert_eq!(target_vote_state, test_vote_state);
541
542        // Trailing zeroes are ignored.
543        let mut buf_with_zeroes = vote_state_buf;
544        buf_with_zeroes.extend_from_slice(&[0u8; 64]);
545        let mut test_vote_state = MaybeUninit::uninit();
546        VoteStateV4::deserialize_into_uninit(&buf_with_zeroes, &mut test_vote_state, &vote_pubkey)
547            .unwrap();
548        let test_vote_state = unsafe { test_vote_state.assume_init() };
549        assert_eq!(target_vote_state, test_vote_state);
550    }
551
552    #[test]
553    fn test_vote_deserialize_into_uninit_nopanic() {
554        let vote_pubkey = Pubkey::new_unique();
555
556        // base case
557        let mut test_vote_state = MaybeUninit::uninit();
558        let e = VoteStateV4::deserialize_into_uninit(&[], &mut test_vote_state, &vote_pubkey)
559            .unwrap_err();
560        assert_eq!(e, InstructionError::InvalidAccountData);
561
562        // variant
563        let serialized_len_x4 = serialized_size(&VoteStateV4::default()).unwrap() * 4;
564        let mut rng = rand::rng();
565        for _ in 0..1000 {
566            let raw_data_length = rng.random_range(1..serialized_len_x4);
567            let mut raw_data: Vec<u8> = (0..raw_data_length).map(|_| rng.random::<u8>()).collect();
568
569            // pure random data will ~never have a valid enum tag, so lets help it out
570            if raw_data_length >= 4 && rng.random::<bool>() {
571                let tag = rng.random_range(1u8..=3);
572                raw_data[0] = tag;
573                raw_data[1] = 0;
574                raw_data[2] = 0;
575                raw_data[3] = 0;
576            }
577
578            // it is extremely improbable, though theoretically possible, for random bytes to be syntactically valid
579            // so we only check that the parser does not panic and that it succeeds or fails exactly in line with bincode
580            let mut test_vote_state = MaybeUninit::uninit();
581            let test_res =
582                VoteStateV4::deserialize_into_uninit(&raw_data, &mut test_vote_state, &vote_pubkey);
583            let bincode_res = bincode::deserialize::<VoteStateVersions>(&raw_data)
584                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
585
586            if test_res.is_err() {
587                assert!(bincode_res.is_err());
588            } else {
589                let test_vote_state = unsafe { test_vote_state.assume_init() };
590                assert_eq!(test_vote_state, bincode_res.unwrap());
591            }
592        }
593    }
594
595    #[test]
596    fn test_vote_deserialize_into_uninit_ill_sized() {
597        let vote_pubkey = Pubkey::new_unique();
598
599        // provide 4x the minimum struct size in bytes to ensure we typically touch every field
600        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
601        for _ in 0..1000 {
602            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
603            let mut unstructured = Unstructured::new(&raw_data);
604
605            let original_vote_state_versions =
606                VoteStateVersions::arbitrary(&mut unstructured).unwrap();
607            let original_buf = bincode::serialize(&original_vote_state_versions).unwrap();
608
609            let mut truncated_buf = original_buf.clone();
610            let mut expanded_buf = original_buf.clone();
611
612            truncated_buf.resize(original_buf.len() - 8, 0);
613            expanded_buf.resize(original_buf.len() + 8, 0);
614
615            // truncated fails
616            let mut test_vote_state = MaybeUninit::uninit();
617            let test_res = VoteStateV4::deserialize_into_uninit(
618                &truncated_buf,
619                &mut test_vote_state,
620                &vote_pubkey,
621            );
622            let bincode_res = bincode::deserialize::<VoteStateVersions>(&truncated_buf)
623                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
624
625            assert!(test_res.is_err());
626            assert!(bincode_res.is_err());
627
628            // expanded succeeds
629            let mut test_vote_state = MaybeUninit::uninit();
630            VoteStateV4::deserialize_into_uninit(&expanded_buf, &mut test_vote_state, &vote_pubkey)
631                .unwrap();
632            let bincode_res = bincode::deserialize::<VoteStateVersions>(&expanded_buf)
633                .map(|versioned| versioned.try_convert_to_v4(&vote_pubkey).unwrap());
634
635            let test_vote_state = unsafe { test_vote_state.assume_init() };
636            assert_eq!(test_vote_state, bincode_res.unwrap());
637        }
638    }
639
640    #[test]
641    fn test_bls_pubkey_compressed() {
642        let vote_pubkey = Pubkey::new_unique();
643
644        let run_test = |start, expected| {
645            let versioned = VoteStateVersions::new_v4(start);
646            let serialized = bincode::serialize(&versioned).unwrap();
647            let deserialized = VoteStateV4::deserialize(&serialized, &vote_pubkey).unwrap();
648            assert_eq!(deserialized.bls_pubkey_compressed, expected);
649        };
650
651        // First try `None`.
652        let vote_state_none = VoteStateV4::default();
653        assert_eq!(vote_state_none.bls_pubkey_compressed, None);
654        run_test(vote_state_none, None);
655
656        // Now try `Some`.
657        let test_bls_key = [42u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
658        let vote_state_some = VoteStateV4 {
659            bls_pubkey_compressed: Some(test_bls_key),
660            ..VoteStateV4::default()
661        };
662        assert_eq!(vote_state_some.bls_pubkey_compressed, Some(test_bls_key));
663        run_test(vote_state_some, Some(test_bls_key));
664    }
665
666    #[test]
667    fn test_deserialize_invalid_variant_tags() {
668        let vote_pubkey = Pubkey::new_unique();
669        let mut buf = vec![0u8; VoteStateV4::size_of()];
670
671        // Tag 0 (V0_23_5 — rejected).
672        let mut vs = VoteStateV4::default();
673        assert_eq!(
674            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
675            Err(InstructionError::InvalidAccountData)
676        );
677
678        // Tag 4 (unknown).
679        buf[..4].copy_from_slice(&4u32.to_le_bytes());
680        assert_eq!(
681            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
682            Err(InstructionError::InvalidAccountData)
683        );
684
685        // Tag u32::MAX.
686        buf[..4].copy_from_slice(&u32::MAX.to_le_bytes());
687        assert_eq!(
688            VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
689            Err(InstructionError::InvalidAccountData)
690        );
691    }
692
693    #[test]
694    fn test_invalid_option_discriminants() {
695        let vote_pubkey = Pubkey::new_unique();
696        let vote_state = VoteStateV4 {
697            root_slot: Some(42),
698            ..VoteStateV4::default()
699        };
700        let valid_buf = bincode::serialize(&VoteStateVersions::new_v4(vote_state)).unwrap();
701
702        // bls_pubkey_compressed Option discriminant.
703        // tag(4) + node_pubkey(32) + authorized_withdrawer(32) +
704        // inflation_rewards_collector(32) + block_revenue_collector(32) +
705        // commission_bps(2) + block_revenue_bps(2) + pending_rewards(8)
706        let bls_offset = 4 + 32 + 32 + 32 + 32 + 2 + 2 + 8;
707        assert_eq!(valid_buf[bls_offset], 0); // None
708
709        {
710            let mut buf = valid_buf.clone();
711            buf[bls_offset] = 2;
712            let mut vs = VoteStateV4::default();
713            assert_eq!(
714                VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
715                Err(InstructionError::InvalidAccountData)
716            );
717        }
718
719        // root_slot Option discriminant.
720        // bls(1 for None) + votes_count(8)
721        let root_slot_offset = bls_offset + 1 + 8;
722        assert_eq!(valid_buf[root_slot_offset], 1); // Some
723
724        {
725            let mut buf = valid_buf.clone();
726            buf[root_slot_offset] = 2;
727            let mut vs = VoteStateV4::default();
728            assert_eq!(
729                VoteStateV4::deserialize_into(&buf, &mut vs, &vote_pubkey),
730                Err(InstructionError::InvalidAccountData)
731            );
732        }
733    }
734
735    #[allow(clippy::arithmetic_side_effects)]
736    #[test_matrix(
737        [0u8, 50, 100, 255],
738        [0usize, 1, 2]
739    )]
740    fn test_deserialize_prior_versions_simd_0185(commission: u8, prior_voters_count: usize) {
741        let vote_pubkey = Pubkey::new_unique();
742        let node_pubkey = Pubkey::new_unique();
743        let root_slot = Some(42);
744        let epoch_credits = vec![(0, 100, 0), (1, 200, 100)];
745        let last_timestamp = BlockTimestamp {
746            slot: 999,
747            timestamp: 12345,
748        };
749        let mut prior_voters = CircBuf::default();
750        for i in 0..prior_voters_count {
751            prior_voters.append((Pubkey::new_unique(), i as u64 * 5, (i as u64 + 1) * 5));
752        }
753
754        // SIMD-0185 specifies the following defaults when converting older
755        // vote state versions to V4:
756        //
757        //   inflation_rewards_collector:      vote_pubkey
758        //   block_revenue_collector:          old_vote_state.node_pubkey
759        //   inflation_rewards_commission_bps: 100 * (old_vote_state.commission as u16)
760        //   block_revenue_commission_bps:     10_000 (100%)
761        //   pending_delegator_rewards:        0
762        //   bls_pubkey_compressed:            None
763        let assert_simd_0185_defaults = |v4: &VoteStateV4| {
764            assert_eq!(
765                v4.inflation_rewards_commission_bps,
766                u16::from(commission) * 100
767            );
768            assert_eq!(v4.block_revenue_commission_bps, 10_000);
769            assert_eq!(v4.inflation_rewards_collector, vote_pubkey);
770            assert_eq!(v4.block_revenue_collector, node_pubkey);
771            assert_eq!(v4.pending_delegator_rewards, 0);
772            assert_eq!(v4.bls_pubkey_compressed, None);
773
774            // Assert fields after `prior_voters` survived `skip_prior_voters`.
775            assert_eq!(v4.epoch_credits, epoch_credits);
776            assert_eq!(v4.last_timestamp, last_timestamp);
777        };
778
779        // V1_14_11 → V4
780        let v1_state = VoteState1_14_11 {
781            node_pubkey,
782            commission,
783            root_slot,
784            epoch_credits: epoch_credits.clone(),
785            last_timestamp: last_timestamp.clone(),
786            prior_voters: prior_voters.clone(),
787            ..VoteState1_14_11::default()
788        };
789        let buf = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(v1_state))).unwrap();
790        let v4 = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
791        assert_simd_0185_defaults(&v4);
792
793        // V3 → V4
794        let v3_state = VoteStateV3 {
795            node_pubkey,
796            commission,
797            root_slot,
798            epoch_credits: epoch_credits.clone(),
799            last_timestamp: last_timestamp.clone(),
800            prior_voters: prior_voters.clone(),
801            ..VoteStateV3::default()
802        };
803        let buf = bincode::serialize(&VoteStateVersions::new_v3(v3_state)).unwrap();
804        let v4 = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
805        assert_simd_0185_defaults(&v4);
806    }
807
808    #[test]
809    fn test_has_latency() {
810        let vote_pubkey = Pubkey::new_unique();
811
812        // V1_14_11 → V4: all latencies should be 0.
813        let mut v1_state = VoteState1_14_11::default();
814        v1_state.votes.push_back(Lockout::new(100));
815        v1_state.votes.push_back(Lockout::new(200));
816        let buf = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(v1_state))).unwrap();
817        let deserialized = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
818        assert_eq!(deserialized.votes.len(), 2);
819        for vote in &deserialized.votes {
820            assert_eq!(vote.latency, 0);
821        }
822
823        // V4 with non-zero latency: preserved.
824        let mut v4_state = VoteStateV4::default();
825        v4_state.votes.push_back(LandedVote {
826            latency: 42,
827            lockout: Lockout::new(100),
828        });
829        let buf = bincode::serialize(&VoteStateVersions::new_v4(v4_state)).unwrap();
830        let deserialized = VoteStateV4::deserialize(&buf, &vote_pubkey).unwrap();
831        assert_eq!(deserialized.votes[0].latency, 42);
832    }
833}