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