use {
crate::{
authorized_voters::AuthorizedVoters,
state::{
BlockTimestamp, LandedVote, Lockout, VoteStateV3, VoteStateV4,
BLS_PUBLIC_KEY_COMPRESSED_SIZE, MAX_EPOCH_CREDITS_HISTORY, MAX_ITEMS,
MAX_LOCKOUT_HISTORY,
},
},
solana_clock::Epoch,
solana_instruction_error::InstructionError,
solana_pubkey::Pubkey,
solana_serialize_utils::cursor::{
read_bool, read_i64, read_option_u64, read_pubkey, read_pubkey_into, read_u16, read_u32,
read_u64, read_u8,
},
std::{
collections::VecDeque,
io::{BufRead, Cursor, Read},
ptr::addr_of_mut,
},
};
struct DropGuard<T: Default> {
vote_state: *mut T,
}
impl<T: Default> Drop for DropGuard<T> {
fn drop(&mut self) {
unsafe {
self.vote_state.write(T::default());
}
}
}
pub(crate) fn deserialize_into<T: Default>(
input: &[u8],
vote_state: &mut T,
deserialize_fn: impl FnOnce(&[u8], *mut T) -> Result<(), InstructionError>,
) -> Result<(), InstructionError> {
let vote_state = vote_state as *mut T;
unsafe {
std::ptr::drop_in_place(vote_state);
}
let guard = DropGuard { vote_state };
let res = deserialize_fn(input, vote_state);
if res.is_ok() {
std::mem::forget(guard);
}
res
}
pub(crate) fn deserialize_vote_state_into_v1_14_11(
cursor: &mut Cursor<&[u8]>,
vote_state: *mut crate::state::vote_state_1_14_11::VoteState1_14_11,
) -> Result<(), InstructionError> {
read_pubkey_into(
cursor,
unsafe { addr_of_mut!((*vote_state).node_pubkey) },
)?;
read_pubkey_into(
cursor,
unsafe { addr_of_mut!((*vote_state).authorized_withdrawer) },
)?;
let commission = read_u8(cursor)?;
let votes = read_votes_as_lockouts(cursor)?; let root_slot = read_option_u64(cursor)?;
let authorized_voters = read_authorized_voters(cursor)?;
read_prior_voters_into(
cursor,
unsafe { addr_of_mut!((*vote_state).prior_voters) },
)?;
let epoch_credits = read_epoch_credits(cursor)?;
let last_timestamp = read_last_timestamp(cursor)?;
unsafe {
addr_of_mut!((*vote_state).commission).write(commission);
addr_of_mut!((*vote_state).votes).write(votes);
addr_of_mut!((*vote_state).root_slot).write(root_slot);
addr_of_mut!((*vote_state).authorized_voters).write(authorized_voters);
addr_of_mut!((*vote_state).epoch_credits).write(epoch_credits);
addr_of_mut!((*vote_state).last_timestamp).write(last_timestamp);
}
Ok(())
}
pub(super) fn deserialize_vote_state_into_v3(
cursor: &mut Cursor<&[u8]>,
vote_state: *mut VoteStateV3,
has_latency: bool,
) -> Result<(), InstructionError> {
read_pubkey_into(
cursor,
unsafe { addr_of_mut!((*vote_state).node_pubkey) },
)?;
read_pubkey_into(
cursor,
unsafe { addr_of_mut!((*vote_state).authorized_withdrawer) },
)?;
let commission = read_u8(cursor)?;
let votes = read_votes(cursor, has_latency)?;
let root_slot = read_option_u64(cursor)?;
let authorized_voters = read_authorized_voters(cursor)?;
read_prior_voters_into(
cursor,
unsafe { addr_of_mut!((*vote_state).prior_voters) },
)?;
let epoch_credits = read_epoch_credits(cursor)?;
let last_timestamp = read_last_timestamp(cursor)?;
unsafe {
addr_of_mut!((*vote_state).commission).write(commission);
addr_of_mut!((*vote_state).votes).write(votes);
addr_of_mut!((*vote_state).root_slot).write(root_slot);
addr_of_mut!((*vote_state).authorized_voters).write(authorized_voters);
addr_of_mut!((*vote_state).epoch_credits).write(epoch_credits);
addr_of_mut!((*vote_state).last_timestamp).write(last_timestamp);
}
Ok(())
}
#[derive(PartialEq)]
pub(crate) enum SourceVersion<'a> {
V1_14_11 { vote_pubkey: &'a Pubkey },
V3 { vote_pubkey: &'a Pubkey },
V4,
}
pub(crate) fn deserialize_vote_state_into_v4<'a>(
cursor: &mut Cursor<&[u8]>,
vote_state: *mut VoteStateV4,
source_version: SourceVersion<'a>,
) -> Result<(), InstructionError> {
let node_pubkey = read_pubkey(cursor)?;
unsafe {
addr_of_mut!((*vote_state).node_pubkey).write(node_pubkey);
}
read_pubkey_into(
cursor,
unsafe { addr_of_mut!((*vote_state).authorized_withdrawer) },
)?;
let (
inflation_rewards_commission_bps,
block_revenue_commission_bps,
pending_delegator_rewards,
bls_pubkey_compressed,
) = match source_version {
SourceVersion::V4 => {
read_pubkey_into(cursor, unsafe {
addr_of_mut!((*vote_state).inflation_rewards_collector)
})?;
read_pubkey_into(cursor, unsafe {
addr_of_mut!((*vote_state).block_revenue_collector)
})?;
let inflation = read_u16(cursor)?;
let block = read_u16(cursor)?;
let pending = read_u64(cursor)?;
let bls_pubkey_compressed = read_option_bls_public_key_compressed(cursor)?;
(inflation, block, pending, bls_pubkey_compressed)
}
SourceVersion::V1_14_11 { vote_pubkey } | SourceVersion::V3 { vote_pubkey } => {
let commission = read_u8(cursor)?;
unsafe {
addr_of_mut!((*vote_state).inflation_rewards_collector).write(*vote_pubkey);
addr_of_mut!((*vote_state).block_revenue_collector).write(node_pubkey);
}
(
u16::from(commission).saturating_mul(100),
10_000u16,
0u64,
None,
)
}
};
let has_latency = !matches!(source_version, SourceVersion::V1_14_11 { .. });
let votes = read_votes(cursor, has_latency)?;
let root_slot = read_option_u64(cursor)?;
let authorized_voters = read_authorized_voters(cursor)?;
if !matches!(source_version, SourceVersion::V4) {
skip_prior_voters(cursor)?;
}
let epoch_credits = read_epoch_credits(cursor)?;
let last_timestamp = read_last_timestamp(cursor)?;
unsafe {
addr_of_mut!((*vote_state).inflation_rewards_commission_bps)
.write(inflation_rewards_commission_bps);
addr_of_mut!((*vote_state).block_revenue_commission_bps)
.write(block_revenue_commission_bps);
addr_of_mut!((*vote_state).pending_delegator_rewards).write(pending_delegator_rewards);
addr_of_mut!((*vote_state).bls_pubkey_compressed).write(bls_pubkey_compressed);
addr_of_mut!((*vote_state).votes).write(votes);
addr_of_mut!((*vote_state).root_slot).write(root_slot);
addr_of_mut!((*vote_state).authorized_voters).write(authorized_voters);
addr_of_mut!((*vote_state).epoch_credits).write(epoch_credits);
addr_of_mut!((*vote_state).last_timestamp).write(last_timestamp);
}
Ok(())
}
fn read_votes<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
has_latency: bool,
) -> Result<VecDeque<LandedVote>, InstructionError> {
let vote_count = read_u64(cursor)? as usize;
let mut votes = VecDeque::with_capacity(vote_count.min(MAX_LOCKOUT_HISTORY));
for _ in 0..vote_count {
let latency = if has_latency { read_u8(cursor)? } else { 0 };
let slot = read_u64(cursor)?;
let confirmation_count = read_u32(cursor)?;
let lockout = Lockout::new_with_confirmation_count(slot, confirmation_count);
votes.push_back(LandedVote { latency, lockout });
}
Ok(votes)
}
fn read_votes_as_lockouts<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
) -> Result<VecDeque<Lockout>, InstructionError> {
let vote_count = read_u64(cursor)? as usize;
let mut votes = VecDeque::with_capacity(vote_count.min(MAX_LOCKOUT_HISTORY));
for _ in 0..vote_count {
let slot = read_u64(cursor)?;
let confirmation_count = read_u32(cursor)?;
let lockout = Lockout::new_with_confirmation_count(slot, confirmation_count);
votes.push_back(lockout);
}
Ok(votes)
}
fn read_authorized_voters<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
) -> Result<AuthorizedVoters, InstructionError> {
let authorized_voter_count = read_u64(cursor)?;
let mut authorized_voters = AuthorizedVoters::default();
for _ in 0..authorized_voter_count {
let epoch = read_u64(cursor)?;
let authorized_voter = read_pubkey(cursor)?;
authorized_voters.insert(epoch, authorized_voter);
}
Ok(authorized_voters)
}
fn read_prior_voters_into<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
prior_voters: *mut crate::state::CircBuf<(Pubkey, Epoch, Epoch)>,
) -> Result<(), InstructionError> {
unsafe {
let prior_voters_buf = addr_of_mut!((*prior_voters).buf) as *mut (Pubkey, Epoch, Epoch);
for i in 0..MAX_ITEMS {
let prior_voter = read_pubkey(cursor)?;
let from_epoch = read_u64(cursor)?;
let until_epoch = read_u64(cursor)?;
prior_voters_buf
.add(i)
.write((prior_voter, from_epoch, until_epoch));
}
(*prior_voters).idx = read_u64(cursor)? as usize;
(*prior_voters).is_empty = read_bool(cursor)?;
}
Ok(())
}
fn skip_prior_voters<T: AsRef<[u8]>>(cursor: &mut Cursor<T>) -> Result<(), InstructionError> {
const PRIOR_VOTERS_SIZE: usize = MAX_ITEMS * core::mem::size_of::<(Pubkey, Epoch, Epoch)>() +
core::mem::size_of::<u64>() +
core::mem::size_of::<bool>() ;
cursor.consume(PRIOR_VOTERS_SIZE);
let bytes = cursor.get_ref().as_ref();
if cursor.position() as usize > bytes.len() {
return Err(InstructionError::InvalidAccountData);
}
Ok(())
}
fn read_option_bls_public_key_compressed<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
) -> Result<Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, InstructionError> {
let variant = read_u8(cursor)?;
match variant {
0 => Ok(None),
1 => {
let mut buf = [0; BLS_PUBLIC_KEY_COMPRESSED_SIZE];
cursor
.read_exact(&mut buf)
.map_err(|_| InstructionError::InvalidAccountData)?;
Ok(Some(buf))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn read_epoch_credits<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
) -> Result<Vec<(Epoch, u64, u64)>, InstructionError> {
let epoch_credit_count = read_u64(cursor)? as usize;
let mut epoch_credits = Vec::with_capacity(epoch_credit_count.min(MAX_EPOCH_CREDITS_HISTORY));
for _ in 0..epoch_credit_count {
let epoch = read_u64(cursor)?;
let credits = read_u64(cursor)?;
let prev_credits = read_u64(cursor)?;
epoch_credits.push((epoch, credits, prev_credits));
}
Ok(epoch_credits)
}
fn read_last_timestamp<T: AsRef<[u8]>>(
cursor: &mut Cursor<T>,
) -> Result<BlockTimestamp, InstructionError> {
let slot = read_u64(cursor)?;
let timestamp = read_i64(cursor)?;
Ok(BlockTimestamp { slot, timestamp })
}
#[cfg(test)]
mod tests {
use {super::*, crate::state::CircBuf};
const PRIOR_VOTERS_SIZE: usize = MAX_ITEMS * core::mem::size_of::<(Pubkey, Epoch, Epoch)>() +
core::mem::size_of::<u64>() +
core::mem::size_of::<bool>() ;
#[test]
fn test_skip_prior_voters_success() {
let buffer = vec![0u8; PRIOR_VOTERS_SIZE];
let mut cursor = Cursor::new(&buffer[..]);
let result = skip_prior_voters(&mut cursor);
assert!(result.is_ok());
assert_eq!(cursor.position() as usize, PRIOR_VOTERS_SIZE);
}
#[test]
fn test_skip_prior_voters_success_with_offset() {
let offset = 100;
let buffer = vec![0u8; PRIOR_VOTERS_SIZE + offset];
let mut cursor = Cursor::new(&buffer[..]);
cursor.set_position(offset as u64);
let result = skip_prior_voters(&mut cursor);
assert!(result.is_ok());
assert_eq!(cursor.position() as usize, PRIOR_VOTERS_SIZE + offset);
}
#[test]
fn test_skip_prior_voters_buffer_too_small() {
let buffer = vec![0u8; PRIOR_VOTERS_SIZE - 1];
let mut cursor = Cursor::new(&buffer[..]);
let result = skip_prior_voters(&mut cursor);
assert_eq!(result, Err(InstructionError::InvalidAccountData));
}
#[test]
fn test_skip_prior_voters_insufficient_remaining() {
let buffer = vec![0u8; PRIOR_VOTERS_SIZE + 100];
let mut cursor = Cursor::new(&buffer[..]);
cursor.set_position(101);
let result = skip_prior_voters(&mut cursor);
assert_eq!(result, Err(InstructionError::InvalidAccountData));
}
#[allow(clippy::arithmetic_side_effects)]
fn build_prior_voters_buffer(num_entries: usize) -> Vec<u8> {
let mut buffer = Vec::new();
for i in 0..num_entries {
let mut pubkey_bytes = [0u8; 32];
pubkey_bytes[0] = (i as u8).wrapping_add(1);
buffer.extend_from_slice(&pubkey_bytes);
buffer.extend_from_slice(&(i as u64 + 1).to_le_bytes());
buffer.extend_from_slice(&((i as u64 + 1) * 100).to_le_bytes());
}
buffer
}
#[allow(clippy::arithmetic_side_effects)]
fn expected_entry(i: usize) -> (Pubkey, Epoch, Epoch) {
let mut pubkey_bytes = [0u8; 32];
pubkey_bytes[0] = (i as u8).wrapping_add(1);
(
Pubkey::new_from_array(pubkey_bytes),
i as u64 + 1,
(i as u64 + 1) * 100,
)
}
#[test]
fn test_read_prior_voters_into() {
let mut buffer = build_prior_voters_buffer(MAX_ITEMS);
buffer.extend_from_slice(&5u64.to_le_bytes()); buffer.push(0); assert_eq!(buffer.len(), PRIOR_VOTERS_SIZE);
let mut cursor = Cursor::new(&buffer[..]);
let mut prior_voters = CircBuf::<(Pubkey, Epoch, Epoch)>::default();
let result = read_prior_voters_into(&mut cursor, &mut prior_voters as *mut _);
assert!(result.is_ok());
for i in 0..MAX_ITEMS {
assert_eq!(prior_voters.buf()[i], expected_entry(i));
}
assert_eq!(prior_voters.last(), Some(&expected_entry(5)));
}
#[test]
fn test_read_prior_voters_into_partial_failure() {
let entries_written = 5;
let mut buffer = build_prior_voters_buffer(entries_written);
buffer.extend_from_slice(&[0xFF; 16]);
let mut cursor = Cursor::new(&buffer[..]);
let mut prior_voters = CircBuf::<(Pubkey, Epoch, Epoch)>::default();
let result = read_prior_voters_into(&mut cursor, &mut prior_voters as *mut _);
assert_eq!(result, Err(InstructionError::InvalidAccountData));
for i in 0..entries_written {
assert_eq!(prior_voters.buf()[i], expected_entry(i));
}
for i in entries_written..MAX_ITEMS {
assert_eq!(prior_voters.buf()[i], (Pubkey::default(), 0u64, 0u64));
}
assert_eq!(prior_voters.last(), None);
}
}