#[cfg(feature = "dev-context-only-utils")]
use arbitrary::Arbitrary;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "frozen-abi")]
use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
use {
crate::authorized_voters::AuthorizedVoters,
solana_clock::{Epoch, Slot, UnixTimestamp},
solana_pubkey::Pubkey,
solana_rent::Rent,
std::{collections::VecDeque, fmt::Debug},
};
pub mod vote_state_1_14_11;
pub use vote_state_1_14_11::*;
pub mod vote_state_versions;
pub use vote_state_versions::*;
pub mod vote_state_v3;
pub use vote_state_v3::VoteStateV3;
pub mod vote_state_v4;
pub use vote_state_v4::VoteStateV4;
mod vote_instruction_data;
pub use vote_instruction_data::*;
#[cfg(any(target_os = "solana", feature = "bincode"))]
pub(crate) mod vote_state_deserialize;
pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48;
pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96;
pub const MAX_LOCKOUT_HISTORY: usize = 31;
pub const INITIAL_LOCKOUT: usize = 2;
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 114;
pub const VOTE_CREDITS_GRACE_SLOTS: u8 = 2;
pub const VOTE_CREDITS_MAXIMUM_PER_SLOT: u8 = 16;
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct Lockout {
slot: Slot,
confirmation_count: u32,
}
impl Lockout {
pub fn new(slot: Slot) -> Self {
Self::new_with_confirmation_count(slot, 1)
}
pub fn new_with_confirmation_count(slot: Slot, confirmation_count: u32) -> Self {
Self {
slot,
confirmation_count,
}
}
pub fn lockout(&self) -> u64 {
(INITIAL_LOCKOUT as u64).wrapping_pow(std::cmp::min(
self.confirmation_count(),
MAX_LOCKOUT_HISTORY as u32,
))
}
pub fn last_locked_out_slot(&self) -> Slot {
self.slot.saturating_add(self.lockout())
}
pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
self.last_locked_out_slot() >= slot
}
pub fn slot(&self) -> Slot {
self.slot
}
pub fn confirmation_count(&self) -> u32 {
self.confirmation_count
}
pub fn increase_confirmation_count(&mut self, by: u32) {
self.confirmation_count = self.confirmation_count.saturating_add(by)
}
}
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct LandedVote {
pub latency: u8,
pub lockout: Lockout,
}
impl LandedVote {
pub fn slot(&self) -> Slot {
self.lockout.slot
}
pub fn confirmation_count(&self) -> u32 {
self.lockout.confirmation_count
}
}
impl From<LandedVote> for Lockout {
fn from(landed_vote: LandedVote) -> Self {
landed_vote.lockout
}
}
impl From<Lockout> for LandedVote {
fn from(lockout: Lockout) -> Self {
Self {
latency: 0,
lockout,
}
}
}
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
#[derive(Debug, Default, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct BlockTimestamp {
pub slot: Slot,
pub timestamp: UnixTimestamp,
}
const MAX_ITEMS: usize = 32;
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct CircBuf<I> {
buf: [I; MAX_ITEMS],
idx: usize,
is_empty: bool,
}
impl<I: Default + Copy> Default for CircBuf<I> {
fn default() -> Self {
Self {
buf: [I::default(); MAX_ITEMS],
idx: MAX_ITEMS
.checked_sub(1)
.expect("`MAX_ITEMS` should be positive"),
is_empty: true,
}
}
}
impl<I> CircBuf<I> {
pub fn append(&mut self, item: I) {
self.idx = self
.idx
.checked_add(1)
.and_then(|idx| idx.checked_rem(MAX_ITEMS))
.expect("`self.idx` should be < `MAX_ITEMS` which should be non-zero");
self.buf[self.idx] = item;
self.is_empty = false;
}
pub fn buf(&self) -> &[I; MAX_ITEMS] {
&self.buf
}
pub fn last(&self) -> Option<&I> {
if !self.is_empty {
self.buf.get(self.idx)
} else {
None
}
}
}
#[cfg(any(feature = "serde", feature = "wincode"))]
mod compact {
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "frozen-abi")]
use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
use {
super::{Lockout, TowerSync, VoteStateUpdate},
solana_clock::{Slot, UnixTimestamp},
solana_hash::Hash,
std::collections::VecDeque,
};
#[cfg(feature = "wincode")]
use {
solana_short_vec::ShortU16,
solana_wincode_varint::Leb128Int,
wincode::{containers, SchemaRead, SchemaWrite},
};
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
struct LockoutOffset {
#[cfg_attr(feature = "serde", serde(with = "solana_serde_varint"))]
#[cfg_attr(feature = "wincode", wincode(with = "Leb128Int<Slot>"))]
offset: Slot,
confirmation_count: u8,
}
#[cfg(feature = "wincode")]
type LockoutOffsetShortVec = containers::Vec<LockoutOffset, ShortU16>;
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
pub(super) struct CompactVoteStateUpdate {
root: Slot,
#[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
#[cfg_attr(feature = "wincode", wincode(with = "LockoutOffsetShortVec"))]
lockout_offsets: Vec<LockoutOffset>,
hash: Hash,
timestamp: Option<UnixTimestamp>,
}
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
pub(super) struct CompactTowerSync {
root: Slot,
#[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
#[cfg_attr(feature = "wincode", wincode(with = "LockoutOffsetShortVec"))]
lockout_offsets: Vec<LockoutOffset>,
hash: Hash,
timestamp: Option<UnixTimestamp>,
block_id: Hash,
}
fn lockout_offsets(
lockouts: &VecDeque<Lockout>,
root: Option<Slot>,
) -> Result<Vec<LockoutOffset>, &'static str> {
let mut offsets = Vec::with_capacity(lockouts.len());
let mut slot = root.unwrap_or_default();
for lockout in lockouts {
let offset = lockout
.slot()
.checked_sub(slot)
.ok_or("Invalid vote lockout")?;
let confirmation_count = u8::try_from(lockout.confirmation_count())
.map_err(|_| "Invalid confirmation count")?;
offsets.push(LockoutOffset {
offset,
confirmation_count,
});
slot = lockout.slot();
}
Ok(offsets)
}
fn lockouts_from_offsets(
lockout_offsets: &[LockoutOffset],
root: Option<Slot>,
) -> Result<VecDeque<Lockout>, &'static str> {
let mut lockouts = VecDeque::with_capacity(lockout_offsets.len());
let mut slot = root.unwrap_or_default();
for lockout_offset in lockout_offsets {
slot = slot
.checked_add(lockout_offset.offset)
.ok_or("Invalid lockout offset")?;
lockouts.push_back(Lockout::new_with_confirmation_count(
slot,
u32::from(lockout_offset.confirmation_count),
));
}
Ok(lockouts)
}
pub(super) fn vote_state_update_to_compact(
src: &VoteStateUpdate,
) -> Result<CompactVoteStateUpdate, &'static str> {
#[allow(clippy::clone_on_copy)]
Ok(CompactVoteStateUpdate {
root: src.root.unwrap_or(Slot::MAX),
lockout_offsets: lockout_offsets(&src.lockouts, src.root)?,
hash: src.hash.clone(),
timestamp: src.timestamp,
})
}
pub(super) fn vote_state_update_from_compact(
repr: CompactVoteStateUpdate,
) -> Result<VoteStateUpdate, &'static str> {
let root = (repr.root != Slot::MAX).then_some(repr.root);
Ok(VoteStateUpdate {
lockouts: lockouts_from_offsets(&repr.lockout_offsets, root)?,
root,
hash: repr.hash,
timestamp: repr.timestamp,
})
}
pub(super) fn tower_sync_to_compact(src: &TowerSync) -> Result<CompactTowerSync, &'static str> {
#[allow(clippy::clone_on_copy)]
Ok(CompactTowerSync {
root: src.root.unwrap_or(Slot::MAX),
lockout_offsets: lockout_offsets(&src.lockouts, src.root)?,
hash: src.hash.clone(),
timestamp: src.timestamp,
block_id: src.block_id.clone(),
})
}
pub(super) fn tower_sync_from_compact(
repr: CompactTowerSync,
) -> Result<TowerSync, &'static str> {
let root = (repr.root != Slot::MAX).then_some(repr.root);
Ok(TowerSync {
lockouts: lockouts_from_offsets(&repr.lockout_offsets, root)?,
root,
hash: repr.hash,
timestamp: repr.timestamp,
block_id: repr.block_id,
})
}
}
#[cfg(feature = "serde")]
pub mod serde_compact_vote_state_update {
use {
super::{compact, compact::CompactVoteStateUpdate, *},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
pub fn serialize<S>(
vote_state_update: &VoteStateUpdate,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
compact::vote_state_update_to_compact(vote_state_update)
.map_err(serde::ser::Error::custom)?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<VoteStateUpdate, D::Error>
where
D: Deserializer<'de>,
{
let repr = CompactVoteStateUpdate::deserialize(deserializer)?;
compact::vote_state_update_from_compact(repr).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
pub mod serde_tower_sync {
use {
super::{compact, compact::CompactTowerSync, *},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
pub fn serialize<S>(tower_sync: &TowerSync, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
compact::tower_sync_to_compact(tower_sync)
.map_err(serde::ser::Error::custom)?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<TowerSync, D::Error>
where
D: Deserializer<'de>,
{
let repr = CompactTowerSync::deserialize(deserializer)?;
compact::tower_sync_from_compact(repr).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "wincode")]
pub mod wincode_compact {
use {
super::{
compact,
compact::{
CompactTowerSync as CompactTowerSyncRepr,
CompactVoteStateUpdate as CompactVoteStateUpdateRepr,
},
TowerSync, VoteStateUpdate,
},
std::mem::MaybeUninit,
wincode::{
config::Config,
io::{Reader, Writer},
ReadError, ReadResult, SchemaRead, SchemaWrite, WriteError, WriteResult,
},
};
pub struct CompactVoteStateUpdate;
unsafe impl<C: Config> SchemaWrite<C> for CompactVoteStateUpdate {
type Src = VoteStateUpdate;
fn size_of(src: &Self::Src) -> WriteResult<usize> {
let repr = compact::vote_state_update_to_compact(src).map_err(WriteError::Custom)?;
<CompactVoteStateUpdateRepr as SchemaWrite<C>>::size_of(&repr)
}
fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
let repr = compact::vote_state_update_to_compact(src).map_err(WriteError::Custom)?;
<CompactVoteStateUpdateRepr as SchemaWrite<C>>::write(writer, &repr)
}
}
unsafe impl<'de, C: Config> SchemaRead<'de, C> for CompactVoteStateUpdate {
type Dst = VoteStateUpdate;
fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
let repr = <CompactVoteStateUpdateRepr as SchemaRead<C>>::get(reader)?;
dst.write(compact::vote_state_update_from_compact(repr).map_err(ReadError::Custom)?);
Ok(())
}
}
pub struct CompactTowerSync;
unsafe impl<C: Config> SchemaWrite<C> for CompactTowerSync {
type Src = TowerSync;
fn size_of(src: &Self::Src) -> WriteResult<usize> {
let repr = compact::tower_sync_to_compact(src).map_err(WriteError::Custom)?;
<CompactTowerSyncRepr as SchemaWrite<C>>::size_of(&repr)
}
fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
let repr = compact::tower_sync_to_compact(src).map_err(WriteError::Custom)?;
<CompactTowerSyncRepr as SchemaWrite<C>>::write(writer, &repr)
}
}
unsafe impl<'de, C: Config> SchemaRead<'de, C> for CompactTowerSync {
type Dst = TowerSync;
fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
let repr = <CompactTowerSyncRepr as SchemaRead<C>>::get(reader)?;
dst.write(compact::tower_sync_from_compact(repr).map_err(ReadError::Custom)?);
Ok(())
}
}
}
#[cfg(all(test, feature = "bincode"))]
mod tests {
use {super::*, itertools::Itertools, rand::Rng, solana_hash::Hash};
fn random_vote_state_update<R: Rng>(rng: &mut R) -> VoteStateUpdate {
let lockouts: VecDeque<_> = std::iter::repeat_with(|| {
let slot = 149_303_885_u64.saturating_add(rng.random_range(0..10_000));
let confirmation_count = rng.random_range(0..33);
Lockout::new_with_confirmation_count(slot, confirmation_count)
})
.take(32)
.sorted_by_key(|lockout| lockout.slot())
.collect();
let root = rng.random_bool(0.5).then(|| {
lockouts[0]
.slot()
.checked_sub(rng.random_range(0..1_000))
.expect("All slots should be greater than 1_000")
});
let timestamp = rng.random_bool(0.5).then(|| rng.random());
let hash = Hash::from(rng.random::<[u8; 32]>());
VoteStateUpdate {
lockouts,
root,
hash,
timestamp,
}
}
#[test]
fn test_serde_compact_vote_state_update() {
let mut rng = rand::rng();
for _ in 0..5000 {
run_serde_compact_vote_state_update(&mut rng);
}
}
fn run_serde_compact_vote_state_update<R: Rng>(rng: &mut R) {
let vote_state_update = random_vote_state_update(rng);
#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
enum VoteInstruction {
#[serde(with = "serde_compact_vote_state_update")]
UpdateVoteState(
#[cfg_attr(
feature = "wincode",
wincode(with = "wincode_compact::CompactVoteStateUpdate")
)]
VoteStateUpdate,
),
UpdateVoteStateSwitch(
#[serde(with = "serde_compact_vote_state_update")]
#[cfg_attr(
feature = "wincode",
wincode(with = "wincode_compact::CompactVoteStateUpdate")
)]
VoteStateUpdate,
Hash,
),
}
let check = |vote: &VoteInstruction| {
let bytes = bincode::serialize(vote).unwrap();
assert_eq!(*vote, bincode::deserialize(&bytes).unwrap());
#[cfg(feature = "wincode")]
{
assert_eq!(bytes, wincode::serialize(vote).unwrap());
assert_eq!(*vote, wincode::deserialize(&bytes).unwrap());
}
};
check(&VoteInstruction::UpdateVoteState(vote_state_update.clone()));
let hash = Hash::from(rng.random::<[u8; 32]>());
check(&VoteInstruction::UpdateVoteStateSwitch(
vote_state_update,
hash,
));
}
#[test]
fn test_circbuf_oob() {
let data: &[u8] = &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
let circ_buf: CircBuf<()> = bincode::deserialize(data).unwrap();
assert_eq!(circ_buf.last(), None);
#[cfg(feature = "wincode")]
{
let circ_buf: CircBuf<()> = wincode::deserialize(data).unwrap();
assert_eq!(circ_buf.last(), None);
}
}
}