#[cfg(feature = "bincode")]
use super::VoteStateVersions;
#[cfg(test)]
use super::{MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY};
#[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::{frozen_abi, AbiExample};
#[cfg(any(target_os = "solana", feature = "bincode"))]
use solana_instruction_error::InstructionError;
use {
super::{BlockTimestamp, CircBuf, LandedVote, Lockout, VoteInit},
crate::{authorized_voters::AuthorizedVoters, state::DEFAULT_PRIOR_VOTERS_OFFSET},
solana_clock::{Clock, Epoch, Slot},
solana_pubkey::Pubkey,
solana_rent::Rent,
std::{collections::VecDeque, fmt::Debug},
};
#[cfg_attr(
feature = "frozen-abi",
frozen_abi(digest = "pZqasQc6duzMYzpzU7eriHH9cMXmubuUP4NmCrkWZjt"),
derive(AbiExample)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Default, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct VoteStateV3 {
pub node_pubkey: Pubkey,
pub authorized_withdrawer: Pubkey,
pub commission: u8,
pub votes: VecDeque<LandedVote>,
pub root_slot: Option<Slot>,
pub authorized_voters: AuthorizedVoters,
pub prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
pub epoch_credits: Vec<(Epoch, u64, u64)>,
pub last_timestamp: BlockTimestamp,
}
impl VoteStateV3 {
pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
Self {
node_pubkey: vote_init.node_pubkey,
authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
authorized_withdrawer: vote_init.authorized_withdrawer,
commission: vote_init.commission,
..VoteStateV3::default()
}
}
pub fn new_rand_for_tests(node_pubkey: Pubkey, root_slot: Slot) -> Self {
let votes = (1..32)
.map(|x| LandedVote {
latency: 0,
lockout: Lockout::new_with_confirmation_count(
u64::from(x).saturating_add(root_slot),
32_u32.saturating_sub(x),
),
})
.collect();
Self {
node_pubkey,
root_slot: Some(root_slot),
votes,
..VoteStateV3::default()
}
}
#[deprecated(
since = "5.1.0",
note = "Use `rent.minimum_balance(VoteStateV3::size_of())` directly"
)]
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
rent.minimum_balance(VoteStateV3::size_of())
}
pub const fn size_of() -> usize {
3762 }
pub fn is_uninitialized(&self) -> bool {
self.authorized_voters.is_empty()
}
#[cfg(any(target_os = "solana", feature = "bincode"))]
pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
let mut vote_state = Self::default();
Self::deserialize_into(input, &mut vote_state)?;
Ok(vote_state)
}
#[cfg(any(target_os = "solana", feature = "bincode"))]
pub fn deserialize_into(
input: &[u8],
vote_state: &mut VoteStateV3,
) -> Result<(), InstructionError> {
use super::vote_state_deserialize;
vote_state_deserialize::deserialize_into(input, vote_state, Self::deserialize_into_ptr)
}
#[cfg(any(target_os = "solana", feature = "bincode"))]
pub fn deserialize_into_uninit(
input: &[u8],
vote_state: &mut std::mem::MaybeUninit<VoteStateV3>,
) -> Result<(), InstructionError> {
VoteStateV3::deserialize_into_ptr(input, vote_state.as_mut_ptr())
}
#[cfg(any(target_os = "solana", feature = "bincode"))]
fn deserialize_into_ptr(
input: &[u8],
vote_state: *mut VoteStateV3,
) -> Result<(), InstructionError> {
use super::vote_state_deserialize::deserialize_vote_state_into_v3;
let mut cursor = std::io::Cursor::new(input);
let variant = solana_serialize_utils::cursor::read_u32(&mut cursor)?;
match variant {
0 => Err(InstructionError::InvalidAccountData),
1 => deserialize_vote_state_into_v3(&mut cursor, vote_state, false),
2 => deserialize_vote_state_into_v3(&mut cursor, vote_state, true),
_ => Err(InstructionError::InvalidAccountData),
}?;
Ok(())
}
#[cfg(feature = "bincode")]
pub fn serialize(
versioned: &VoteStateVersions,
output: &mut [u8],
) -> Result<(), InstructionError> {
bincode::serialize_into(output, versioned).map_err(|err| match *err {
bincode::ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
_ => InstructionError::GenericError,
})
}
#[cfg(test)]
pub(crate) fn get_max_sized_vote_state() -> VoteStateV3 {
use solana_epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET;
let mut authorized_voters = AuthorizedVoters::default();
for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET {
authorized_voters.insert(i, Pubkey::new_unique());
}
VoteStateV3 {
votes: VecDeque::from(vec![LandedVote::default(); MAX_LOCKOUT_HISTORY]),
root_slot: Some(u64::MAX),
epoch_credits: vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY],
authorized_voters,
..Self::default()
}
}
pub fn current_epoch(&self) -> Epoch {
self.epoch_credits.last().map_or(0, |v| v.0)
}
pub fn credits(&self) -> u64 {
self.epoch_credits.last().map_or(0, |v| v.1)
}
pub fn is_correct_size_and_initialized(data: &[u8]) -> bool {
const VERSION_OFFSET: usize = 4;
const DEFAULT_PRIOR_VOTERS_END: usize = VERSION_OFFSET + DEFAULT_PRIOR_VOTERS_OFFSET;
data.len() == VoteStateV3::size_of()
&& data[VERSION_OFFSET..DEFAULT_PRIOR_VOTERS_END] != [0; DEFAULT_PRIOR_VOTERS_OFFSET]
}
}
#[cfg(test)]
mod tests {
use {
super::{
super::{VoteState1_14_11, VoteStateVersions, MAX_LOCKOUT_HISTORY},
*,
},
arbitrary::Unstructured,
bincode::serialized_size,
core::mem::MaybeUninit,
rand::Rng,
solana_instruction::error::InstructionError,
};
#[test]
fn test_size_of() {
let vote_state = VoteStateV3::get_max_sized_vote_state();
let vote_state = VoteStateVersions::new_v3(vote_state);
let size = serialized_size(&vote_state).unwrap();
assert_eq!(VoteStateV3::size_of() as u64, size);
}
#[test]
fn test_minimum_balance() {
let rent = solana_rent::Rent::default();
let minimum_balance = rent.minimum_balance(VoteStateV3::size_of());
assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
}
#[test]
fn test_vote_serialize() {
let mut buffer: Vec<u8> = vec![0; VoteStateV3::size_of()];
let mut vote_state = VoteStateV3::default();
vote_state
.votes
.resize(MAX_LOCKOUT_HISTORY, LandedVote::default());
vote_state.root_slot = Some(1);
let versioned = VoteStateVersions::new_v3(vote_state);
assert!(VoteStateV3::serialize(&versioned, &mut buffer[0..4]).is_err());
VoteStateV3::serialize(&versioned, &mut buffer).unwrap();
assert_eq!(
VoteStateV3::deserialize(&buffer).unwrap(),
versioned.try_convert_to_v3().unwrap()
);
}
#[test]
fn test_vote_deserialize_into() {
let target_vote_state = VoteStateV3::default();
let vote_state_buf =
bincode::serialize(&VoteStateVersions::new_v3(target_vote_state.clone())).unwrap();
let mut test_vote_state = VoteStateV3::default();
VoteStateV3::deserialize_into(&vote_state_buf, &mut test_vote_state).unwrap();
assert_eq!(target_vote_state, test_vote_state);
let struct_bytes_x4 = std::mem::size_of::<VoteStateV3>() * 4;
for _ in 0..1000 {
let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
let mut unstructured = Unstructured::new(&raw_data);
let target_vote_state_versions =
VoteStateVersions::arbitrary(&mut unstructured).unwrap();
let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
if let Ok(target_vote_state) = target_vote_state_versions.try_convert_to_v3() {
let mut test_vote_state = VoteStateV3::default();
VoteStateV3::deserialize_into(&vote_state_buf, &mut test_vote_state).unwrap();
assert_eq!(target_vote_state, test_vote_state);
}
}
}
#[test]
fn test_vote_deserialize_into_trailing_data() {
let target_vote_state = VoteStateV3::new_rand_for_tests(Pubkey::new_unique(), 42);
let vote_state_buf =
bincode::serialize(&VoteStateVersions::new_v3(target_vote_state.clone())).unwrap();
let mut buf_with_garbage = vote_state_buf.clone();
buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let mut test_vote_state = VoteStateV3::default();
VoteStateV3::deserialize_into(&buf_with_garbage, &mut test_vote_state).unwrap();
assert_eq!(target_vote_state, test_vote_state);
let mut buf_with_zeroes = vote_state_buf;
buf_with_zeroes.extend_from_slice(&[0u8; 64]);
let mut test_vote_state = VoteStateV3::default();
VoteStateV3::deserialize_into(&buf_with_zeroes, &mut test_vote_state).unwrap();
assert_eq!(target_vote_state, test_vote_state);
}
#[test]
fn test_vote_deserialize_into_error() {
let target_vote_state = VoteStateV3::new_rand_for_tests(Pubkey::new_unique(), 42);
let mut vote_state_buf =
bincode::serialize(&VoteStateVersions::new_v3(target_vote_state.clone())).unwrap();
let len = vote_state_buf.len();
vote_state_buf.truncate(len - 1);
let mut test_vote_state = VoteStateV3::default();
VoteStateV3::deserialize_into(&vote_state_buf, &mut test_vote_state).unwrap_err();
assert_eq!(test_vote_state, VoteStateV3::default());
}
#[test]
fn test_vote_deserialize_into_error_with_pre_state() {
let mut vote_state = VoteStateV3::new_rand_for_tests(Pubkey::new_unique(), 42);
vote_state.epoch_credits = vec![(0, 100, 0), (1, 200, 100), (2, 300, 200)];
let mut buf =
bincode::serialize(&VoteStateVersions::new_v3(VoteStateV3::default())).unwrap();
buf.truncate(buf.len() - 1);
VoteStateV3::deserialize_into(&buf, &mut vote_state).unwrap_err();
assert_eq!(vote_state, VoteStateV3::default());
}
#[test]
fn test_deserialize_into_uninit_no_reset_on_error() {
let target = VoteStateV3::new_rand_for_tests(Pubkey::new_unique(), 42);
let mut buf = bincode::serialize(&VoteStateVersions::new_v3(target)).unwrap();
buf.truncate(buf.len() - 1);
let mut test_vote_state = MaybeUninit::uninit();
let err = VoteStateV3::deserialize_into_uninit(&buf, &mut test_vote_state);
assert_eq!(err, Err(InstructionError::InvalidAccountData));
}
#[test]
fn test_vote_deserialize_into_uninit() {
let target_vote_state = VoteStateV3::default();
let vote_state_buf =
bincode::serialize(&VoteStateVersions::new_v3(target_vote_state.clone())).unwrap();
let mut test_vote_state = MaybeUninit::uninit();
VoteStateV3::deserialize_into_uninit(&vote_state_buf, &mut test_vote_state).unwrap();
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(target_vote_state, test_vote_state);
let struct_bytes_x4 = std::mem::size_of::<VoteStateV3>() * 4;
for _ in 0..1000 {
let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
let mut unstructured = Unstructured::new(&raw_data);
let target_vote_state_versions =
VoteStateVersions::arbitrary(&mut unstructured).unwrap();
let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap();
if let Ok(target_vote_state) = target_vote_state_versions.try_convert_to_v3() {
let mut test_vote_state = MaybeUninit::uninit();
VoteStateV3::deserialize_into_uninit(&vote_state_buf, &mut test_vote_state)
.unwrap();
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(target_vote_state, test_vote_state);
}
}
}
#[test]
fn test_vote_deserialize_into_uninit_trailing_data() {
let target_vote_state = VoteStateV3::new_rand_for_tests(Pubkey::new_unique(), 42);
let vote_state_buf =
bincode::serialize(&VoteStateVersions::new_v3(target_vote_state.clone())).unwrap();
let mut buf_with_garbage = vote_state_buf.clone();
buf_with_garbage.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let mut test_vote_state = MaybeUninit::uninit();
VoteStateV3::deserialize_into_uninit(&buf_with_garbage, &mut test_vote_state).unwrap();
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(target_vote_state, test_vote_state);
let mut buf_with_zeroes = vote_state_buf;
buf_with_zeroes.extend_from_slice(&[0u8; 64]);
let mut test_vote_state = MaybeUninit::uninit();
VoteStateV3::deserialize_into_uninit(&buf_with_zeroes, &mut test_vote_state).unwrap();
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(target_vote_state, test_vote_state);
}
#[test]
fn test_vote_deserialize_into_uninit_nopanic() {
let mut test_vote_state = MaybeUninit::uninit();
let e = VoteStateV3::deserialize_into_uninit(&[], &mut test_vote_state).unwrap_err();
assert_eq!(e, InstructionError::InvalidAccountData);
let serialized_len_x4 = serialized_size(&VoteStateV3::default()).unwrap() * 4;
let mut rng = rand::rng();
for _ in 0..1000 {
let raw_data_length = rng.random_range(1..serialized_len_x4);
let mut raw_data: Vec<u8> = (0..raw_data_length).map(|_| rng.random::<u8>()).collect();
if raw_data_length >= 4 && rng.random::<bool>() {
let tag = rng.random_range(1u8..=3);
raw_data[0] = tag;
raw_data[1] = 0;
raw_data[2] = 0;
raw_data[3] = 0;
}
let mut test_vote_state = MaybeUninit::uninit();
let test_res = VoteStateV3::deserialize_into_uninit(&raw_data, &mut test_vote_state);
let bincode_res = bincode::deserialize::<VoteStateVersions>(&raw_data)
.map_err(|_| InstructionError::InvalidAccountData)
.and_then(|versioned| versioned.try_convert_to_v3());
if test_res.is_err() {
assert!(bincode_res.is_err());
} else {
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(test_vote_state, bincode_res.unwrap());
}
}
}
#[test]
fn test_vote_deserialize_into_uninit_ill_sized() {
let struct_bytes_x4 = std::mem::size_of::<VoteStateV3>() * 4;
for _ in 0..1000 {
let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
let mut unstructured = Unstructured::new(&raw_data);
let original_vote_state_versions =
VoteStateVersions::arbitrary(&mut unstructured).unwrap();
let original_buf = bincode::serialize(&original_vote_state_versions).unwrap();
if !matches!(original_vote_state_versions, VoteStateVersions::V4(_)) {
let mut truncated_buf = original_buf.clone();
let mut expanded_buf = original_buf.clone();
truncated_buf.resize(original_buf.len() - 8, 0);
expanded_buf.resize(original_buf.len() + 8, 0);
let mut test_vote_state = MaybeUninit::uninit();
let test_res =
VoteStateV3::deserialize_into_uninit(&truncated_buf, &mut test_vote_state);
let bincode_res = bincode::deserialize::<VoteStateVersions>(&truncated_buf)
.map_err(|_| InstructionError::InvalidAccountData)
.and_then(|versioned| versioned.try_convert_to_v3());
assert!(test_res.is_err());
assert!(bincode_res.is_err());
let mut test_vote_state = MaybeUninit::uninit();
VoteStateV3::deserialize_into_uninit(&expanded_buf, &mut test_vote_state).unwrap();
let bincode_res = bincode::deserialize::<VoteStateVersions>(&expanded_buf)
.map_err(|_| InstructionError::InvalidAccountData)
.and_then(|versioned| versioned.try_convert_to_v3());
let test_vote_state = unsafe { test_vote_state.assume_init() };
assert_eq!(test_vote_state, bincode_res.unwrap());
}
}
}
#[test]
fn test_deserialize_invalid_variant_tags() {
let mut buf = vec![0u8; VoteStateV3::size_of()];
let mut vs = VoteStateV3::default();
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
buf[..4].copy_from_slice(&3u32.to_le_bytes());
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
buf[..4].copy_from_slice(&4u32.to_le_bytes());
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
buf[..4].copy_from_slice(&u32::MAX.to_le_bytes());
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_invalid_option_bool_discriminants() {
let vote_state = VoteStateV3 {
root_slot: Some(42),
..VoteStateV3::default()
};
let valid_buf = bincode::serialize(&VoteStateVersions::new_v3(vote_state)).unwrap();
let root_slot_offset = 4 + 32 + 32 + 1 + 8;
assert_eq!(valid_buf[root_slot_offset], 1);
{
let mut buf = valid_buf.clone();
buf[root_slot_offset] = 2;
let mut vs = VoteStateV3::default();
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
}
let is_empty_offset = root_slot_offset + 1 + 8 + 8 + 32 * 48 + 8;
assert_eq!(valid_buf[is_empty_offset], 1);
{
let mut buf = valid_buf.clone();
buf[is_empty_offset] = 2;
let mut vs = VoteStateV3::default();
assert_eq!(
VoteStateV3::deserialize_into(&buf, &mut vs),
Err(InstructionError::InvalidAccountData)
);
}
}
#[test]
fn test_has_latency() {
let mut v1_state = VoteState1_14_11::default();
v1_state.votes.push_back(Lockout::new(100));
v1_state.votes.push_back(Lockout::new(200));
let buf = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new(v1_state))).unwrap();
let deserialized = VoteStateV3::deserialize(&buf).unwrap();
assert_eq!(deserialized.votes.len(), 2);
for vote in &deserialized.votes {
assert_eq!(vote.latency, 0);
}
let mut v3_state = VoteStateV3::default();
v3_state.votes.push_back(LandedVote {
latency: 42,
lockout: Lockout::new(100),
});
v3_state.votes.push_back(LandedVote {
latency: 7,
lockout: Lockout::new(200),
});
let buf = bincode::serialize(&VoteStateVersions::new_v3(v3_state)).unwrap();
let deserialized = VoteStateV3::deserialize(&buf).unwrap();
assert_eq!(deserialized.votes[0].latency, 42);
assert_eq!(deserialized.votes[1].latency, 7);
}
#[test]
fn test_empty_collections_round_trip() {
let vote_state = VoteStateV3 {
node_pubkey: Pubkey::new_unique(),
authorized_withdrawer: Pubkey::new_unique(),
commission: 50,
root_slot: Some(100),
..VoteStateV3::default()
};
let versioned = VoteStateVersions::new_v3(vote_state.clone());
let buf = bincode::serialize(&versioned).unwrap();
let deserialized = VoteStateV3::deserialize(&buf).unwrap();
assert_eq!(vote_state, deserialized);
}
}