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, StableAbi, StableAbiSample};
9use {
10 crate::authorized_voters::AuthorizedVoters,
11 solana_clock::{Epoch, Slot, UnixTimestamp},
12 solana_pubkey::Pubkey,
13 std::{collections::VecDeque, fmt::Debug},
14};
15
16pub mod vote_state_1_14_11;
17pub use vote_state_1_14_11::*;
18pub mod vote_state_versions;
19pub use vote_state_versions::*;
20pub mod vote_state_v3;
21pub use vote_state_v3::VoteStateV3;
22pub mod vote_state_v4;
23pub use vote_state_v4::VoteStateV4;
24mod vote_instruction_data;
25pub use vote_instruction_data::*;
26#[cfg(any(target_os = "solana", feature = "bincode"))]
27pub(crate) mod vote_state_deserialize;
28
29pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48;
31
32pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96;
34
35pub const MAX_LOCKOUT_HISTORY: usize = 31;
37pub const INITIAL_LOCKOUT: usize = 2;
38
39pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
41
42const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 114;
44
45pub const VOTE_CREDITS_GRACE_SLOTS: u8 = 2;
47
48pub const VOTE_CREDITS_MAXIMUM_PER_SLOT: u8 = 16;
50
51#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
52#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
53#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
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 #[cfg_attr(
61 feature = "frozen-abi",
62 stable_abi_sample(with = "sampling::sample_confirmation_count(rng)")
63 )]
64 confirmation_count: u32,
65}
66
67impl Lockout {
68 pub fn new(slot: Slot) -> Self {
69 Self::new_with_confirmation_count(slot, 1)
70 }
71
72 pub fn new_with_confirmation_count(slot: Slot, confirmation_count: u32) -> Self {
73 Self {
74 slot,
75 confirmation_count,
76 }
77 }
78
79 pub fn lockout(&self) -> u64 {
81 (INITIAL_LOCKOUT as u64).wrapping_pow(std::cmp::min(
82 self.confirmation_count(),
83 MAX_LOCKOUT_HISTORY as u32,
84 ))
85 }
86
87 pub fn last_locked_out_slot(&self) -> Slot {
91 self.slot.saturating_add(self.lockout())
92 }
93
94 pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
95 self.last_locked_out_slot() >= slot
96 }
97
98 pub fn slot(&self) -> Slot {
99 self.slot
100 }
101
102 pub fn confirmation_count(&self) -> u32 {
103 self.confirmation_count
104 }
105
106 pub fn increase_confirmation_count(&mut self, by: u32) {
107 self.confirmation_count = self.confirmation_count.saturating_add(by)
108 }
109}
110
111#[cfg(any(feature = "frozen-abi", test))]
120mod sampling {
121 use {
122 super::{Lockout, MAX_LOCKOUT_HISTORY},
123 solana_clock::Slot,
124 std::collections::VecDeque,
125 };
126
127 const LOCKOUT_SAMPLE_SLOT_BASE: Slot = 149_303_885;
128 const LOCKOUT_SAMPLE_SLOT_STEP: Slot = 1_000;
129
130 pub(super) fn sample_confirmation_count<R: rand::Rng + ?Sized>(rng: &mut R) -> u32 {
133 rng.random_range(0..=MAX_LOCKOUT_HISTORY as u32)
134 }
135
136 pub(super) fn sample_lockouts<R: rand::Rng + ?Sized>(rng: &mut R) -> VecDeque<Lockout> {
139 let mut slot = LOCKOUT_SAMPLE_SLOT_BASE;
140 (0..rng.random_range(0..=MAX_LOCKOUT_HISTORY))
141 .map(|_| {
142 slot = slot.saturating_add(rng.random_range(1..=LOCKOUT_SAMPLE_SLOT_STEP));
143 Lockout::new_with_confirmation_count(slot, sample_confirmation_count(rng))
144 })
145 .collect()
146 }
147
148 pub(super) fn sample_root<R: rand::Rng + ?Sized>(rng: &mut R) -> Option<Slot> {
151 rng.random_bool(0.5).then(|| {
152 LOCKOUT_SAMPLE_SLOT_BASE.saturating_sub(rng.random_range(0..=LOCKOUT_SAMPLE_SLOT_STEP))
153 })
154 }
155}
156
157#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
158#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
159#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
160#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
161#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
162pub struct LandedVote {
163 pub latency: u8,
167 pub lockout: Lockout,
168}
169
170impl LandedVote {
171 pub fn slot(&self) -> Slot {
172 self.lockout.slot
173 }
174
175 pub fn confirmation_count(&self) -> u32 {
176 self.lockout.confirmation_count
177 }
178}
179
180impl From<LandedVote> for Lockout {
181 fn from(landed_vote: LandedVote) -> Self {
182 landed_vote.lockout
183 }
184}
185
186impl From<Lockout> for LandedVote {
187 fn from(lockout: Lockout) -> Self {
188 Self {
189 latency: 0,
190 lockout,
191 }
192 }
193}
194
195#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
196#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
197#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
198#[derive(Debug, Default, PartialEq, Eq, Clone)]
199#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
200pub struct BlockTimestamp {
201 pub slot: Slot,
202 pub timestamp: UnixTimestamp,
203}
204
205const MAX_ITEMS: usize = 32;
207
208#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
209#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
210#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
211#[derive(Debug, PartialEq, Eq, Clone)]
212#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
213pub struct CircBuf<I> {
214 buf: [I; MAX_ITEMS],
215 idx: usize,
217 is_empty: bool,
218}
219
220impl<I: Default + Copy> Default for CircBuf<I> {
221 fn default() -> Self {
222 Self {
223 buf: [I::default(); MAX_ITEMS],
224 idx: MAX_ITEMS
225 .checked_sub(1)
226 .expect("`MAX_ITEMS` should be positive"),
227 is_empty: true,
228 }
229 }
230}
231
232impl<I> CircBuf<I> {
233 pub fn append(&mut self, item: I) {
234 self.idx = self
236 .idx
237 .checked_add(1)
238 .and_then(|idx| idx.checked_rem(MAX_ITEMS))
239 .expect("`self.idx` should be < `MAX_ITEMS` which should be non-zero");
240
241 self.buf[self.idx] = item;
242 self.is_empty = false;
243 }
244
245 pub fn buf(&self) -> &[I; MAX_ITEMS] {
246 &self.buf
247 }
248
249 pub fn last(&self) -> Option<&I> {
250 if !self.is_empty {
251 self.buf.get(self.idx)
252 } else {
253 None
254 }
255 }
256}
257
258#[cfg(any(feature = "serde", feature = "wincode"))]
266mod compact {
267 #[cfg(feature = "serde")]
268 use serde_derive::{Deserialize, Serialize};
269 #[cfg(feature = "frozen-abi")]
270 use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
271 use {
272 super::{Lockout, TowerSync, VoteStateUpdate},
273 solana_clock::{Slot, UnixTimestamp},
274 solana_hash::Hash,
275 std::collections::VecDeque,
276 };
277 #[cfg(feature = "wincode")]
278 use {
279 solana_short_vec::ShortU16,
280 solana_wincode_varint::Leb128Int,
281 wincode::{containers, SchemaRead, SchemaWrite},
282 };
283
284 #[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
285 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
286 #[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
287 struct LockoutOffset {
288 #[cfg_attr(feature = "serde", serde(with = "solana_serde_varint"))]
289 #[cfg_attr(feature = "wincode", wincode(with = "Leb128Int<Slot>"))]
290 offset: Slot,
291 confirmation_count: u8,
292 }
293
294 #[cfg(feature = "wincode")]
297 type LockoutOffsetShortVec = containers::Vec<LockoutOffset, ShortU16>;
298
299 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
300 #[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
301 pub(super) struct CompactVoteStateUpdate {
302 root: Slot,
303 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
304 #[cfg_attr(feature = "wincode", wincode(with = "LockoutOffsetShortVec"))]
305 lockout_offsets: Vec<LockoutOffset>,
306 hash: Hash,
307 timestamp: Option<UnixTimestamp>,
308 }
309
310 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
311 #[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
312 pub(super) struct CompactTowerSync {
313 root: Slot,
314 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
315 #[cfg_attr(feature = "wincode", wincode(with = "LockoutOffsetShortVec"))]
316 lockout_offsets: Vec<LockoutOffset>,
317 hash: Hash,
318 timestamp: Option<UnixTimestamp>,
319 block_id: Hash,
320 }
321
322 fn lockout_offsets(
328 lockouts: &VecDeque<Lockout>,
329 root: Option<Slot>,
330 ) -> Result<Vec<LockoutOffset>, &'static str> {
331 let mut offsets = Vec::with_capacity(lockouts.len());
332 let mut slot = root.unwrap_or_default();
333 for lockout in lockouts {
334 let offset = lockout
335 .slot()
336 .checked_sub(slot)
337 .ok_or("Invalid vote lockout")?;
338 let confirmation_count = u8::try_from(lockout.confirmation_count())
339 .map_err(|_| "Invalid confirmation count")?;
340 offsets.push(LockoutOffset {
341 offset,
342 confirmation_count,
343 });
344 slot = lockout.slot();
345 }
346 Ok(offsets)
347 }
348
349 fn lockouts_from_offsets(
352 lockout_offsets: &[LockoutOffset],
353 root: Option<Slot>,
354 ) -> Result<VecDeque<Lockout>, &'static str> {
355 let mut lockouts = VecDeque::with_capacity(lockout_offsets.len());
356 let mut slot = root.unwrap_or_default();
357 for lockout_offset in lockout_offsets {
358 slot = slot
359 .checked_add(lockout_offset.offset)
360 .ok_or("Invalid lockout offset")?;
361 lockouts.push_back(Lockout::new_with_confirmation_count(
362 slot,
363 u32::from(lockout_offset.confirmation_count),
364 ));
365 }
366 Ok(lockouts)
367 }
368
369 pub(super) fn vote_state_update_to_compact(
370 src: &VoteStateUpdate,
371 ) -> Result<CompactVoteStateUpdate, &'static str> {
372 #[allow(clippy::clone_on_copy)]
373 Ok(CompactVoteStateUpdate {
374 root: src.root.unwrap_or(Slot::MAX),
375 lockout_offsets: lockout_offsets(&src.lockouts, src.root)?,
376 hash: src.hash.clone(),
377 timestamp: src.timestamp,
378 })
379 }
380
381 pub(super) fn vote_state_update_from_compact(
382 repr: CompactVoteStateUpdate,
383 ) -> Result<VoteStateUpdate, &'static str> {
384 let root = (repr.root != Slot::MAX).then_some(repr.root);
385 Ok(VoteStateUpdate {
386 lockouts: lockouts_from_offsets(&repr.lockout_offsets, root)?,
387 root,
388 hash: repr.hash,
389 timestamp: repr.timestamp,
390 })
391 }
392
393 pub(super) fn tower_sync_to_compact(src: &TowerSync) -> Result<CompactTowerSync, &'static str> {
394 #[allow(clippy::clone_on_copy)]
395 Ok(CompactTowerSync {
396 root: src.root.unwrap_or(Slot::MAX),
397 lockout_offsets: lockout_offsets(&src.lockouts, src.root)?,
398 hash: src.hash.clone(),
399 timestamp: src.timestamp,
400 block_id: src.block_id.clone(),
401 })
402 }
403
404 pub(super) fn tower_sync_from_compact(
405 repr: CompactTowerSync,
406 ) -> Result<TowerSync, &'static str> {
407 let root = (repr.root != Slot::MAX).then_some(repr.root);
408 Ok(TowerSync {
409 lockouts: lockouts_from_offsets(&repr.lockout_offsets, root)?,
410 root,
411 hash: repr.hash,
412 timestamp: repr.timestamp,
413 block_id: repr.block_id,
414 })
415 }
416}
417
418#[cfg(feature = "serde")]
419pub mod serde_compact_vote_state_update {
420 use {
421 super::{compact, compact::CompactVoteStateUpdate, *},
422 serde::{Deserialize, Deserializer, Serialize, Serializer},
423 };
424
425 pub fn serialize<S>(
426 vote_state_update: &VoteStateUpdate,
427 serializer: S,
428 ) -> Result<S::Ok, S::Error>
429 where
430 S: Serializer,
431 {
432 compact::vote_state_update_to_compact(vote_state_update)
433 .map_err(serde::ser::Error::custom)?
434 .serialize(serializer)
435 }
436
437 pub fn deserialize<'de, D>(deserializer: D) -> Result<VoteStateUpdate, D::Error>
438 where
439 D: Deserializer<'de>,
440 {
441 let repr = CompactVoteStateUpdate::deserialize(deserializer)?;
442 compact::vote_state_update_from_compact(repr).map_err(serde::de::Error::custom)
443 }
444}
445
446#[cfg(feature = "serde")]
447pub mod serde_tower_sync {
448 use {
449 super::{compact, compact::CompactTowerSync, *},
450 serde::{Deserialize, Deserializer, Serialize, Serializer},
451 };
452
453 pub fn serialize<S>(tower_sync: &TowerSync, serializer: S) -> Result<S::Ok, S::Error>
454 where
455 S: Serializer,
456 {
457 compact::tower_sync_to_compact(tower_sync)
458 .map_err(serde::ser::Error::custom)?
459 .serialize(serializer)
460 }
461
462 pub fn deserialize<'de, D>(deserializer: D) -> Result<TowerSync, D::Error>
463 where
464 D: Deserializer<'de>,
465 {
466 let repr = CompactTowerSync::deserialize(deserializer)?;
467 compact::tower_sync_from_compact(repr).map_err(serde::de::Error::custom)
468 }
469}
470
471#[cfg(feature = "wincode")]
482pub mod wincode_compact {
483 use {
484 super::{
485 compact,
486 compact::{
487 CompactTowerSync as CompactTowerSyncRepr,
488 CompactVoteStateUpdate as CompactVoteStateUpdateRepr,
489 },
490 TowerSync, VoteStateUpdate,
491 },
492 std::mem::MaybeUninit,
493 wincode::{
494 config::Config,
495 io::{Reader, Writer},
496 ReadError, ReadResult, SchemaRead, SchemaWrite, WriteError, WriteResult,
497 },
498 };
499
500 pub struct CompactVoteStateUpdate;
502
503 unsafe impl<C: Config> SchemaWrite<C> for CompactVoteStateUpdate {
504 type Src = VoteStateUpdate;
505
506 fn size_of(src: &Self::Src) -> WriteResult<usize> {
507 let repr = compact::vote_state_update_to_compact(src).map_err(WriteError::Custom)?;
508 <CompactVoteStateUpdateRepr as SchemaWrite<C>>::size_of(&repr)
509 }
510
511 fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
512 let repr = compact::vote_state_update_to_compact(src).map_err(WriteError::Custom)?;
513 <CompactVoteStateUpdateRepr as SchemaWrite<C>>::write(writer, &repr)
514 }
515 }
516
517 unsafe impl<'de, C: Config> SchemaRead<'de, C> for CompactVoteStateUpdate {
518 type Dst = VoteStateUpdate;
519
520 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
521 let repr = <CompactVoteStateUpdateRepr as SchemaRead<C>>::get(reader)?;
522 dst.write(compact::vote_state_update_from_compact(repr).map_err(ReadError::Custom)?);
523 Ok(())
524 }
525 }
526
527 pub struct CompactTowerSync;
529
530 unsafe impl<C: Config> SchemaWrite<C> for CompactTowerSync {
531 type Src = TowerSync;
532
533 fn size_of(src: &Self::Src) -> WriteResult<usize> {
534 let repr = compact::tower_sync_to_compact(src).map_err(WriteError::Custom)?;
535 <CompactTowerSyncRepr as SchemaWrite<C>>::size_of(&repr)
536 }
537
538 fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
539 let repr = compact::tower_sync_to_compact(src).map_err(WriteError::Custom)?;
540 <CompactTowerSyncRepr as SchemaWrite<C>>::write(writer, &repr)
541 }
542 }
543
544 unsafe impl<'de, C: Config> SchemaRead<'de, C> for CompactTowerSync {
545 type Dst = TowerSync;
546
547 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
548 let repr = <CompactTowerSyncRepr as SchemaRead<C>>::get(reader)?;
549 dst.write(compact::tower_sync_from_compact(repr).map_err(ReadError::Custom)?);
550 Ok(())
551 }
552 }
553}
554
555#[cfg(all(test, feature = "bincode"))]
556mod tests {
557 use {super::*, rand::Rng, solana_hash::Hash};
558
559 fn random_vote_state_update<R: Rng>(rng: &mut R) -> VoteStateUpdate {
563 VoteStateUpdate {
564 lockouts: sampling::sample_lockouts(rng),
565 root: sampling::sample_root(rng),
566 hash: Hash::from(rng.random::<[u8; 32]>()),
567 timestamp: rng.random_bool(0.5).then(|| rng.random()),
568 }
569 }
570
571 #[test]
572 fn test_serde_compact_vote_state_update() {
573 let mut rng = rand::rng();
574 for _ in 0..5000 {
575 run_serde_compact_vote_state_update(&mut rng);
576 }
577 }
578
579 fn run_serde_compact_vote_state_update<R: Rng>(rng: &mut R) {
580 let vote_state_update = random_vote_state_update(rng);
581 #[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
582 #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
583 enum VoteInstruction {
584 #[serde(with = "serde_compact_vote_state_update")]
585 UpdateVoteState(
586 #[cfg_attr(
587 feature = "wincode",
588 wincode(with = "wincode_compact::CompactVoteStateUpdate")
589 )]
590 VoteStateUpdate,
591 ),
592 UpdateVoteStateSwitch(
593 #[serde(with = "serde_compact_vote_state_update")]
594 #[cfg_attr(
595 feature = "wincode",
596 wincode(with = "wincode_compact::CompactVoteStateUpdate")
597 )]
598 VoteStateUpdate,
599 Hash,
600 ),
601 }
602
603 let check = |vote: &VoteInstruction| {
606 let bytes = bincode::serialize(vote).unwrap();
607 assert_eq!(*vote, bincode::deserialize(&bytes).unwrap());
608 #[cfg(feature = "wincode")]
609 {
610 assert_eq!(bytes, wincode::serialize(vote).unwrap());
611 assert_eq!(*vote, wincode::deserialize(&bytes).unwrap());
612 }
613 };
614
615 check(&VoteInstruction::UpdateVoteState(vote_state_update.clone()));
616 let hash = Hash::from(rng.random::<[u8; 32]>());
617 check(&VoteInstruction::UpdateVoteStateSwitch(
618 vote_state_update,
619 hash,
620 ));
621 }
622
623 #[test]
624 fn test_circbuf_oob() {
625 let data: &[u8] = &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
627 let circ_buf: CircBuf<()> = bincode::deserialize(data).unwrap();
628 assert_eq!(circ_buf.last(), None);
629
630 #[cfg(feature = "wincode")]
631 {
632 let circ_buf: CircBuf<()> = wincode::deserialize(data).unwrap();
633 assert_eq!(circ_buf.last(), None);
634 }
635 }
636}