Skip to main content

solana_vote_interface/state/
vote_state_versions.rs

1use crate::state::{vote_state_1_14_11::VoteState1_14_11, VoteStateV3, VoteStateV4};
2#[cfg(test)]
3use arbitrary::{Arbitrary, Unstructured};
4#[cfg(feature = "frozen-abi")]
5use solana_frozen_abi_macro::{frozen_abi, StableAbi, StableAbiSample};
6#[cfg(any(target_os = "solana", feature = "bincode"))]
7use solana_instruction_error::InstructionError;
8#[cfg(test)]
9use {
10    crate::state::{LandedVote, Lockout},
11    solana_pubkey::Pubkey,
12    std::collections::VecDeque,
13};
14
15#[cfg_attr(
16    feature = "frozen-abi",
17    frozen_abi(
18        abi_digest = "292pqhdmS6k7yrk5pUSQBpiQq1MLHb15pu6oM5WnWcug",
19        abi_serializer = ["bincode", "wincode"]
20    ),
21    derive(StableAbi, StableAbiSample)
22)]
23#[cfg_attr(
24    feature = "serde",
25    derive(serde_derive::Deserialize, serde_derive::Serialize)
26)]
27#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
28#[derive(Debug, PartialEq, Eq, Clone)]
29pub enum VoteStateVersions {
30    Uninitialized,
31    V1_14_11(Box<VoteState1_14_11>),
32    V3(Box<VoteStateV3>),
33    V4(Box<VoteStateV4>),
34}
35
36impl VoteStateVersions {
37    pub fn new_v3(vote_state: VoteStateV3) -> Self {
38        Self::V3(Box::new(vote_state))
39    }
40
41    pub fn new_v4(vote_state: VoteStateV4) -> Self {
42        Self::V4(Box::new(vote_state))
43    }
44
45    /// Convert from vote state `V1_14_11` or `V3` to `V3`.
46    ///
47    /// NOTE: Does not support conversion from `V4`. Attempting to convert from
48    /// v4 to v3 will throw an error.
49    #[cfg(test)]
50    pub(crate) fn try_convert_to_v3(self) -> Result<VoteStateV3, InstructionError> {
51        match self {
52            VoteStateVersions::Uninitialized => Err(InstructionError::UninitializedAccount),
53
54            VoteStateVersions::V1_14_11(state) => Ok(VoteStateV3 {
55                node_pubkey: state.node_pubkey,
56                authorized_withdrawer: state.authorized_withdrawer,
57                commission: state.commission,
58
59                votes: Self::landed_votes_from_lockouts(state.votes),
60
61                root_slot: state.root_slot,
62
63                authorized_voters: state.authorized_voters.clone(),
64
65                prior_voters: state.prior_voters,
66
67                epoch_credits: state.epoch_credits,
68
69                last_timestamp: state.last_timestamp,
70            }),
71
72            VoteStateVersions::V3(state) => Ok(*state),
73
74            // Cannot convert V4 to V3.
75            VoteStateVersions::V4(_) => Err(InstructionError::InvalidArgument),
76        }
77    }
78
79    #[cfg(test)]
80    pub(crate) fn try_convert_to_v4(
81        self,
82        vote_pubkey: &Pubkey,
83    ) -> Result<VoteStateV4, InstructionError> {
84        match self {
85            VoteStateVersions::Uninitialized => Err(InstructionError::UninitializedAccount),
86
87            VoteStateVersions::V1_14_11(state) => Ok(VoteStateV4 {
88                node_pubkey: state.node_pubkey,
89                authorized_withdrawer: state.authorized_withdrawer,
90                inflation_rewards_collector: *vote_pubkey,
91                block_revenue_collector: state.node_pubkey,
92                inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
93                block_revenue_commission_bps: 10_000u16,
94                pending_delegator_rewards: 0,
95                bls_pubkey_compressed: None,
96                votes: Self::landed_votes_from_lockouts(state.votes),
97                root_slot: state.root_slot,
98                authorized_voters: state.authorized_voters.clone(),
99                epoch_credits: state.epoch_credits,
100                last_timestamp: state.last_timestamp,
101            }),
102
103            VoteStateVersions::V3(state) => Ok(VoteStateV4 {
104                node_pubkey: state.node_pubkey,
105                authorized_withdrawer: state.authorized_withdrawer,
106                inflation_rewards_collector: *vote_pubkey,
107                block_revenue_collector: state.node_pubkey,
108                inflation_rewards_commission_bps: u16::from(state.commission).saturating_mul(100),
109                block_revenue_commission_bps: 10_000u16,
110                pending_delegator_rewards: 0,
111                bls_pubkey_compressed: None,
112                votes: state.votes,
113                root_slot: state.root_slot,
114                authorized_voters: state.authorized_voters,
115                epoch_credits: state.epoch_credits,
116                last_timestamp: state.last_timestamp,
117            }),
118
119            VoteStateVersions::V4(state) => Ok(*state),
120        }
121    }
122
123    #[cfg(test)]
124    fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
125        lockouts.into_iter().map(|lockout| lockout.into()).collect()
126    }
127
128    pub fn is_uninitialized(&self) -> bool {
129        match self {
130            VoteStateVersions::Uninitialized => true,
131
132            VoteStateVersions::V1_14_11(vote_state) => vote_state.is_uninitialized(),
133
134            VoteStateVersions::V3(vote_state) => vote_state.is_uninitialized(),
135
136            // As per SIMD-0185, v4 is always initialized.
137            VoteStateVersions::V4(_) => false,
138        }
139    }
140
141    pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
142        VoteStateV4::is_correct_size_and_initialized(data)
143            || VoteStateV3::is_correct_size_and_initialized(data)
144            || VoteState1_14_11::is_correct_size_and_initialized(data)
145    }
146
147    /// Deserializes the input buffer directly into the appropriate `VoteStateVersions` variant.
148    ///
149    /// V0_23_5 is not supported. All other versions (V1_14_11, V3, V4) are deserialized as-is
150    /// without any version coercion.
151    #[cfg(any(target_os = "solana", feature = "bincode"))]
152    pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
153        use {
154            crate::state::vote_state_deserialize::{
155                deserialize_vote_state_into_v1_14_11, deserialize_vote_state_into_v3,
156                deserialize_vote_state_into_v4, SourceVersion,
157            },
158            std::mem::MaybeUninit,
159        };
160
161        let mut cursor = std::io::Cursor::new(input);
162
163        let variant = solana_serialize_utils::cursor::read_u32(&mut cursor)?;
164        match variant {
165            // V0_23_5 not supported.
166            0 => Err(InstructionError::InvalidAccountData),
167            // V1_14_11
168            1 => {
169                let mut vote_state = Box::new(MaybeUninit::uninit());
170                deserialize_vote_state_into_v1_14_11(&mut cursor, vote_state.as_mut_ptr())?;
171                let vote_state =
172                    unsafe { Box::from_raw(Box::into_raw(vote_state) as *mut VoteState1_14_11) };
173                Ok(VoteStateVersions::V1_14_11(vote_state))
174            }
175            // V3
176            2 => {
177                let mut vote_state = Box::new(MaybeUninit::uninit());
178                deserialize_vote_state_into_v3(&mut cursor, vote_state.as_mut_ptr(), true)?;
179                let vote_state =
180                    unsafe { Box::from_raw(Box::into_raw(vote_state) as *mut VoteStateV3) };
181                Ok(VoteStateVersions::V3(vote_state))
182            }
183            // V4
184            3 => {
185                let mut vote_state = Box::new(MaybeUninit::uninit());
186                deserialize_vote_state_into_v4(
187                    &mut cursor,
188                    vote_state.as_mut_ptr(),
189                    SourceVersion::V4,
190                )?;
191                let vote_state =
192                    unsafe { Box::from_raw(Box::into_raw(vote_state) as *mut VoteStateV4) };
193                Ok(VoteStateVersions::V4(vote_state))
194            }
195            _ => Err(InstructionError::InvalidAccountData),
196        }
197    }
198}
199
200#[cfg(test)]
201impl Arbitrary<'_> for VoteStateVersions {
202    fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
203        let variant = u.choose_index(3)?;
204        match variant {
205            0 => Ok(Self::V4(Box::new(VoteStateV4::arbitrary(u)?))),
206            1 => Ok(Self::V3(Box::new(VoteStateV3::arbitrary(u)?))),
207            2 => Ok(Self::V1_14_11(Box::new(VoteState1_14_11::arbitrary(u)?))),
208            _ => unreachable!(),
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use {
216        super::*,
217        crate::state::{VoteInit, BLS_PUBLIC_KEY_COMPRESSED_SIZE, DEFAULT_PRIOR_VOTERS_OFFSET},
218        rand::Rng,
219        solana_clock::Clock,
220        solana_instruction::error::InstructionError,
221    };
222
223    #[test]
224    fn test_v3_default_vote_state_is_uninitialized() {
225        // The default `VoteStateV3` is stored to de-initialize a zero-balance vote account,
226        // so must remain such that `VoteStateVersions::is_uninitialized()` returns true
227        // when called on a `VoteStateVersions` that stores it
228        assert!(VoteStateVersions::new_v3(VoteStateV3::default()).is_uninitialized());
229    }
230
231    #[test]
232    fn test_v4_default_vote_state_is_always_initialized() {
233        // Per SIMD-0185, V4 is always initialized.
234        assert!(!VoteStateVersions::new_v4(VoteStateV4::default()).is_uninitialized());
235    }
236
237    #[test]
238    fn test_is_correct_size_and_initialized_v3() {
239        // Check all zeroes
240        let mut vote_account_data = vec![0; VoteStateV3::size_of()];
241        assert!(!VoteStateVersions::is_correct_size_and_initialized(
242            &vote_account_data
243        ));
244
245        // Check default VoteStateV3
246        let default_account_state = VoteStateVersions::new_v3(VoteStateV3::default());
247        VoteStateV3::serialize(&default_account_state, &mut vote_account_data).unwrap();
248        assert!(!VoteStateVersions::is_correct_size_and_initialized(
249            &vote_account_data
250        ));
251
252        // Check non-zero data shorter than offset index used
253        let short_data = vec![1; DEFAULT_PRIOR_VOTERS_OFFSET];
254        assert!(!VoteStateVersions::is_correct_size_and_initialized(
255            &short_data
256        ));
257
258        // Check non-zero large account
259        let mut large_vote_data = vec![1; 2 * VoteStateV3::size_of()];
260        let default_account_state = VoteStateVersions::new_v3(VoteStateV3::default());
261        VoteStateV3::serialize(&default_account_state, &mut large_vote_data).unwrap();
262        assert!(!VoteStateVersions::is_correct_size_and_initialized(
263            &vote_account_data
264        ));
265
266        // Check populated VoteStateV3
267        let vote_state = VoteStateV3::new(
268            &VoteInit {
269                node_pubkey: Pubkey::new_unique(),
270                authorized_voter: Pubkey::new_unique(),
271                authorized_withdrawer: Pubkey::new_unique(),
272                commission: 0,
273            },
274            &Clock::default(),
275        );
276        let account_state = VoteStateVersions::new_v3(vote_state.clone());
277        VoteStateV3::serialize(&account_state, &mut vote_account_data).unwrap();
278        assert!(VoteStateVersions::is_correct_size_and_initialized(
279            &vote_account_data
280        ));
281
282        // Check old VoteStateV3 that hasn't been upgraded to newest version yet
283        let old_vote_state = VoteState1_14_11::from(vote_state);
284        let account_state = VoteStateVersions::V1_14_11(Box::new(old_vote_state));
285        let mut vote_account_data = vec![0; VoteState1_14_11::size_of()];
286        VoteStateV3::serialize(&account_state, &mut vote_account_data).unwrap();
287        assert!(VoteStateVersions::is_correct_size_and_initialized(
288            &vote_account_data
289        ));
290    }
291
292    #[test]
293    fn test_is_correct_size_and_initialized_v4() {
294        // All zeros at V4 size — false (discriminant is 0, not 3).
295        let zeros = vec![0u8; VoteStateV4::size_of()];
296        assert!(!VoteStateVersions::is_correct_size_and_initialized(&zeros));
297
298        // Valid serialized V4 — true.
299        let versioned = VoteStateVersions::new_v4(VoteStateV4::default());
300        let mut buf = vec![0u8; VoteStateV4::size_of()];
301        VoteStateV4::serialize(&versioned, &mut buf).unwrap();
302        assert!(VoteStateVersions::is_correct_size_and_initialized(&buf));
303
304        // Populated V4 — true.
305        let vote_state = VoteStateV4::new_with_defaults(
306            &Pubkey::new_unique(),
307            &VoteInit {
308                node_pubkey: Pubkey::new_unique(),
309                authorized_voter: Pubkey::new_unique(),
310                authorized_withdrawer: Pubkey::new_unique(),
311                commission: 50,
312            },
313            &Clock::default(),
314        );
315        let versioned = VoteStateVersions::new_v4(vote_state);
316        let mut buf = vec![0u8; VoteStateV4::size_of()];
317        VoteStateV4::serialize(&versioned, &mut buf).unwrap();
318        assert!(VoteStateVersions::is_correct_size_and_initialized(&buf));
319
320        // Wrong length — false.
321        assert!(!VoteStateVersions::is_correct_size_and_initialized(
322            &buf[..buf.len() - 1]
323        ));
324        let mut extended = buf.clone();
325        extended.push(0);
326        assert!(!VoteStateVersions::is_correct_size_and_initialized(
327            &extended
328        ));
329    }
330
331    #[test]
332    fn test_vote_state_version_conversion_bls_pubkey() {
333        let vote_pubkey = Pubkey::new_unique();
334
335        // All versions before v4 should result in `None` for BLS pubkey.
336        let v1_14_11_state = VoteState1_14_11::default();
337        let v1_14_11_versioned = VoteStateVersions::V1_14_11(Box::new(v1_14_11_state));
338
339        let v3_state = VoteStateV3::default();
340        let v3_versioned = VoteStateVersions::V3(Box::new(v3_state));
341
342        for versioned in [v1_14_11_versioned, v3_versioned] {
343            let converted = versioned.try_convert_to_v4(&vote_pubkey).unwrap();
344            assert_eq!(converted.bls_pubkey_compressed, None);
345        }
346
347        // v4 to v4 conversion should preserve the BLS pubkey.
348        let test_bls_key = [128u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
349        let v4_state = VoteStateV4 {
350            bls_pubkey_compressed: Some(test_bls_key),
351            ..VoteStateV4::default()
352        };
353        let v4_versioned = VoteStateVersions::V4(Box::new(v4_state));
354        let converted = v4_versioned.try_convert_to_v4(&vote_pubkey).unwrap();
355        assert_eq!(converted.bls_pubkey_compressed, Some(test_bls_key));
356    }
357
358    #[test]
359    fn test_versions_deserialize_invalid_variant_tags() {
360        let mut buf = vec![0u8; VoteStateV3::size_of()];
361
362        // Tag 0 (V0_23_5 — explicitly rejected).
363        assert_eq!(
364            VoteStateVersions::deserialize(&buf),
365            Err(InstructionError::InvalidAccountData)
366        );
367
368        // Tag 4 (unknown).
369        buf[..4].copy_from_slice(&4u32.to_le_bytes());
370        assert_eq!(
371            VoteStateVersions::deserialize(&buf),
372            Err(InstructionError::InvalidAccountData)
373        );
374
375        // Tag u32::MAX.
376        buf[..4].copy_from_slice(&u32::MAX.to_le_bytes());
377        assert_eq!(
378            VoteStateVersions::deserialize(&buf),
379            Err(InstructionError::InvalidAccountData)
380        );
381    }
382
383    #[test]
384    fn test_versions_deserialize_error_paths() {
385        // Empty input.
386        assert_eq!(
387            VoteStateVersions::deserialize(&[]),
388            Err(InstructionError::InvalidAccountData)
389        );
390
391        // Too short for variant tag.
392        assert_eq!(
393            VoteStateVersions::deserialize(&[1, 0, 0]),
394            Err(InstructionError::InvalidAccountData)
395        );
396
397        // Valid tag, truncated body.
398        let vote_state = VoteStateV3::new(
399            &VoteInit {
400                node_pubkey: Pubkey::new_unique(),
401                authorized_voter: Pubkey::new_unique(),
402                authorized_withdrawer: Pubkey::new_unique(),
403                commission: 50,
404            },
405            &Clock::default(),
406        );
407        let mut buf = bincode::serialize(&VoteStateVersions::new_v3(vote_state)).unwrap();
408        buf.truncate(buf.len() / 2);
409        assert_eq!(
410            VoteStateVersions::deserialize(&buf),
411            Err(InstructionError::InvalidAccountData)
412        );
413
414        // Random bytes — must not panic.
415        let mut rng = rand::rng();
416        for _ in 0..100 {
417            let len = rng.random_range(0u64..512);
418            let random_data: Vec<u8> = (0..len).map(|_| rng.random::<u8>()).collect();
419            let _ = VoteStateVersions::deserialize(&random_data);
420        }
421    }
422
423    #[test]
424    fn test_versions_deserialize_default() {
425        let ser_deser = |original: VoteStateVersions| {
426            let serialized = bincode::serialize(&original).unwrap();
427            VoteStateVersions::deserialize(&serialized)
428        };
429
430        let v1_14_11 = VoteStateVersions::V1_14_11(Box::default());
431        assert_eq!(
432            ser_deser(v1_14_11.clone()),
433            Ok(v1_14_11), // <-- Matches original
434        );
435
436        let v3 = VoteStateVersions::V3(Box::default());
437        assert_eq!(
438            ser_deser(v3.clone()),
439            Ok(v3), // <-- Matches original
440        );
441
442        let v4 = VoteStateVersions::V4(Box::default());
443        assert_eq!(
444            ser_deser(v4.clone()),
445            Ok(v4), // <-- Matches original
446        );
447    }
448
449    #[test]
450    fn test_versions_deserialize_non_default() {
451        let vote_init = VoteInit {
452            node_pubkey: Pubkey::new_unique(),
453            authorized_voter: Pubkey::new_unique(),
454            authorized_withdrawer: Pubkey::new_unique(),
455            commission: 50,
456        };
457
458        let ser_deser = |original: VoteStateVersions| {
459            let serialized = bincode::serialize(&original).unwrap();
460            let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
461            assert_eq!(original, deserialized);
462        };
463
464        // Populated V1_14_11 round-trip.
465        let vote_state = VoteState1_14_11::from(VoteStateV3::new(&vote_init, &Clock::default()));
466        ser_deser(VoteStateVersions::V1_14_11(Box::new(vote_state)));
467
468        // Populated V3 round-trip.
469        let vote_state = VoteStateV3::new(&vote_init, &Clock::default());
470        ser_deser(VoteStateVersions::new_v3(vote_state));
471
472        // Populated V4 round-trip.
473        let vote_state =
474            VoteStateV4::new_with_defaults(&Pubkey::new_unique(), &vote_init, &Clock::default());
475        ser_deser(VoteStateVersions::new_v4(vote_state));
476    }
477
478    #[test]
479    fn test_versions_deserialize_arbitrary() {
480        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
481        for _ in 0..1000 {
482            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
483            let mut unstructured = Unstructured::new(&raw_data);
484            let original = VoteStateVersions::arbitrary(&mut unstructured).unwrap();
485            let serialized = bincode::serialize(&original).unwrap();
486            let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
487            assert_eq!(original, deserialized);
488        }
489    }
490
491    #[test]
492    fn test_collection_count_exceeding_max() {
493        // Deserialization reads unbounded Vec/VecDeque lengths from the input,
494        // so it must handle counts that exceed the compile-time capacity hints
495        // (MAX_LOCKOUT_HISTORY, MAX_EPOCH_CREDITS_HISTORY) without panicking.
496
497        let mut vote_state = VoteStateV3::default();
498        // 100 votes > MAX_LOCKOUT_HISTORY (31).
499        for i in 0..100u64 {
500            vote_state.votes.push_back(LandedVote {
501                latency: 0,
502                lockout: Lockout::new(i),
503            });
504        }
505        // 100 epoch credits > MAX_EPOCH_CREDITS_HISTORY (64).
506        for i in 0..100u64 {
507            vote_state.epoch_credits.push((i, i * 10, i * 5));
508        }
509        let versioned = VoteStateVersions::new_v3(vote_state);
510        let serialized = bincode::serialize(&versioned).unwrap();
511        let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
512        assert_eq!(versioned, deserialized);
513    }
514}