From 65f77e4f2bfe3f7f209656d49419010d8e3a139b Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Thu, 1 Feb 2024 03:06:59 -0800 Subject: [PATCH 1/4] solana-program: make VoteState::deserialize_into() much faster --- sdk/program/src/vote/state/mod.rs | 6 ----- .../src/vote/state/vote_state_deserialize.rs | 25 +++++++++++++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index 1342fb9deea4a3..a97f1777d2b543 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -500,12 +500,6 @@ impl VoteState { input: &[u8], vote_state: &mut VoteState, ) -> Result<(), InstructionError> { - let minimum_size = - serialized_size(vote_state).map_err(|_| InstructionError::InvalidAccountData)?; - if (input.len() as u64) < minimum_size { - return Err(InstructionError::InvalidAccountData); - } - let mut cursor = Cursor::new(input); let variant = read_u32(&mut cursor)?; diff --git a/sdk/program/src/vote/state/vote_state_deserialize.rs b/sdk/program/src/vote/state/vote_state_deserialize.rs index b457395ccbd38a..3694f25af37564 100644 --- a/sdk/program/src/vote/state/vote_state_deserialize.rs +++ b/sdk/program/src/vote/state/vote_state_deserialize.rs @@ -5,10 +5,13 @@ use { serialize_utils::cursor::*, vote::state::{BlockTimestamp, LandedVote, Lockout, VoteState, MAX_ITEMS}, }, - bincode::serialized_size, std::io::Cursor, }; +// hardcode this number to avoid calculating onchain; this is a fixed-size ringbuffer +// `serialized_size()` must be used over `mem::size_of()` because of alignment +const PRIOR_VOTERS_SERIALIZED_SIZE: u64 = 1545; + pub(super) fn deserialize_vote_state_into( cursor: &mut Cursor<&[u8]>, vote_state: &mut VoteState, @@ -70,10 +73,8 @@ fn read_prior_voters_into>( // record our position at the start of the struct let prior_voters_position = cursor.position(); - // `serialized_size()` must be used over `mem::size_of()` because of alignment - let is_empty_position = serialized_size(&vote_state.prior_voters) - .ok() - .and_then(|v| v.checked_add(prior_voters_position)) + let is_empty_position = PRIOR_VOTERS_SERIALIZED_SIZE + .checked_add(prior_voters_position) .and_then(|v| v.checked_sub(1)) .ok_or(InstructionError::InvalidAccountData)?; @@ -140,3 +141,17 @@ fn read_last_timestamp_into>( Ok(()) } + +#[cfg(test)] +mod tests { + use {super::*, bincode::serialized_size}; + + #[test] + fn test_prior_voters_serialized_size() { + let vote_state = VoteState::default(); + assert_eq!( + serialized_size(&vote_state.prior_voters).unwrap(), + PRIOR_VOTERS_SERIALIZED_SIZE + ); + } +} From 9af1a0f12a7e8f6aedfd42d648ad4cbf3a96b6c3 Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Thu, 1 Feb 2024 03:21:22 -0800 Subject: [PATCH 2/4] solana-program: use custom VoteState deserialize in non-bpf --- account-decoder/src/parse_vote.rs | 3 +- programs/vote/src/vote_state/mod.rs | 2 +- runtime/src/bank.rs | 2 +- sdk/program/src/vote/state/mod.rs | 68 +++++++++++++++++------------ vote/src/vote_account.rs | 6 ++- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/account-decoder/src/parse_vote.rs b/account-decoder/src/parse_vote.rs index 7f7831f2f3ff48..c1310f8be00339 100644 --- a/account-decoder/src/parse_vote.rs +++ b/account-decoder/src/parse_vote.rs @@ -8,7 +8,8 @@ use { }; pub fn parse_vote(data: &[u8]) -> Result { - let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?; + let mut vote_state = + VoteState::deserialize_with_bincode(data).map_err(ParseAccountError::from)?; let epoch_credits = vote_state .epoch_credits() .iter() diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 8d8ec0cf24b255..6873092b59dfc6 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -153,7 +153,7 @@ impl From for VoteTransaction { // utility function, used by Stakes, tests pub fn from(account: &T) -> Option { - VoteState::deserialize(account.data()).ok() + VoteState::deserialize_with_bincode(account.data()).ok() } // utility function, used by Stakes, tests diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index b73a16696f8fcf..f07505cc13a239 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2318,7 +2318,7 @@ impl Bank { // vote_accounts_cache_miss_count is shown to be always zero. let account = self.get_account_with_fixed_root(vote_pubkey)?; if account.owner() == &solana_vote_program - && VoteState::deserialize(account.data()).is_ok() + && VoteState::deserialize_with_bincode(account.data()).is_ok() { vote_accounts_cache_miss_count.fetch_add(1, Relaxed); } diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index a97f1777d2b543..3697110c66223c 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -1,7 +1,5 @@ //! Vote state -#[cfg(not(target_os = "solana"))] -use bincode::deserialize; #[cfg(test)] use { crate::epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET, @@ -18,7 +16,7 @@ use { sysvar::clock::Clock, vote::{authorized_voters::AuthorizedVoters, error::VoteError}, }, - bincode::{serialize_into, serialized_size, ErrorKind}, + bincode::{serialize_into, ErrorKind}, serde_derive::{Deserialize, Serialize}, std::{collections::VecDeque, fmt::Debug, io::Cursor}, }; @@ -475,27 +473,33 @@ impl VoteState { 3762 // see test_vote_state_size_of. } - // we retain bincode deserialize for not(target_os = "solana") - // because the hand-written parser does not support V0_23_5 + /// Deserializes the input buffer into a newly allocated `VoteState` + /// + /// This function is intended as a drop-in replacement for `bincode::deserialize()`. + /// V0_23_5 is not supported in a BPF context, but all versions are supported on non-BPF. pub fn deserialize(input: &[u8]) -> Result { + let mut vote_state = Self::default(); + Self::deserialize_into(input, &mut vote_state)?; + Ok(vote_state) + } + + // this only exists for the sake of the feature gated upgrade to the new parser; do not use it + #[doc(hidden)] + #[allow(clippy::used_underscore_binding)] + pub fn deserialize_with_bincode(_input: &[u8]) -> Result { #[cfg(not(target_os = "solana"))] { - deserialize::(input) + bincode::deserialize::(_input) .map(|versioned| versioned.convert_to_current()) .map_err(|_| InstructionError::InvalidAccountData) } #[cfg(target_os = "solana")] - { - let mut vote_state = Self::default(); - Self::deserialize_into(input, &mut vote_state)?; - Ok(vote_state) - } + unimplemented!() } /// Deserializes the input buffer into the provided `VoteState` /// - /// This function exists to deserialize `VoteState` in a BPF context without going above - /// the compute limit, and must be kept up to date with `bincode::deserialize`. + /// This function is exposed to allow deserialization in a BPF context directly into boxed memory. pub fn deserialize_into( input: &[u8], vote_state: &mut VoteState, @@ -504,8 +508,20 @@ impl VoteState { let variant = read_u32(&mut cursor)?; match variant { - // V0_23_5. not supported; these should not exist on mainnet - 0 => Err(InstructionError::InvalidAccountData), + // V0_23_5. not supported for bpf targets; these should not exist on mainnet + // supported for non-bpf targets for backwards compatibility + 0 => { + #[cfg(not(target_os = "solana"))] + { + *vote_state = bincode::deserialize::(input) + .map(|versioned| versioned.convert_to_current()) + .map_err(|_| InstructionError::InvalidAccountData)?; + + Ok(()) + } + #[cfg(target_os = "solana")] + Err(InstructionError::InvalidAccountData) + } // V1_14_11. substantially different layout and data from V0_23_5 1 => deserialize_vote_state_into(&mut cursor, vote_state, false), // Current. the only difference from V1_14_11 is the addition of a slot-latency to each vote @@ -513,6 +529,8 @@ impl VoteState { _ => Err(InstructionError::InvalidAccountData), }?; + // if cursor overruns the input, it produces 0 values and continues to advance `position` + // this check ensures we do not accept such a malformed input erroneously if cursor.position() > input.len() as u64 { return Err(InstructionError::InvalidAccountData); } @@ -1081,7 +1099,7 @@ pub mod serde_tower_sync { #[cfg(test)] mod tests { - use {super::*, itertools::Itertools, rand::Rng}; + use {super::*, bincode::serialized_size, itertools::Itertools, rand::Rng}; #[test] fn test_vote_serialize() { @@ -1101,14 +1119,13 @@ mod tests { } #[test] - fn test_vote_deserialize_into() { + fn test_vote_deserialize() { // base case let target_vote_state = VoteState::default(); let vote_state_buf = bincode::serialize(&VoteStateVersions::new_current(target_vote_state.clone())).unwrap(); - let mut test_vote_state = VoteState::default(); - VoteState::deserialize_into(&vote_state_buf, &mut test_vote_state).unwrap(); + let test_vote_state = VoteState::deserialize(&vote_state_buf).unwrap(); assert_eq!(target_vote_state, test_vote_state); @@ -1124,22 +1141,20 @@ mod tests { let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap(); let target_vote_state = target_vote_state_versions.convert_to_current(); - let mut test_vote_state = VoteState::default(); - VoteState::deserialize_into(&vote_state_buf, &mut test_vote_state).unwrap(); + let test_vote_state = VoteState::deserialize(&vote_state_buf).unwrap(); assert_eq!(target_vote_state, test_vote_state); } } #[test] - fn test_vote_deserialize_into_nopanic() { + fn test_vote_deserialize_nopanic() { // base case - let mut test_vote_state = VoteState::default(); - let e = VoteState::deserialize_into(&[], &mut test_vote_state).unwrap_err(); + let e = VoteState::deserialize(&[]).unwrap_err(); assert_eq!(e, InstructionError::InvalidAccountData); // variant - let serialized_len_x4 = serialized_size(&test_vote_state).unwrap() * 4; + let serialized_len_x4 = serialized_size(&VoteState::default()).unwrap() * 4; let mut rng = rand::thread_rng(); for _ in 0..1000 { let raw_data_length = rng.gen_range(1..serialized_len_x4); @@ -1147,8 +1162,7 @@ mod tests { // it is extremely improbable, though theoretically possible, for random bytes to be syntactically valid // so we only check that the deserialize function does not panic - let mut test_vote_state = VoteState::default(); - let _ = VoteState::deserialize_into(&raw_data, &mut test_vote_state); + let _ = VoteState::deserialize(&raw_data); } } diff --git a/vote/src/vote_account.rs b/vote/src/vote_account.rs index 11df8c734d1c7b..289921a2669caf 100644 --- a/vote/src/vote_account.rs +++ b/vote/src/vote_account.rs @@ -68,11 +68,13 @@ impl VoteAccount { } pub fn vote_state(&self) -> Result<&VoteState, &Error> { - // VoteState::deserialize deserializes a VoteStateVersions and then + // VoteState::deserialize_with_bincode deserializes a VoteStateVersions and then // calls VoteStateVersions::convert_to_current. self.0 .vote_state - .get_or_init(|| VoteState::deserialize(self.0.account.data()).map_err(Error::from)) + .get_or_init(|| { + VoteState::deserialize_with_bincode(self.0.account.data()).map_err(Error::from) + }) .as_ref() } From b9c81b9f307fa0f0ea2308bc42d0acfc60c3839b Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Thu, 1 Feb 2024 03:35:19 -0800 Subject: [PATCH 3/4] solana-program: test new codepath for V0_23_5 deserialize --- .../src/vote/state/vote_state_0_23_5.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sdk/program/src/vote/state/vote_state_0_23_5.rs b/sdk/program/src/vote/state/vote_state_0_23_5.rs index ae3b9207fe494e..18756ac31ea17b 100644 --- a/sdk/program/src/vote/state/vote_state_0_23_5.rs +++ b/sdk/program/src/vote/state/vote_state_0_23_5.rs @@ -1,9 +1,12 @@ #![allow(clippy::arithmetic_side_effects)] use super::*; +#[cfg(test)] +use arbitrary::{Arbitrary, Unstructured}; const MAX_ITEMS: usize = 32; #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[cfg_attr(test, derive(Arbitrary))] pub struct VoteState0_23_5 { /// the node that votes in this account pub node_pubkey: Pubkey, @@ -59,3 +62,59 @@ impl CircBuf { self.buf[self.idx] = item; } } + +#[cfg(test)] +impl<'a, I: Default + Copy> Arbitrary<'a> for CircBuf +where + I: Arbitrary<'a>, +{ + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let mut circbuf = Self::default(); + + let len = u.arbitrary_len::()?; + for _ in 0..len { + circbuf.append(I::arbitrary(u)?); + } + + Ok(circbuf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vote_deserialize_0_23_5() { + // base case + let target_vote_state = VoteState0_23_5::default(); + let target_vote_state_versions = VoteStateVersions::V0_23_5(Box::new(target_vote_state)); + let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap(); + + let test_vote_state = VoteState::deserialize(&vote_state_buf).unwrap(); + + assert_eq!( + target_vote_state_versions.convert_to_current(), + test_vote_state + ); + + // variant + // provide 4x the minimum struct size in bytes to ensure we typically touch every field + let struct_bytes_x4 = std::mem::size_of::() * 4; + for _ in 0..100 { + let raw_data: Vec = (0..struct_bytes_x4).map(|_| rand::random::()).collect(); + let mut unstructured = Unstructured::new(&raw_data); + + let arbitrary_vote_state = VoteState0_23_5::arbitrary(&mut unstructured).unwrap(); + let target_vote_state_versions = + VoteStateVersions::V0_23_5(Box::new(arbitrary_vote_state)); + + let vote_state_buf = bincode::serialize(&target_vote_state_versions).unwrap(); + let target_vote_state = target_vote_state_versions.convert_to_current(); + + let test_vote_state = VoteState::deserialize(&vote_state_buf).unwrap(); + + assert_eq!(target_vote_state, test_vote_state); + } + } +} From cd9ec155581eb08c7d565812e8f6929ca567983c Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Mon, 5 Feb 2024 04:07:19 -0800 Subject: [PATCH 4/4] use VoteState::deserialize() everywhere --- account-decoder/src/parse_vote.rs | 3 +- programs/vote/src/vote_state/mod.rs | 32 ++++++----------- runtime/src/bank.rs | 2 +- sdk/program/src/vote/authorized_voters.rs | 5 +++ sdk/program/src/vote/state/mod.rs | 36 ++++++++++--------- .../src/vote/state/vote_state_1_14_11.rs | 4 +++ .../src/vote/state/vote_state_deserialize.rs | 14 ++------ .../src/vote/state/vote_state_versions.rs | 4 +-- vote/src/vote_account.rs | 7 ++-- 9 files changed, 47 insertions(+), 60 deletions(-) diff --git a/account-decoder/src/parse_vote.rs b/account-decoder/src/parse_vote.rs index c1310f8be00339..7f7831f2f3ff48 100644 --- a/account-decoder/src/parse_vote.rs +++ b/account-decoder/src/parse_vote.rs @@ -8,8 +8,7 @@ use { }; pub fn parse_vote(data: &[u8]) -> Result { - let mut vote_state = - VoteState::deserialize_with_bincode(data).map_err(ParseAccountError::from)?; + let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?; let epoch_credits = vote_state .epoch_credits() .iter() diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 6873092b59dfc6..d0eacbe555ca6f 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -153,7 +153,7 @@ impl From for VoteTransaction { // utility function, used by Stakes, tests pub fn from(account: &T) -> Option { - VoteState::deserialize_with_bincode(account.data()).ok() + VoteState::deserialize(account.data()).ok() } // utility function, used by Stakes, tests @@ -854,9 +854,7 @@ pub fn authorize( clock: &Clock, feature_set: &FeatureSet, ) -> Result<(), InstructionError> { - let mut vote_state: VoteState = vote_account - .get_state::()? - .convert_to_current(); + let mut vote_state = VoteState::deserialize(vote_account.get_data())?; match vote_authorize { VoteAuthorize::Voter => { @@ -897,9 +895,7 @@ pub fn update_validator_identity( signers: &HashSet, feature_set: &FeatureSet, ) -> Result<(), InstructionError> { - let mut vote_state: VoteState = vote_account - .get_state::()? - .convert_to_current(); + let mut vote_state = VoteState::deserialize(vote_account.get_data())?; // current authorized withdrawer must say "yay" verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; @@ -926,8 +922,8 @@ pub fn update_commission( let enforce_commission_update_rule = if feature_set.is_active(&feature_set::allow_commission_decrease_at_any_time::id()) { - if let Ok(decoded_vote_state) = vote_account.get_state::() { - vote_state = Some(decoded_vote_state.convert_to_current()); + if let Ok(decoded_vote_state) = VoteState::deserialize(vote_account.get_data()) { + vote_state = Some(decoded_vote_state); is_commission_increase(vote_state.as_ref().unwrap(), commission) } else { true @@ -948,9 +944,7 @@ pub fn update_commission( let mut vote_state = match vote_state { Some(vote_state) => vote_state, - None => vote_account - .get_state::()? - .convert_to_current(), + None => VoteState::deserialize(vote_account.get_data())?, }; // current authorized withdrawer must say "yay" @@ -1007,9 +1001,7 @@ pub fn withdraw( ) -> Result<(), InstructionError> { let mut vote_account = instruction_context .try_borrow_instruction_account(transaction_context, vote_account_index)?; - let vote_state: VoteState = vote_account - .get_state::()? - .convert_to_current(); + let vote_state = VoteState::deserialize(vote_account.get_data())?; verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; @@ -1071,9 +1063,9 @@ pub fn initialize_account( { return Err(InstructionError::InvalidAccountData); } - let versioned = vote_account.get_state::()?; - if !versioned.is_uninitialized() { + let vote_state = VoteState::deserialize(vote_account.get_data())?; + if !vote_state.is_uninitialized() { return Err(InstructionError::AccountAlreadyInitialized); } @@ -1088,13 +1080,11 @@ fn verify_and_get_vote_state( clock: &Clock, signers: &HashSet, ) -> Result { - let versioned = vote_account.get_state::()?; - - if versioned.is_uninitialized() { + let mut vote_state = VoteState::deserialize(vote_account.get_data())?; + if vote_state.is_uninitialized() { return Err(InstructionError::UninitializedAccount); } - let mut vote_state = versioned.convert_to_current(); let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?; verify_authorized_signer(&authorized_voter, signers)?; diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f07505cc13a239..b73a16696f8fcf 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2318,7 +2318,7 @@ impl Bank { // vote_accounts_cache_miss_count is shown to be always zero. let account = self.get_account_with_fixed_root(vote_pubkey)?; if account.owner() == &solana_vote_program - && VoteState::deserialize_with_bincode(account.data()).is_ok() + && VoteState::deserialize(account.data()).is_ok() { vote_accounts_cache_miss_count.fetch_add(1, Relaxed); } diff --git a/sdk/program/src/vote/authorized_voters.rs b/sdk/program/src/vote/authorized_voters.rs index 773dfa70c690de..ba9254ea1b85f1 100644 --- a/sdk/program/src/vote/authorized_voters.rs +++ b/sdk/program/src/vote/authorized_voters.rs @@ -65,6 +65,11 @@ impl AuthorizedVoters { self.authorized_voters.is_empty() } + // when an uninitialized V0_23_5 account is converted to current, it inserts a null voter + pub fn is_uninitialized(&self) -> bool { + self.is_empty() || (self.len() == 1 && self.first() == Some((&0, &Pubkey::default()))) + } + pub fn first(&self) -> Option<(&u64, &Pubkey)> { self.authorized_voters.iter().next() } diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index 3697110c66223c..8508465ebfed65 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -483,20 +483,6 @@ impl VoteState { Ok(vote_state) } - // this only exists for the sake of the feature gated upgrade to the new parser; do not use it - #[doc(hidden)] - #[allow(clippy::used_underscore_binding)] - pub fn deserialize_with_bincode(_input: &[u8]) -> Result { - #[cfg(not(target_os = "solana"))] - { - bincode::deserialize::(_input) - .map(|versioned| versioned.convert_to_current()) - .map_err(|_| InstructionError::InvalidAccountData) - } - #[cfg(target_os = "solana")] - unimplemented!() - } - /// Deserializes the input buffer into the provided `VoteState` /// /// This function is exposed to allow deserialization in a BPF context directly into boxed memory. @@ -900,6 +886,10 @@ impl VoteState { data.len() == VoteState::size_of() && data[VERSION_OFFSET..DEFAULT_PRIOR_VOTERS_END] != [0; DEFAULT_PRIOR_VOTERS_OFFSET] } + + pub fn is_uninitialized(&self) -> bool { + self.authorized_voters.is_uninitialized() + } } pub mod serde_compact_vote_state_update { @@ -1158,11 +1148,23 @@ mod tests { let mut rng = rand::thread_rng(); for _ in 0..1000 { let raw_data_length = rng.gen_range(1..serialized_len_x4); - let raw_data: Vec = (0..raw_data_length).map(|_| rng.gen::()).collect(); + let mut raw_data: Vec = (0..raw_data_length).map(|_| rng.gen::()).collect(); + + // pure random data will ~never have a valid enum tag, so lets help it out + if raw_data_length >= 4 && rng.gen::() { + let tag = rng.gen::() % 3; + raw_data[0] = tag; + raw_data[1] = 0; + raw_data[2] = 0; + raw_data[3] = 0; + } // it is extremely improbable, though theoretically possible, for random bytes to be syntactically valid - // so we only check that the deserialize function does not panic - let _ = VoteState::deserialize(&raw_data); + // so we only check that the parser does not panic and that it succeeds or fails exactly in line with bincode + let test_res = VoteState::deserialize(&raw_data); + let bincode_res = bincode::deserialize::(&raw_data); + + assert_eq!(test_res.is_ok(), bincode_res.is_ok()); } } diff --git a/sdk/program/src/vote/state/vote_state_1_14_11.rs b/sdk/program/src/vote/state/vote_state_1_14_11.rs index 9a2365674171c2..a0ed5b03e9c5e4 100644 --- a/sdk/program/src/vote/state/vote_state_1_14_11.rs +++ b/sdk/program/src/vote/state/vote_state_1_14_11.rs @@ -61,6 +61,10 @@ impl VoteState1_14_11 { data.len() == VoteState1_14_11::size_of() && data[VERSION_OFFSET..DEFAULT_PRIOR_VOTERS_END] != [0; DEFAULT_PRIOR_VOTERS_OFFSET] } + + pub fn is_uninitialized(&self) -> bool { + self.authorized_voters.is_uninitialized() + } } impl From for VoteState1_14_11 { diff --git a/sdk/program/src/vote/state/vote_state_deserialize.rs b/sdk/program/src/vote/state/vote_state_deserialize.rs index 3694f25af37564..f4628332c2f2c3 100644 --- a/sdk/program/src/vote/state/vote_state_deserialize.rs +++ b/sdk/program/src/vote/state/vote_state_deserialize.rs @@ -1,7 +1,6 @@ use { crate::{ instruction::InstructionError, - pubkey::Pubkey, serialize_utils::cursor::*, vote::state::{BlockTimestamp, LandedVote, Lockout, VoteState, MAX_ITEMS}, }, @@ -87,21 +86,12 @@ fn read_prior_voters_into>( if !is_empty { cursor.set_position(prior_voters_position); - let mut encountered_null_voter = false; for i in 0..MAX_ITEMS { let prior_voter = read_pubkey(cursor)?; let from_epoch = read_u64(cursor)?; let until_epoch = read_u64(cursor)?; - let item = (prior_voter, from_epoch, until_epoch); - - if item == (Pubkey::default(), 0, 0) { - encountered_null_voter = true; - } else if encountered_null_voter { - // `prior_voters` should never be sparse - return Err(InstructionError::InvalidAccountData); - } else { - vote_state.prior_voters.buf[i] = item; - } + + vote_state.prior_voters.buf[i] = (prior_voter, from_epoch, until_epoch); } vote_state.prior_voters.idx = read_u64(cursor)? as usize; diff --git a/sdk/program/src/vote/state/vote_state_versions.rs b/sdk/program/src/vote/state/vote_state_versions.rs index 58d63d15def379..f720e36fb87770 100644 --- a/sdk/program/src/vote/state/vote_state_versions.rs +++ b/sdk/program/src/vote/state/vote_state_versions.rs @@ -73,9 +73,9 @@ impl VoteStateVersions { vote_state.authorized_voter == Pubkey::default() } - VoteStateVersions::V1_14_11(vote_state) => vote_state.authorized_voters.is_empty(), + VoteStateVersions::V1_14_11(vote_state) => vote_state.is_uninitialized(), - VoteStateVersions::Current(vote_state) => vote_state.authorized_voters.is_empty(), + VoteStateVersions::Current(vote_state) => vote_state.is_uninitialized(), } } diff --git a/vote/src/vote_account.rs b/vote/src/vote_account.rs index 289921a2669caf..b45faf8f9d78b9 100644 --- a/vote/src/vote_account.rs +++ b/vote/src/vote_account.rs @@ -68,13 +68,10 @@ impl VoteAccount { } pub fn vote_state(&self) -> Result<&VoteState, &Error> { - // VoteState::deserialize_with_bincode deserializes a VoteStateVersions and then - // calls VoteStateVersions::convert_to_current. + // VoteState::deserialize deserializes a VoteStateVersions directly into VoteState self.0 .vote_state - .get_or_init(|| { - VoteState::deserialize_with_bincode(self.0.account.data()).map_err(Error::from) - }) + .get_or_init(|| VoteState::deserialize(self.0.account.data()).map_err(Error::from)) .as_ref() }