1#[cfg(feature = "dev-context-only-utils")]
4use arbitrary::Arbitrary;
5#[cfg(feature = "serde")]
6use serde_derive::{Deserialize, Serialize};
7#[cfg(feature = "frozen-abi")]
8use solana_frozen_abi_macro::AbiExample;
9use {
10 crate::authorized_voters::AuthorizedVoters,
11 solana_clock::{Epoch, Slot, UnixTimestamp},
12 solana_pubkey::Pubkey,
13 solana_rent::Rent,
14 std::{collections::VecDeque, fmt::Debug},
15};
16
17pub mod vote_state_1_14_11;
18pub use vote_state_1_14_11::*;
19pub mod vote_state_versions;
20pub use vote_state_versions::*;
21pub mod vote_state_v3;
22pub use vote_state_v3::VoteStateV3;
23pub mod vote_state_v4;
24pub use vote_state_v4::VoteStateV4;
25mod vote_instruction_data;
26pub use vote_instruction_data::*;
27#[cfg(any(target_os = "solana", feature = "bincode"))]
28pub(crate) mod vote_state_deserialize;
29
30pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48;
32
33pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96;
35
36pub const MAX_LOCKOUT_HISTORY: usize = 31;
38pub const INITIAL_LOCKOUT: usize = 2;
39
40pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
42
43const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 114;
45
46pub const VOTE_CREDITS_GRACE_SLOTS: u8 = 2;
48
49pub const VOTE_CREDITS_MAXIMUM_PER_SLOT: u8 = 16;
51
52#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
53#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
54#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
55#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
56pub struct Lockout {
57 slot: Slot,
58 confirmation_count: u32,
59}
60
61impl Lockout {
62 pub fn new(slot: Slot) -> Self {
63 Self::new_with_confirmation_count(slot, 1)
64 }
65
66 pub fn new_with_confirmation_count(slot: Slot, confirmation_count: u32) -> Self {
67 Self {
68 slot,
69 confirmation_count,
70 }
71 }
72
73 pub fn lockout(&self) -> u64 {
75 (INITIAL_LOCKOUT as u64).wrapping_pow(std::cmp::min(
76 self.confirmation_count(),
77 MAX_LOCKOUT_HISTORY as u32,
78 ))
79 }
80
81 pub fn last_locked_out_slot(&self) -> Slot {
85 self.slot.saturating_add(self.lockout())
86 }
87
88 pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
89 self.last_locked_out_slot() >= slot
90 }
91
92 pub fn slot(&self) -> Slot {
93 self.slot
94 }
95
96 pub fn confirmation_count(&self) -> u32 {
97 self.confirmation_count
98 }
99
100 pub fn increase_confirmation_count(&mut self, by: u32) {
101 self.confirmation_count = self.confirmation_count.saturating_add(by)
102 }
103}
104
105#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
106#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
107#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
108#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
109pub struct LandedVote {
110 pub latency: u8,
114 pub lockout: Lockout,
115}
116
117impl LandedVote {
118 pub fn slot(&self) -> Slot {
119 self.lockout.slot
120 }
121
122 pub fn confirmation_count(&self) -> u32 {
123 self.lockout.confirmation_count
124 }
125}
126
127impl From<LandedVote> for Lockout {
128 fn from(landed_vote: LandedVote) -> Self {
129 landed_vote.lockout
130 }
131}
132
133impl From<Lockout> for LandedVote {
134 fn from(lockout: Lockout) -> Self {
135 Self {
136 latency: 0,
137 lockout,
138 }
139 }
140}
141
142#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
143#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
144#[derive(Debug, Default, PartialEq, Eq, Clone)]
145#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
146pub struct BlockTimestamp {
147 pub slot: Slot,
148 pub timestamp: UnixTimestamp,
149}
150
151const MAX_ITEMS: usize = 32;
153
154#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
155#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
156#[derive(Debug, PartialEq, Eq, Clone)]
157#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
158pub struct CircBuf<I> {
159 buf: [I; MAX_ITEMS],
160 idx: usize,
162 is_empty: bool,
163}
164
165impl<I: Default + Copy> Default for CircBuf<I> {
166 fn default() -> Self {
167 Self {
168 buf: [I::default(); MAX_ITEMS],
169 idx: MAX_ITEMS
170 .checked_sub(1)
171 .expect("`MAX_ITEMS` should be positive"),
172 is_empty: true,
173 }
174 }
175}
176
177impl<I> CircBuf<I> {
178 pub fn append(&mut self, item: I) {
179 self.idx = self
181 .idx
182 .checked_add(1)
183 .and_then(|idx| idx.checked_rem(MAX_ITEMS))
184 .expect("`self.idx` should be < `MAX_ITEMS` which should be non-zero");
185
186 self.buf[self.idx] = item;
187 self.is_empty = false;
188 }
189
190 pub fn buf(&self) -> &[I; MAX_ITEMS] {
191 &self.buf
192 }
193
194 pub fn last(&self) -> Option<&I> {
195 if !self.is_empty {
196 self.buf.get(self.idx)
197 } else {
198 None
199 }
200 }
201}
202
203#[cfg(feature = "serde")]
204pub mod serde_compact_vote_state_update {
205 use {
206 super::*,
207 crate::state::Lockout,
208 serde::{Deserialize, Deserializer, Serialize, Serializer},
209 solana_hash::Hash,
210 solana_serde_varint as serde_varint, solana_short_vec as short_vec,
211 };
212
213 #[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
214 #[derive(serde_derive::Deserialize, serde_derive::Serialize)]
215 struct LockoutOffset {
216 #[serde(with = "serde_varint")]
217 offset: Slot,
218 confirmation_count: u8,
219 }
220
221 #[derive(serde_derive::Deserialize, serde_derive::Serialize)]
222 struct CompactVoteStateUpdate {
223 root: Slot,
224 #[serde(with = "short_vec")]
225 lockout_offsets: Vec<LockoutOffset>,
226 hash: Hash,
227 timestamp: Option<UnixTimestamp>,
228 }
229
230 pub fn serialize<S>(
231 vote_state_update: &VoteStateUpdate,
232 serializer: S,
233 ) -> Result<S::Ok, S::Error>
234 where
235 S: Serializer,
236 {
237 let lockout_offsets = vote_state_update.lockouts.iter().scan(
238 vote_state_update.root.unwrap_or_default(),
239 |slot, lockout| {
240 let Some(offset) = lockout.slot().checked_sub(*slot) else {
241 return Some(Err(serde::ser::Error::custom("Invalid vote lockout")));
242 };
243 let Ok(confirmation_count) = u8::try_from(lockout.confirmation_count()) else {
244 return Some(Err(serde::ser::Error::custom("Invalid confirmation count")));
245 };
246 let lockout_offset = LockoutOffset {
247 offset,
248 confirmation_count,
249 };
250 *slot = lockout.slot();
251 Some(Ok(lockout_offset))
252 },
253 );
254 let compact_vote_state_update = CompactVoteStateUpdate {
255 root: vote_state_update.root.unwrap_or(Slot::MAX),
256 lockout_offsets: lockout_offsets.collect::<Result<_, _>>()?,
257 hash: Hash::new_from_array(vote_state_update.hash.to_bytes()),
258 timestamp: vote_state_update.timestamp,
259 };
260 compact_vote_state_update.serialize(serializer)
261 }
262
263 pub fn deserialize<'de, D>(deserializer: D) -> Result<VoteStateUpdate, D::Error>
264 where
265 D: Deserializer<'de>,
266 {
267 let CompactVoteStateUpdate {
268 root,
269 lockout_offsets,
270 hash,
271 timestamp,
272 } = CompactVoteStateUpdate::deserialize(deserializer)?;
273 let root = (root != Slot::MAX).then_some(root);
274 let lockouts =
275 lockout_offsets
276 .iter()
277 .scan(root.unwrap_or_default(), |slot, lockout_offset| {
278 *slot = match slot.checked_add(lockout_offset.offset) {
279 None => {
280 return Some(Err(serde::de::Error::custom("Invalid lockout offset")))
281 }
282 Some(slot) => slot,
283 };
284 let lockout = Lockout::new_with_confirmation_count(
285 *slot,
286 u32::from(lockout_offset.confirmation_count),
287 );
288 Some(Ok(lockout))
289 });
290 Ok(VoteStateUpdate {
291 root,
292 lockouts: lockouts.collect::<Result<_, _>>()?,
293 hash,
294 timestamp,
295 })
296 }
297}
298
299#[cfg(feature = "serde")]
300pub mod serde_tower_sync {
301 use {
302 super::*,
303 crate::state::Lockout,
304 serde::{Deserialize, Deserializer, Serialize, Serializer},
305 solana_hash::Hash,
306 solana_serde_varint as serde_varint, solana_short_vec as short_vec,
307 };
308
309 #[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
310 #[derive(serde_derive::Deserialize, serde_derive::Serialize)]
311 struct LockoutOffset {
312 #[serde(with = "serde_varint")]
313 offset: Slot,
314 confirmation_count: u8,
315 }
316
317 #[derive(serde_derive::Deserialize, serde_derive::Serialize)]
318 struct CompactTowerSync {
319 root: Slot,
320 #[serde(with = "short_vec")]
321 lockout_offsets: Vec<LockoutOffset>,
322 hash: Hash,
323 timestamp: Option<UnixTimestamp>,
324 block_id: Hash,
325 }
326
327 pub fn serialize<S>(tower_sync: &TowerSync, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: Serializer,
330 {
331 let lockout_offsets = tower_sync.lockouts.iter().scan(
332 tower_sync.root.unwrap_or_default(),
333 |slot, lockout| {
334 let Some(offset) = lockout.slot().checked_sub(*slot) else {
335 return Some(Err(serde::ser::Error::custom("Invalid vote lockout")));
336 };
337 let Ok(confirmation_count) = u8::try_from(lockout.confirmation_count()) else {
338 return Some(Err(serde::ser::Error::custom("Invalid confirmation count")));
339 };
340 let lockout_offset = LockoutOffset {
341 offset,
342 confirmation_count,
343 };
344 *slot = lockout.slot();
345 Some(Ok(lockout_offset))
346 },
347 );
348 let compact_tower_sync = CompactTowerSync {
349 root: tower_sync.root.unwrap_or(Slot::MAX),
350 lockout_offsets: lockout_offsets.collect::<Result<_, _>>()?,
351 hash: Hash::new_from_array(tower_sync.hash.to_bytes()),
352 timestamp: tower_sync.timestamp,
353 block_id: Hash::new_from_array(tower_sync.block_id.to_bytes()),
354 };
355 compact_tower_sync.serialize(serializer)
356 }
357
358 pub fn deserialize<'de, D>(deserializer: D) -> Result<TowerSync, D::Error>
359 where
360 D: Deserializer<'de>,
361 {
362 let CompactTowerSync {
363 root,
364 lockout_offsets,
365 hash,
366 timestamp,
367 block_id,
368 } = CompactTowerSync::deserialize(deserializer)?;
369 let root = (root != Slot::MAX).then_some(root);
370 let lockouts =
371 lockout_offsets
372 .iter()
373 .scan(root.unwrap_or_default(), |slot, lockout_offset| {
374 *slot = match slot.checked_add(lockout_offset.offset) {
375 None => {
376 return Some(Err(serde::de::Error::custom("Invalid lockout offset")))
377 }
378 Some(slot) => slot,
379 };
380 let lockout = Lockout::new_with_confirmation_count(
381 *slot,
382 u32::from(lockout_offset.confirmation_count),
383 );
384 Some(Ok(lockout))
385 });
386 Ok(TowerSync {
387 root,
388 lockouts: lockouts.collect::<Result<_, _>>()?,
389 hash,
390 timestamp,
391 block_id,
392 })
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use {super::*, itertools::Itertools, rand::Rng, solana_hash::Hash};
399
400 #[test]
401 fn test_serde_compact_vote_state_update() {
402 let mut rng = rand::rng();
403 for _ in 0..5000 {
404 run_serde_compact_vote_state_update(&mut rng);
405 }
406 }
407
408 fn run_serde_compact_vote_state_update<R: Rng>(rng: &mut R) {
409 let lockouts: VecDeque<_> = std::iter::repeat_with(|| {
410 let slot = 149_303_885_u64.saturating_add(rng.random_range(0..10_000));
411 let confirmation_count = rng.random_range(0..33);
412 Lockout::new_with_confirmation_count(slot, confirmation_count)
413 })
414 .take(32)
415 .sorted_by_key(|lockout| lockout.slot())
416 .collect();
417 let root = rng.random_bool(0.5).then(|| {
418 lockouts[0]
419 .slot()
420 .checked_sub(rng.random_range(0..1_000))
421 .expect("All slots should be greater than 1_000")
422 });
423 let timestamp = rng.random_bool(0.5).then(|| rng.random());
424 let hash = Hash::from(rng.random::<[u8; 32]>());
425 let vote_state_update = VoteStateUpdate {
426 lockouts,
427 root,
428 hash,
429 timestamp,
430 };
431 #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
432 enum VoteInstruction {
433 #[serde(with = "serde_compact_vote_state_update")]
434 UpdateVoteState(VoteStateUpdate),
435 UpdateVoteStateSwitch(
436 #[serde(with = "serde_compact_vote_state_update")] VoteStateUpdate,
437 Hash,
438 ),
439 }
440 let vote = VoteInstruction::UpdateVoteState(vote_state_update.clone());
441 let bytes = bincode::serialize(&vote).unwrap();
442 assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
443 let hash = Hash::from(rng.random::<[u8; 32]>());
444 let vote = VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash);
445 let bytes = bincode::serialize(&vote).unwrap();
446 assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
447 }
448
449 #[test]
450 fn test_circbuf_oob() {
451 let data: &[u8] = &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
453 let circ_buf: CircBuf<()> = bincode::deserialize(data).unwrap();
454 assert_eq!(circ_buf.last(), None);
455 }
456}