use {
super::{
field_frames::{BlsPubkeyCompressedFrame, LandedVotesListFrame, ListFrame},
AuthorizedVotersListFrame, EpochCreditsListFrame, Field, Result, RootSlotFrame,
Simd185Field, VoteStateViewError,
},
atlas_pubkey::Pubkey,
atlas_vote_interface::state::BlockTimestamp,
std::io::BufRead,
};
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
pub(crate) struct VoteStateFrameV4 {
pub(super) bls_pubkey_compressed_frame: BlsPubkeyCompressedFrame,
pub(super) votes_frame: LandedVotesListFrame,
pub(super) root_slot_frame: RootSlotFrame,
pub(super) authorized_voters_frame: AuthorizedVotersListFrame,
pub(super) epoch_credits_frame: EpochCreditsListFrame,
}
impl VoteStateFrameV4 {
pub(crate) fn try_new(bytes: &[u8]) -> Result<Self> {
let bls_pubkey_offset = Self::bls_pubkey_compressed_offset();
let mut cursor = std::io::Cursor::new(bytes);
cursor.set_position(bls_pubkey_offset as u64);
let bls_pubkey_compressed_frame = BlsPubkeyCompressedFrame::read(&mut cursor)?;
let votes_frame = LandedVotesListFrame::read(&mut cursor)?;
let root_slot_frame = RootSlotFrame::read(&mut cursor)?;
let authorized_voters_frame = AuthorizedVotersListFrame::read(&mut cursor)?;
let epoch_credits_frame = EpochCreditsListFrame::read(&mut cursor)?;
cursor.consume(core::mem::size_of::<BlockTimestamp>());
if cursor.position() as usize <= bytes.len() {
Ok(Self {
bls_pubkey_compressed_frame,
votes_frame,
root_slot_frame,
authorized_voters_frame,
epoch_credits_frame,
})
} else {
Err(VoteStateViewError::AccountDataTooSmall)
}
}
pub(super) fn field_offset(&self, field: Field) -> usize {
match field {
Field::NodePubkey => Self::node_pubkey_offset(),
Field::Commission => Self::inflation_rewards_commission_offset(),
Field::Votes => self.votes_offset(),
Field::RootSlot => self.root_slot_offset(),
Field::AuthorizedVoters => self.authorized_voters_offset(),
Field::EpochCredits => self.epoch_credits_offset(),
Field::LastTimestamp => self.last_timestamp_offset(),
}
}
pub(super) fn simd185_field_offset(&self, field: Simd185Field) -> usize {
match field {
Simd185Field::InflationRewardsCollector => Self::inflation_rewards_collector_offset(),
Simd185Field::BlockRevenueCollector => Self::block_revenue_collector_offset(),
Simd185Field::BlockRevenueCommission => Self::block_revenue_commission_offset(),
Simd185Field::PendingDelegatorRewards => Self::pending_delegator_rewards_offset(),
Simd185Field::BlsPubkeyCompressed => Self::bls_pubkey_compressed_offset(),
}
}
const fn node_pubkey_offset() -> usize {
core::mem::size_of::<u32>() }
const fn authorized_withdrawer_offset() -> usize {
Self::node_pubkey_offset() + core::mem::size_of::<Pubkey>()
}
const fn inflation_rewards_collector_offset() -> usize {
Self::authorized_withdrawer_offset() + core::mem::size_of::<Pubkey>()
}
const fn block_revenue_collector_offset() -> usize {
Self::inflation_rewards_collector_offset() + core::mem::size_of::<Pubkey>()
}
const fn inflation_rewards_commission_offset() -> usize {
Self::block_revenue_collector_offset() + core::mem::size_of::<Pubkey>()
}
const fn block_revenue_commission_offset() -> usize {
Self::inflation_rewards_commission_offset() + core::mem::size_of::<u16>()
}
const fn pending_delegator_rewards_offset() -> usize {
Self::block_revenue_commission_offset() + core::mem::size_of::<u16>()
}
const fn bls_pubkey_compressed_offset() -> usize {
Self::pending_delegator_rewards_offset() + core::mem::size_of::<u64>()
}
fn votes_offset(&self) -> usize {
Self::bls_pubkey_compressed_offset() + self.bls_pubkey_compressed_frame.total_size()
}
fn root_slot_offset(&self) -> usize {
self.votes_offset() + self.votes_frame.total_size()
}
fn authorized_voters_offset(&self) -> usize {
self.root_slot_offset() + self.root_slot_frame.total_size()
}
fn epoch_credits_offset(&self) -> usize {
self.authorized_voters_offset() + self.authorized_voters_frame.total_size()
}
fn last_timestamp_offset(&self) -> usize {
self.epoch_credits_offset() + self.epoch_credits_frame.total_size()
}
}
#[cfg(test)]
mod tests {
use {
super::*,
atlas_vote_interface::{
authorized_voters::AuthorizedVoters,
state::{LandedVote, Lockout, VoteStateV4, BLS_PUBLIC_KEY_COMPRESSED_SIZE},
},
std::collections::VecDeque,
};
#[derive(Debug, Clone, Deserialize, Serialize)]
enum TestVoteStateVersions {
V0_23_5,
V1_14_11,
V3,
V4(VoteStateV4),
}
#[test]
fn test_try_new_zeroed() {
let target_vote_state = VoteStateV4::default();
let target_vote_state_versions = TestVoteStateVersions::V4(target_vote_state);
let mut bytes = bincode::serialize(&target_vote_state_versions).unwrap();
for i in 0..bytes.len() {
let vote_state_frame = VoteStateFrameV4::try_new(&bytes[..i]);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::AccountDataTooSmall)
);
}
for has_trailing_bytes in [false, true] {
if has_trailing_bytes {
bytes.extend_from_slice(&[0; 42]);
}
assert_eq!(
VoteStateFrameV4::try_new(&bytes),
Ok(VoteStateFrameV4 {
bls_pubkey_compressed_frame: BlsPubkeyCompressedFrame { has_pubkey: false },
votes_frame: LandedVotesListFrame { len: 0 },
root_slot_frame: RootSlotFrame {
has_root_slot: false,
},
authorized_voters_frame: AuthorizedVotersListFrame { len: 0 },
epoch_credits_frame: EpochCreditsListFrame { len: 0 },
})
);
}
}
#[test]
fn test_try_new_simple() {
let target_vote_state = VoteStateV4 {
authorized_voters: AuthorizedVoters::new(0, Pubkey::default()),
epoch_credits: vec![(1, 2, 3)],
bls_pubkey_compressed: Some([42; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
votes: VecDeque::from([LandedVote {
latency: 0,
lockout: Lockout::default(),
}]),
root_slot: Some(42),
..VoteStateV4::default()
};
let target_vote_state_versions = TestVoteStateVersions::V4(target_vote_state);
let mut bytes = bincode::serialize(&target_vote_state_versions).unwrap();
for i in 0..bytes.len() {
let vote_state_frame = VoteStateFrameV4::try_new(&bytes[..i]);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::AccountDataTooSmall)
);
}
for has_trailing_bytes in [false, true] {
if has_trailing_bytes {
bytes.extend_from_slice(&[0; 42]);
}
assert_eq!(
VoteStateFrameV4::try_new(&bytes),
Ok(VoteStateFrameV4 {
bls_pubkey_compressed_frame: BlsPubkeyCompressedFrame { has_pubkey: true },
votes_frame: LandedVotesListFrame { len: 1 },
root_slot_frame: RootSlotFrame {
has_root_slot: true,
},
authorized_voters_frame: AuthorizedVotersListFrame { len: 1 },
epoch_credits_frame: EpochCreditsListFrame { len: 1 },
})
);
}
}
#[test]
fn test_try_new_invalid_values() {
let mut bytes = vec![0; VoteStateFrameV4::bls_pubkey_compressed_offset()];
{
let mut bytes = bytes.clone();
bytes.extend_from_slice(&(2u8.to_le_bytes()));
let vote_state_frame = VoteStateFrameV4::try_new(&bytes);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::InvalidBlsPubkeyCompressedOption)
);
}
bytes.extend_from_slice(&[0; 1]);
{
let mut bytes = bytes.clone();
bytes.extend_from_slice(&(256u64.to_le_bytes()));
let vote_state_frame = VoteStateFrameV4::try_new(&bytes);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::InvalidVotesLength)
);
}
bytes.extend_from_slice(&[0; core::mem::size_of::<u64>()]);
{
let mut bytes = bytes.clone();
bytes.extend_from_slice(&(2u8.to_le_bytes()));
let vote_state_frame = VoteStateFrameV4::try_new(&bytes);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::InvalidRootSlotOption)
);
}
bytes.extend_from_slice(&[0; 1]);
{
let mut bytes = bytes.clone();
bytes.extend_from_slice(&(256u64.to_le_bytes()));
let vote_state_frame = VoteStateFrameV4::try_new(&bytes);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::InvalidAuthorizedVotersLength)
);
}
bytes.extend_from_slice(&[0; core::mem::size_of::<u64>()]);
{
let mut bytes = bytes.clone();
bytes.extend_from_slice(&(256u64.to_le_bytes()));
let vote_state_frame = VoteStateFrameV4::try_new(&bytes);
assert_eq!(
vote_state_frame,
Err(VoteStateViewError::InvalidEpochCreditsLength)
);
}
}
}