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    };
221
222    #[test]
223    fn test_v3_default_vote_state_is_uninitialized() {
224        // The default `VoteStateV3` is stored to de-initialize a zero-balance vote account,
225        // so must remain such that `VoteStateVersions::is_uninitialized()` returns true
226        // when called on a `VoteStateVersions` that stores it
227        assert!(VoteStateVersions::new_v3(VoteStateV3::default()).is_uninitialized());
228    }
229
230    #[test]
231    fn test_v4_default_vote_state_is_always_initialized() {
232        // Per SIMD-0185, V4 is always initialized.
233        assert!(!VoteStateVersions::new_v4(VoteStateV4::default()).is_uninitialized());
234    }
235
236    #[test]
237    fn test_is_correct_size_and_initialized_v3() {
238        // Check all zeroes
239        let mut vote_account_data = vec![0; VoteStateV3::size_of()];
240        assert!(!VoteStateVersions::is_correct_size_and_initialized(
241            &vote_account_data
242        ));
243
244        // Check default VoteStateV3
245        let default_account_state = VoteStateVersions::new_v3(VoteStateV3::default());
246        VoteStateV3::serialize(&default_account_state, &mut vote_account_data).unwrap();
247        assert!(!VoteStateVersions::is_correct_size_and_initialized(
248            &vote_account_data
249        ));
250
251        // Check non-zero data shorter than offset index used
252        let short_data = vec![1; DEFAULT_PRIOR_VOTERS_OFFSET];
253        assert!(!VoteStateVersions::is_correct_size_and_initialized(
254            &short_data
255        ));
256
257        // Check non-zero large account
258        let mut large_vote_data = vec![1; 2 * VoteStateV3::size_of()];
259        let default_account_state = VoteStateVersions::new_v3(VoteStateV3::default());
260        VoteStateV3::serialize(&default_account_state, &mut large_vote_data).unwrap();
261        assert!(!VoteStateVersions::is_correct_size_and_initialized(
262            &vote_account_data
263        ));
264
265        // Check populated VoteStateV3
266        let vote_state = VoteStateV3::new(
267            &VoteInit {
268                node_pubkey: Pubkey::new_unique(),
269                authorized_voter: Pubkey::new_unique(),
270                authorized_withdrawer: Pubkey::new_unique(),
271                commission: 0,
272            },
273            &Clock::default(),
274        );
275        let account_state = VoteStateVersions::new_v3(vote_state.clone());
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        // Check old VoteStateV3 that hasn't been upgraded to newest version yet
282        let old_vote_state = VoteState1_14_11::from(vote_state);
283        let account_state = VoteStateVersions::V1_14_11(Box::new(old_vote_state));
284        let mut vote_account_data = vec![0; VoteState1_14_11::size_of()];
285        VoteStateV3::serialize(&account_state, &mut vote_account_data).unwrap();
286        assert!(VoteStateVersions::is_correct_size_and_initialized(
287            &vote_account_data
288        ));
289    }
290
291    #[test]
292    fn test_is_correct_size_and_initialized_v4() {
293        // All zeros at V4 size — false (discriminant is 0, not 3).
294        let zeros = vec![0u8; VoteStateV4::size_of()];
295        assert!(!VoteStateVersions::is_correct_size_and_initialized(&zeros));
296
297        // Valid serialized V4 — true.
298        let versioned = VoteStateVersions::new_v4(VoteStateV4::default());
299        let mut buf = vec![0u8; VoteStateV4::size_of()];
300        VoteStateV4::serialize(&versioned, &mut buf).unwrap();
301        assert!(VoteStateVersions::is_correct_size_and_initialized(&buf));
302
303        // Populated V4 — true.
304        let vote_state = VoteStateV4::new_with_defaults(
305            &Pubkey::new_unique(),
306            &VoteInit {
307                node_pubkey: Pubkey::new_unique(),
308                authorized_voter: Pubkey::new_unique(),
309                authorized_withdrawer: Pubkey::new_unique(),
310                commission: 50,
311            },
312            &Clock::default(),
313        );
314        let versioned = VoteStateVersions::new_v4(vote_state);
315        let mut buf = vec![0u8; VoteStateV4::size_of()];
316        VoteStateV4::serialize(&versioned, &mut buf).unwrap();
317        assert!(VoteStateVersions::is_correct_size_and_initialized(&buf));
318
319        // Wrong length — false.
320        assert!(!VoteStateVersions::is_correct_size_and_initialized(
321            &buf[..buf.len() - 1]
322        ));
323        let mut extended = buf.clone();
324        extended.push(0);
325        assert!(!VoteStateVersions::is_correct_size_and_initialized(
326            &extended
327        ));
328    }
329
330    #[test]
331    fn test_vote_state_version_conversion_bls_pubkey() {
332        let vote_pubkey = Pubkey::new_unique();
333
334        // All versions before v4 should result in `None` for BLS pubkey.
335        let v1_14_11_state = VoteState1_14_11::default();
336        let v1_14_11_versioned = VoteStateVersions::V1_14_11(Box::new(v1_14_11_state));
337
338        let v3_state = VoteStateV3::default();
339        let v3_versioned = VoteStateVersions::V3(Box::new(v3_state));
340
341        for versioned in [v1_14_11_versioned, v3_versioned] {
342            let converted = versioned.try_convert_to_v4(&vote_pubkey).unwrap();
343            assert_eq!(converted.bls_pubkey_compressed, None);
344        }
345
346        // v4 to v4 conversion should preserve the BLS pubkey.
347        let test_bls_key = [128u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
348        let v4_state = VoteStateV4 {
349            bls_pubkey_compressed: Some(test_bls_key),
350            ..VoteStateV4::default()
351        };
352        let v4_versioned = VoteStateVersions::V4(Box::new(v4_state));
353        let converted = v4_versioned.try_convert_to_v4(&vote_pubkey).unwrap();
354        assert_eq!(converted.bls_pubkey_compressed, Some(test_bls_key));
355    }
356
357    #[test]
358    fn test_versions_deserialize_invalid_variant_tags() {
359        let mut buf = vec![0u8; VoteStateV3::size_of()];
360
361        // Tag 0 (V0_23_5 — explicitly rejected).
362        assert_eq!(
363            VoteStateVersions::deserialize(&buf),
364            Err(InstructionError::InvalidAccountData)
365        );
366
367        // Tag 4 (unknown).
368        buf[..4].copy_from_slice(&4u32.to_le_bytes());
369        assert_eq!(
370            VoteStateVersions::deserialize(&buf),
371            Err(InstructionError::InvalidAccountData)
372        );
373
374        // Tag u32::MAX.
375        buf[..4].copy_from_slice(&u32::MAX.to_le_bytes());
376        assert_eq!(
377            VoteStateVersions::deserialize(&buf),
378            Err(InstructionError::InvalidAccountData)
379        );
380    }
381
382    #[test]
383    fn test_versions_deserialize_error_paths() {
384        // Empty input.
385        assert_eq!(
386            VoteStateVersions::deserialize(&[]),
387            Err(InstructionError::InvalidAccountData)
388        );
389
390        // Too short for variant tag.
391        assert_eq!(
392            VoteStateVersions::deserialize(&[1, 0, 0]),
393            Err(InstructionError::InvalidAccountData)
394        );
395
396        // Valid tag, truncated body.
397        let vote_state = VoteStateV3::new(
398            &VoteInit {
399                node_pubkey: Pubkey::new_unique(),
400                authorized_voter: Pubkey::new_unique(),
401                authorized_withdrawer: Pubkey::new_unique(),
402                commission: 50,
403            },
404            &Clock::default(),
405        );
406        let mut buf = bincode::serialize(&VoteStateVersions::new_v3(vote_state)).unwrap();
407        buf.truncate(buf.len() / 2);
408        assert_eq!(
409            VoteStateVersions::deserialize(&buf),
410            Err(InstructionError::InvalidAccountData)
411        );
412
413        // Random bytes — must not panic.
414        let mut rng = rand::rng();
415        for _ in 0..100 {
416            let len = rng.random_range(0u64..512);
417            let random_data: Vec<u8> = (0..len).map(|_| rng.random::<u8>()).collect();
418            let _ = VoteStateVersions::deserialize(&random_data);
419        }
420    }
421
422    #[test]
423    fn test_versions_deserialize_default() {
424        let ser_deser = |original: VoteStateVersions| {
425            let serialized = bincode::serialize(&original).unwrap();
426            VoteStateVersions::deserialize(&serialized)
427        };
428
429        let v1_14_11 = VoteStateVersions::V1_14_11(Box::default());
430        assert_eq!(
431            ser_deser(v1_14_11.clone()),
432            Ok(v1_14_11), // <-- Matches original
433        );
434
435        let v3 = VoteStateVersions::V3(Box::default());
436        assert_eq!(
437            ser_deser(v3.clone()),
438            Ok(v3), // <-- Matches original
439        );
440
441        let v4 = VoteStateVersions::V4(Box::default());
442        assert_eq!(
443            ser_deser(v4.clone()),
444            Ok(v4), // <-- Matches original
445        );
446    }
447
448    #[test]
449    fn test_versions_deserialize_non_default() {
450        let vote_init = VoteInit {
451            node_pubkey: Pubkey::new_unique(),
452            authorized_voter: Pubkey::new_unique(),
453            authorized_withdrawer: Pubkey::new_unique(),
454            commission: 50,
455        };
456
457        let ser_deser = |original: VoteStateVersions| {
458            let serialized = bincode::serialize(&original).unwrap();
459            let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
460            assert_eq!(original, deserialized);
461        };
462
463        // Populated V1_14_11 round-trip.
464        let vote_state = VoteState1_14_11::from(VoteStateV3::new(&vote_init, &Clock::default()));
465        ser_deser(VoteStateVersions::V1_14_11(Box::new(vote_state)));
466
467        // Populated V3 round-trip.
468        let vote_state = VoteStateV3::new(&vote_init, &Clock::default());
469        ser_deser(VoteStateVersions::new_v3(vote_state));
470
471        // Populated V4 round-trip.
472        let vote_state =
473            VoteStateV4::new_with_defaults(&Pubkey::new_unique(), &vote_init, &Clock::default());
474        ser_deser(VoteStateVersions::new_v4(vote_state));
475    }
476
477    #[test]
478    fn test_versions_deserialize_arbitrary() {
479        let struct_bytes_x4 = std::mem::size_of::<VoteStateV4>() * 4;
480        for _ in 0..1000 {
481            let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
482            let mut unstructured = Unstructured::new(&raw_data);
483            let original = VoteStateVersions::arbitrary(&mut unstructured).unwrap();
484            let serialized = bincode::serialize(&original).unwrap();
485            let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
486            assert_eq!(original, deserialized);
487        }
488    }
489
490    #[test]
491    fn test_collection_count_exceeding_max() {
492        // Deserialization reads unbounded Vec/VecDeque lengths from the input,
493        // so it must handle counts that exceed the compile-time capacity hints
494        // (MAX_LOCKOUT_HISTORY, MAX_EPOCH_CREDITS_HISTORY) without panicking.
495
496        let mut vote_state = VoteStateV3::default();
497        // 100 votes > MAX_LOCKOUT_HISTORY (31).
498        for i in 0..100u64 {
499            vote_state.votes.push_back(LandedVote {
500                latency: 0,
501                lockout: Lockout::new(i),
502            });
503        }
504        // 100 epoch credits > MAX_EPOCH_CREDITS_HISTORY (64).
505        for i in 0..100u64 {
506            vote_state.epoch_credits.push((i, i * 10, i * 5));
507        }
508        let versioned = VoteStateVersions::new_v3(vote_state);
509        let serialized = bincode::serialize(&versioned).unwrap();
510        let deserialized = VoteStateVersions::deserialize(&serialized).unwrap();
511        assert_eq!(versioned, deserialized);
512    }
513}