atlas_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 atlas_frozen_abi_macro::{frozen_abi, AbiExample};
11#[cfg(any(target_os = "atlas", feature = "bincode"))]
12use atlas_instruction::error::InstructionError;
13use {
14    super::{BlockTimestamp, LandedVote, BLS_PUBLIC_KEY_COMPRESSED_SIZE},
15    crate::authorized_voters::AuthorizedVoters,
16    atlas_clock::{Epoch, Slot},
17    atlas_pubkey::Pubkey,
18    std::{collections::VecDeque, fmt::Debug},
19};
20
21#[cfg_attr(
22    feature = "frozen-abi",
23    frozen_abi(digest = "2H9WgTh7LgdnpinvEwxzP3HF6SDuKp6qdwFmJk9jHDRP"),
24    derive(AbiExample)
25)]
26#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)]
27#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28#[derive(Debug, Default, PartialEq, Eq, Clone)]
29#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
30pub struct VoteStateV4 {
31    /// The node that votes in this account.
32    pub node_pubkey: Pubkey,
33    /// The signer for withdrawals.
34    pub authorized_withdrawer: Pubkey,
35
36    /// The collector account for inflation rewards.
37    pub inflation_rewards_collector: Pubkey,
38    /// The collector account for block revenue.
39    pub block_revenue_collector: Pubkey,
40
41    /// Basis points (0-10,000) that represent how much of the inflation
42    /// rewards should be given to this vote account.
43    pub inflation_rewards_commission_bps: u16,
44    /// Basis points (0-10,000) that represent how much of the block revenue
45    /// should be given to this vote account.
46    pub block_revenue_commission_bps: u16,
47
48    /// Reward amount pending distribution to stake delegators.
49    pub pending_delegator_rewards: u64,
50
51    /// Compressed BLS pubkey for Alpenglow.
52    #[cfg_attr(
53        feature = "serde",
54        serde_as(as = "Option<[_; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>")
55    )]
56    pub bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
57
58    pub votes: VecDeque<LandedVote>,
59    pub root_slot: Option<Slot>,
60
61    /// The signer for vote transactions.
62    /// Contains entries for the current epoch and the previous epoch.
63    pub authorized_voters: AuthorizedVoters,
64
65    /// History of credits earned by the end of each epoch.
66    /// Each tuple is (Epoch, credits, prev_credits).
67    pub epoch_credits: Vec<(Epoch, u64, u64)>,
68
69    /// Most recent timestamp submitted with a vote.
70    pub last_timestamp: BlockTimestamp,
71}
72
73impl VoteStateV4 {
74    /// Upper limit on the size of the Vote State
75    /// when votes.len() is MAX_LOCKOUT_HISTORY.
76    pub const fn size_of() -> usize {
77        3762 // Same size as V3 to avoid account resizing
78    }
79
80    #[cfg(any(target_os = "atlas", feature = "bincode"))]
81    pub fn deserialize(input: &[u8], vote_pubkey: &Pubkey) -> Result<Self, InstructionError> {
82        let mut vote_state = Self::default();
83        Self::deserialize_into(input, &mut vote_state, vote_pubkey)?;
84        Ok(vote_state)
85    }
86
87    /// Deserializes the input `VoteStateVersions` buffer directly into the provided `VoteStateV4`.
88    ///
89    /// In a SBPF context, V0_23_5 is not supported, but in non-SBPF, all versions are supported for
90    /// compatibility with `bincode::deserialize`.
91    ///
92    /// On success, `vote_state` reflects the state of the input data. On failure, `vote_state` is
93    /// reset to `VoteStateV4::default()`.
94    #[cfg(any(target_os = "atlas", feature = "bincode"))]
95    pub fn deserialize_into(
96        input: &[u8],
97        vote_state: &mut VoteStateV4,
98        vote_pubkey: &Pubkey,
99    ) -> Result<(), InstructionError> {
100        use super::vote_state_deserialize;
101        vote_state_deserialize::deserialize_into(input, vote_state, |input, vote_state| {
102            Self::deserialize_into_ptr(input, vote_state, vote_pubkey)
103        })
104    }
105
106    /// Deserializes the input `VoteStateVersions` buffer directly into the provided
107    /// `MaybeUninit<VoteStateV4>`.
108    ///
109    /// In a SBPF context, V0_23_5 is not supported, but in non-SBPF, all versions are supported for
110    /// compatibility with `bincode::deserialize`.
111    ///
112    /// On success, `vote_state` is fully initialized and can be converted to
113    /// `VoteStateV4` using
114    /// [`MaybeUninit::assume_init`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.assume_init).
115    /// On failure, `vote_state` may still be uninitialized and must not be
116    /// converted to `VoteStateV4`.
117    #[cfg(any(target_os = "atlas", feature = "bincode"))]
118    pub fn deserialize_into_uninit(
119        input: &[u8],
120        vote_state: &mut std::mem::MaybeUninit<VoteStateV4>,
121        vote_pubkey: &Pubkey,
122    ) -> Result<(), InstructionError> {
123        Self::deserialize_into_ptr(input, vote_state.as_mut_ptr(), vote_pubkey)
124    }
125
126    #[cfg(any(target_os = "atlas", feature = "bincode"))]
127    fn deserialize_into_ptr(
128        input: &[u8],
129        vote_state: *mut VoteStateV4,
130        vote_pubkey: &Pubkey,
131    ) -> Result<(), InstructionError> {
132        use super::vote_state_deserialize::{deserialize_vote_state_into_v4, SourceVersion};
133
134        let mut cursor = std::io::Cursor::new(input);
135
136        let variant = atlas_serialize_utils::cursor::read_u32(&mut cursor)?;
137        match variant {
138            // V0_23_5. not supported for bpf targets; these should not exist on mainnet
139            // supported for non-bpf targets for backwards compatibility.
140            // **Same pattern as v3 for this variant**.
141            0 => {
142                #[cfg(not(target_os = "atlas"))]
143                {
144                    // Safety: vote_state is valid as it comes from `&mut MaybeUninit<VoteStateV4>` or
145                    // `&mut VoteStateV4`. In the first case, the value is uninitialized so we write()
146                    // to avoid dropping invalid data; in the latter case, we `drop_in_place()`
147                    // before writing so the value has already been dropped and we just write a new
148                    // one in place.
149                    unsafe {
150                        vote_state.write(
151                            bincode::deserialize::<VoteStateVersions>(input)
152                                .map_err(|_| InstructionError::InvalidAccountData)
153                                .and_then(|versioned| versioned.try_convert_to_v4(vote_pubkey))?,
154                        );
155                    }
156                    Ok(())
157                }
158                #[cfg(target_os = "atlas")]
159                Err(InstructionError::InvalidAccountData)
160            }
161            // V1_14_11
162            1 => deserialize_vote_state_into_v4(
163                &mut cursor,
164                vote_state,
165                SourceVersion::V1_14_11 { vote_pubkey },
166            ),
167            // V3
168            2 => deserialize_vote_state_into_v4(
169                &mut cursor,
170                vote_state,
171                SourceVersion::V3 { vote_pubkey },
172            ),
173            // V4
174            3 => deserialize_vote_state_into_v4(&mut cursor, vote_state, SourceVersion::V4),
175            _ => Err(InstructionError::InvalidAccountData),
176        }?;
177
178        Ok(())
179    }
180
181    #[cfg(feature = "bincode")]
182    pub fn serialize(
183        versioned: &VoteStateVersions,
184        output: &mut [u8],
185    ) -> Result<(), InstructionError> {
186        bincode::serialize_into(output, versioned).map_err(|err| match *err {
187            bincode::ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
188            _ => InstructionError::GenericError,
189        })
190    }
191
192    pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
193        data.len() == VoteStateV4::size_of() && data[..4] == [3, 0, 0, 0] // little-endian 3u32
194                                                                          // Always initialized
195    }
196
197    #[cfg(test)]
198    pub(crate) fn get_max_sized_vote_state() -> Self {
199        use super::{MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY};
200
201        // V4 stores a maximum of 4 authorized voter entries.
202        const MAX_AUTHORIZED_VOTERS: usize = 4;
203
204        let mut authorized_voters = AuthorizedVoters::default();
205        for i in 0..MAX_AUTHORIZED_VOTERS as u64 {
206            authorized_voters.insert(i, Pubkey::new_unique());
207        }
208
209        Self {
210            votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
211            root_slot: Some(u64::MAX),
212            epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
213            authorized_voters,
214            ..Self::default()
215        }
216    }
217}