solana-program: only decode prior_voters if needed (#34972)
This commit is contained in:
parent
2455dc1a69
commit
c99427eb9e
|
@ -61,6 +61,15 @@ pub(crate) fn read_pubkey<T: AsRef<[u8]>>(
|
||||||
Ok(Pubkey::from(buf))
|
Ok(Pubkey::from(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read_bool<T: AsRef<[u8]>>(cursor: &mut Cursor<T>) -> Result<bool, InstructionError> {
|
||||||
|
let byte = read_u8(cursor)?;
|
||||||
|
match byte {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => Err(InstructionError::InvalidAccountData),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {super::*, rand::Rng, std::fmt::Debug};
|
use {super::*, rand::Rng, std::fmt::Debug};
|
||||||
|
@ -115,6 +124,12 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_bool() {
|
||||||
|
test_read(read_bool, false);
|
||||||
|
test_read(read_bool, true);
|
||||||
|
}
|
||||||
|
|
||||||
fn test_read<T: Debug + PartialEq + serde::Serialize + borsh0_10::BorshSerialize>(
|
fn test_read<T: Debug + PartialEq + serde::Serialize + borsh0_10::BorshSerialize>(
|
||||||
reader: fn(&mut Cursor<Vec<u8>>) -> Result<T, InstructionError>,
|
reader: fn(&mut Cursor<Vec<u8>>) -> Result<T, InstructionError>,
|
||||||
test_value: T,
|
test_value: T,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use {
|
||||||
sysvar::clock::Clock,
|
sysvar::clock::Clock,
|
||||||
vote::{authorized_voters::AuthorizedVoters, error::VoteError},
|
vote::{authorized_voters::AuthorizedVoters, error::VoteError},
|
||||||
},
|
},
|
||||||
bincode::{serialize_into, ErrorKind},
|
bincode::{serialize_into, serialized_size, ErrorKind},
|
||||||
serde_derive::{Deserialize, Serialize},
|
serde_derive::{Deserialize, Serialize},
|
||||||
std::{collections::VecDeque, fmt::Debug, io::Cursor},
|
std::{collections::VecDeque, fmt::Debug, io::Cursor},
|
||||||
};
|
};
|
||||||
|
@ -399,6 +399,12 @@ impl VoteState {
|
||||||
input: &[u8],
|
input: &[u8],
|
||||||
vote_state: &mut VoteState,
|
vote_state: &mut VoteState,
|
||||||
) -> Result<(), InstructionError> {
|
) -> 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 mut cursor = Cursor::new(input);
|
||||||
|
|
||||||
let variant = read_u32(&mut cursor)?;
|
let variant = read_u32(&mut cursor)?;
|
||||||
|
@ -410,7 +416,13 @@ impl VoteState {
|
||||||
// Current. the only difference from V1_14_11 is the addition of a slot-latency to each vote
|
// Current. the only difference from V1_14_11 is the addition of a slot-latency to each vote
|
||||||
2 => deserialize_vote_state_into(&mut cursor, vote_state, true),
|
2 => deserialize_vote_state_into(&mut cursor, vote_state, true),
|
||||||
_ => Err(InstructionError::InvalidAccountData),
|
_ => Err(InstructionError::InvalidAccountData),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if cursor.position() > input.len() as u64 {
|
||||||
|
return Err(InstructionError::InvalidAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(
|
pub fn serialize(
|
||||||
|
@ -886,7 +898,7 @@ mod tests {
|
||||||
|
|
||||||
// variant
|
// variant
|
||||||
// provide 4x the minimum struct size in bytes to ensure we typically touch every field
|
// provide 4x the minimum struct size in bytes to ensure we typically touch every field
|
||||||
let struct_bytes_x4 = std::mem::size_of::<u64>() * 4;
|
let struct_bytes_x4 = std::mem::size_of::<VoteState>() * 4;
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
|
let raw_data: Vec<u8> = (0..struct_bytes_x4).map(|_| rand::random::<u8>()).collect();
|
||||||
let mut unstructured = Unstructured::new(&raw_data);
|
let mut unstructured = Unstructured::new(&raw_data);
|
||||||
|
@ -911,7 +923,7 @@ mod tests {
|
||||||
assert_eq!(e, InstructionError::InvalidAccountData);
|
assert_eq!(e, InstructionError::InvalidAccountData);
|
||||||
|
|
||||||
// variant
|
// variant
|
||||||
let serialized_len_x4 = bincode::serialized_size(&test_vote_state).unwrap() * 4;
|
let serialized_len_x4 = serialized_size(&test_vote_state).unwrap() * 4;
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let raw_data_length = rng.gen_range(1..serialized_len_x4);
|
let raw_data_length = rng.gen_range(1..serialized_len_x4);
|
||||||
|
@ -1262,7 +1274,7 @@ mod tests {
|
||||||
fn test_vote_state_size_of() {
|
fn test_vote_state_size_of() {
|
||||||
let vote_state = VoteState::get_max_sized_vote_state();
|
let vote_state = VoteState::get_max_sized_vote_state();
|
||||||
let vote_state = VoteStateVersions::new_current(vote_state);
|
let vote_state = VoteStateVersions::new_current(vote_state);
|
||||||
let size = bincode::serialized_size(&vote_state).unwrap();
|
let size = serialized_size(&vote_state).unwrap();
|
||||||
assert_eq!(VoteState::size_of() as u64, size);
|
assert_eq!(VoteState::size_of() as u64, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use {
|
||||||
serialize_utils::cursor::*,
|
serialize_utils::cursor::*,
|
||||||
vote::state::{BlockTimestamp, LandedVote, Lockout, VoteState, MAX_ITEMS},
|
vote::state::{BlockTimestamp, LandedVote, Lockout, VoteState, MAX_ITEMS},
|
||||||
},
|
},
|
||||||
|
bincode::serialized_size,
|
||||||
std::io::Cursor,
|
std::io::Cursor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,34 +67,46 @@ fn read_prior_voters_into<T: AsRef<[u8]>>(
|
||||||
cursor: &mut Cursor<T>,
|
cursor: &mut Cursor<T>,
|
||||||
vote_state: &mut VoteState,
|
vote_state: &mut VoteState,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut encountered_null_voter = false;
|
// record our position at the start of the struct
|
||||||
for i in 0..MAX_ITEMS {
|
let prior_voters_position = cursor.position();
|
||||||
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) {
|
// `serialized_size()` must be used over `mem::size_of()` because of alignment
|
||||||
encountered_null_voter = true;
|
let is_empty_position = serialized_size(&vote_state.prior_voters)
|
||||||
} else if encountered_null_voter {
|
.ok()
|
||||||
// `prior_voters` should never be sparse
|
.and_then(|v| v.checked_add(prior_voters_position))
|
||||||
return Err(InstructionError::InvalidAccountData);
|
.and_then(|v| v.checked_sub(1))
|
||||||
} else {
|
.ok_or(InstructionError::InvalidAccountData)?;
|
||||||
vote_state.prior_voters.buf[i] = item;
|
|
||||||
|
// move to the end, to check if we need to parse the data
|
||||||
|
cursor.set_position(is_empty_position);
|
||||||
|
|
||||||
|
// if empty, we already read past the end of this struct and need to do no further work
|
||||||
|
// otherwise we go back to the start and proceed to decode the data
|
||||||
|
let is_empty = read_bool(cursor)?;
|
||||||
|
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.idx = read_u64(cursor)? as usize;
|
||||||
|
vote_state.prior_voters.is_empty = read_bool(cursor)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = read_u64(cursor)? as usize;
|
|
||||||
vote_state.prior_voters.idx = idx;
|
|
||||||
|
|
||||||
let is_empty_byte = read_u8(cursor)?;
|
|
||||||
let is_empty = match is_empty_byte {
|
|
||||||
0 => false,
|
|
||||||
1 => true,
|
|
||||||
_ => return Err(InstructionError::InvalidAccountData),
|
|
||||||
};
|
|
||||||
vote_state.prior_voters.is_empty = is_empty;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue