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