uses varint encoding for vote-state lockout offsets

The commit removes CompactVoteStateUpdate and instead reduces serialized
size of VoteStateUpdate using varint encoding for vote-state lockout
offsets.
This commit is contained in:
behzad nouri 2022-09-05 13:02:16 -04:00
parent f6fbc47b2d
commit 4f22ee8f9b
8 changed files with 192 additions and 396 deletions

View File

@ -169,7 +169,7 @@ impl TowerVersions {
}
}
#[frozen_abi(digest = "GrkFcKqGEkJNUYoK1M8rorehi2yyLF4N3Gsj6j8f47Jn")]
#[frozen_abi(digest = "HQoLKAJEQTuVy8nMSkVWbrH3M5xKksxdMEZHGLWbnX6w")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, AbiExample)]
pub struct Tower {
pub node_pubkey: Pubkey,

View File

@ -2020,13 +2020,7 @@ impl ReplayStage {
.is_active(&feature_set::compact_vote_state_updates::id());
let vote = match (should_compact, vote) {
(true, VoteTransaction::VoteStateUpdate(vote_state_update)) => {
if let Some(compact_vote_state_update) = vote_state_update.compact() {
VoteTransaction::from(compact_vote_state_update)
} else {
// Compaction failed
warn!("Compaction failed when generating vote tx for vote account {}. Unable to vote", vote_account_pubkey);
return None;
}
VoteTransaction::CompactVoteStateUpdate(vote_state_update)
}
(_, vote) => vote,
};

View File

@ -172,8 +172,8 @@ pub fn process_instruction(
Err(InstructionError::InvalidInstructionData)
}
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update)
| VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, _) => {
VoteInstruction::CompactUpdateVoteState(vote_state_update)
| VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
if invoke_context
.feature_set
.is_active(&feature_set::allow_votes_to_directly_update_vote_state::id())
@ -184,7 +184,6 @@ pub fn process_instruction(
let sysvar_cache = invoke_context.get_sysvar_cache();
let slot_hashes = sysvar_cache.get_slot_hashes()?;
let clock = sysvar_cache.get_clock()?;
let vote_state_update = compact_vote_state_update.uncompact()?;
vote_state::process_vote_state_update(
&mut me,
slot_hashes.slot_hashes(),

View File

@ -5,7 +5,7 @@ use {
log::*,
serde_derive::{Deserialize, Serialize},
solana_metrics::datapoint_debug,
solana_program::vote::{error::VoteError, program::id},
solana_program::vote::{error::VoteError, program::id, state::serde_compact_vote_state_update},
solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
clock::{Epoch, Slot, UnixTimestamp},
@ -27,12 +27,13 @@ use {
},
};
#[frozen_abi(digest = "8Xa47j7LCp99Q7CQeTz4KPWU8sZgGFpAJw2K4VbPgGh8")]
#[frozen_abi(digest = "2AuJFjx7SYrJ2ugCfH1jFh3Lr9UHMEPfKwwk1NcjqND1")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiEnumVisitor, AbiExample)]
pub enum VoteTransaction {
Vote(Vote),
VoteStateUpdate(VoteStateUpdate),
CompactVoteStateUpdate(CompactVoteStateUpdate),
#[serde(with = "serde_compact_vote_state_update")]
CompactVoteStateUpdate(VoteStateUpdate),
}
impl VoteTransaction {
@ -40,32 +41,26 @@ impl VoteTransaction {
match self {
VoteTransaction::Vote(vote) => vote.slots.clone(),
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.slots(),
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.slots()
}
VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.slots(),
}
}
pub fn slot(&self, i: usize) -> Slot {
match self {
VoteTransaction::Vote(vote) => vote.slots[i],
VoteTransaction::VoteStateUpdate(vote_state_update) => {
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts[i].slot
}
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.slots()[i]
}
}
}
pub fn len(&self) -> usize {
match self {
VoteTransaction::Vote(vote) => vote.slots.len(),
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.lockouts.len(),
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
1 + compact_state_update.lockouts_32.len()
+ compact_state_update.lockouts_16.len()
+ compact_state_update.lockouts_8.len()
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts.len()
}
}
}
@ -73,10 +68,10 @@ impl VoteTransaction {
pub fn is_empty(&self) -> bool {
match self {
VoteTransaction::Vote(vote) => vote.slots.is_empty(),
VoteTransaction::VoteStateUpdate(vote_state_update) => {
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts.is_empty()
}
VoteTransaction::CompactVoteStateUpdate(_) => false,
}
}
@ -84,18 +79,16 @@ impl VoteTransaction {
match self {
VoteTransaction::Vote(vote) => vote.hash,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.hash
}
VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.hash,
}
}
pub fn timestamp(&self) -> Option<UnixTimestamp> {
match self {
VoteTransaction::Vote(vote) => vote.timestamp,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp,
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.timestamp
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.timestamp
}
}
}
@ -103,9 +96,9 @@ impl VoteTransaction {
pub fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
match self {
VoteTransaction::Vote(vote) => vote.timestamp = ts,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp = ts,
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.timestamp = ts
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.timestamp = ts
}
}
}
@ -113,12 +106,10 @@ impl VoteTransaction {
pub fn last_voted_slot(&self) -> Option<Slot> {
match self {
VoteTransaction::Vote(vote) => vote.slots.last().copied(),
VoteTransaction::VoteStateUpdate(vote_state_update) => {
VoteTransaction::VoteStateUpdate(vote_state_update)
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
Some(vote_state_update.lockouts.back()?.slot)
}
VoteTransaction::CompactVoteStateUpdate(compact_state_update) => {
compact_state_update.slots().last().copied()
}
}
}
@ -139,12 +130,6 @@ impl From<VoteStateUpdate> for VoteTransaction {
}
}
impl From<CompactVoteStateUpdate> for VoteTransaction {
fn from(compact_state_update: CompactVoteStateUpdate) -> Self {
VoteTransaction::CompactVoteStateUpdate(compact_state_update)
}
}
// utility function, used by Stakes, tests
pub fn from<T: ReadableAccount>(account: &T) -> Option<VoteState> {
VoteState::deserialize(account.data()).ok()
@ -2970,103 +2955,4 @@ mod tests {
Err(VoteError::SlotHashMismatch),
);
}
#[test]
fn test_compact_vote_state_update_parity() {
let mut vote_state_update = VoteStateUpdate::from(vec![(2, 4), (4, 3), (6, 2), (7, 1)]);
vote_state_update.hash = Hash::new_unique();
vote_state_update.root = Some(1);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
assert_eq!(vote_state_update.slots(), compact_vote_state_update.slots());
assert_eq!(vote_state_update.hash, compact_vote_state_update.hash);
assert_eq!(vote_state_update.root, compact_vote_state_update.root());
let vote_state_update_new = compact_vote_state_update.uncompact().unwrap();
assert_eq!(vote_state_update, vote_state_update_new);
}
#[test]
fn test_compact_vote_state_update_large_offsets() {
let vote_state_update = VoteStateUpdate::from(vec![
(0, 31),
(1, 30),
(2, 29),
(3, 28),
(u64::pow(2, 28), 17),
(u64::pow(2, 28) + u64::pow(2, 16), 1),
]);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
assert_eq!(vote_state_update.slots(), compact_vote_state_update.slots());
let vote_state_update_new = compact_vote_state_update.uncompact().unwrap();
assert_eq!(vote_state_update, vote_state_update_new);
}
#[test]
fn test_compact_vote_state_update_border_conditions() {
let two_31 = u64::pow(2, 31);
let two_15 = u64::pow(2, 15);
let vote_state_update = VoteStateUpdate::from(vec![
(0, 31),
(two_31, 16),
(two_31 + 1, 15),
(two_31 + two_15, 7),
(two_31 + two_15 + 1, 6),
(two_31 + two_15 + 1 + 64, 1),
]);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
assert_eq!(vote_state_update.slots(), compact_vote_state_update.slots());
let vote_state_update_new = compact_vote_state_update.uncompact().unwrap();
assert_eq!(vote_state_update, vote_state_update_new);
}
#[test]
fn test_compact_vote_state_update_large_root() {
let two_58 = u64::pow(2, 58);
let two_31 = u64::pow(2, 31);
let mut vote_state_update = VoteStateUpdate::from(vec![(two_58, 31), (two_58 + two_31, 1)]);
vote_state_update.root = Some(two_31);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
assert_eq!(vote_state_update.slots(), compact_vote_state_update.slots());
let vote_state_update_new = compact_vote_state_update.uncompact().unwrap();
assert_eq!(vote_state_update, vote_state_update_new);
}
#[test]
fn test_compact_vote_state_update_overflow() {
let compact_vote_state_update = CompactVoteStateUpdate {
root: u64::MAX - 1,
root_to_first_vote_offset: 10,
lockouts_32: vec![],
lockouts_16: vec![],
lockouts_8: vec![CompactLockout::new(10)],
hash: Hash::new_unique(),
timestamp: None,
};
assert_eq!(
Err(InstructionError::ArithmeticOverflow),
compact_vote_state_update.uncompact()
);
let compact_vote_state_update = CompactVoteStateUpdate {
root: u64::MAX - u32::MAX as u64,
root_to_first_vote_offset: 10,
lockouts_32: vec![CompactLockout::new(u32::MAX)],
lockouts_16: vec![],
lockouts_8: vec![CompactLockout::new(10)],
hash: Hash::new_unique(),
timestamp: None,
};
assert_eq!(
Err(InstructionError::ArithmeticOverflow),
compact_vote_state_update.uncompact()
);
}
}

View File

@ -82,17 +82,11 @@ fn parse_vote_instruction_data(
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
Some((VoteTransaction::from(vote_state_update), Some(hash)))
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update) => {
compact_vote_state_update
.uncompact()
.ok()
.map(|vote_state_update| (VoteTransaction::from(vote_state_update), None))
VoteInstruction::CompactUpdateVoteState(vote_state_update) => {
Some((VoteTransaction::from(vote_state_update), None))
}
VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, hash) => {
compact_vote_state_update
.uncompact()
.ok()
.map(|vote_state_update| (VoteTransaction::from(vote_state_update), Some(hash)))
VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, hash) => {
Some((VoteTransaction::from(vote_state_update), Some(hash)))
}
VoteInstruction::Authorize(_, _)
| VoteInstruction::AuthorizeChecked(_)

View File

@ -9,8 +9,9 @@ use {
vote::{
program::id,
state::{
CompactVoteStateUpdate, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
VoteAuthorizeWithSeedArgs, VoteInit, VoteState, VoteStateUpdate,
serde_compact_vote_state_update, Vote, VoteAuthorize,
VoteAuthorizeCheckedWithSeedArgs, VoteAuthorizeWithSeedArgs, VoteInit, VoteState,
VoteStateUpdate,
},
},
},
@ -132,14 +133,18 @@ pub enum VoteInstruction {
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
CompactUpdateVoteState(CompactVoteStateUpdate),
#[serde(with = "serde_compact_vote_state_update")]
CompactUpdateVoteState(VoteStateUpdate),
/// Update the onchain vote state for the signer along with a switching proof.
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
CompactUpdateVoteStateSwitch(CompactVoteStateUpdate, Hash),
CompactUpdateVoteStateSwitch(
#[serde(with = "serde_compact_vote_state_update")] VoteStateUpdate,
Hash,
),
}
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
@ -387,7 +392,7 @@ pub fn update_vote_state_switch(
pub fn compact_update_vote_state(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
compact_vote_state_update: CompactVoteStateUpdate,
vote_state_update: VoteStateUpdate,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
@ -396,7 +401,7 @@ pub fn compact_update_vote_state(
Instruction::new_with_bincode(
id(),
&VoteInstruction::CompactUpdateVoteState(compact_vote_state_update),
&VoteInstruction::CompactUpdateVoteState(vote_state_update),
account_metas,
)
}
@ -404,7 +409,7 @@ pub fn compact_update_vote_state(
pub fn compact_update_vote_state_switch(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
vote_state_update: CompactVoteStateUpdate,
vote_state_update: VoteStateUpdate,
proof_hash: Hash,
) -> Instruction {
let account_metas = vec![

View File

@ -9,7 +9,6 @@ use {
instruction::InstructionError,
pubkey::Pubkey,
rent::Rent,
short_vec,
sysvar::clock::Clock,
vote::{authorized_voters::AuthorizedVoters, error::VoteError},
},
@ -85,28 +84,6 @@ impl Lockout {
}
}
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Copy, Clone, AbiExample)]
pub struct CompactLockout<T: Sized> {
// Offset to the next vote, 0 if this is the last vote in the tower
pub offset: T,
// Confirmation count, guarenteed to be < 32
pub confirmation_count: u8,
}
impl<T> CompactLockout<T> {
pub fn new(offset: T) -> Self {
Self {
offset,
confirmation_count: 1,
}
}
// The number of slots for which this vote is locked
pub fn lockout(&self) -> u64 {
(INITIAL_LOCKOUT as u64).pow(self.confirmation_count.into())
}
}
#[frozen_abi(digest = "GwJfVFsATSj7nvKwtUkHYzqPRaPY6SLxPGXApuCya3x5")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
pub struct VoteStateUpdate {
@ -151,205 +128,6 @@ impl VoteStateUpdate {
pub fn slots(&self) -> Vec<Slot> {
self.lockouts.iter().map(|lockout| lockout.slot).collect()
}
pub fn compact(self) -> Option<CompactVoteStateUpdate> {
CompactVoteStateUpdate::new(self.lockouts, self.root, self.hash, self.timestamp)
}
}
/// Ignoring overhead, in a full `VoteStateUpdate` the lockouts take up
/// 31 * (64 + 32) = 2976 bits.
///
/// In this schema we separate the votes into 3 separate lockout structures
/// and store offsets rather than slot number, allowing us to use smaller fields.
///
/// In a full `CompactVoteStateUpdate` the lockouts take up
/// 64 + (32 + 8) * 16 + (16 + 8) * 8 + (8 + 8) * 6 = 992 bits
/// allowing us to greatly reduce block size.
#[frozen_abi(digest = "EeMnyxPUyd3hK7UQ8BcWDW8qrsdXA9F6ZUoAWAh1nDxX")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
pub struct CompactVoteStateUpdate {
/// The proposed root, u64::MAX if there is no root
pub root: Slot,
/// The offset from the root (or 0 if no root) to the first vote
pub root_to_first_vote_offset: u64,
/// Part of the proposed tower, votes with confirmation_count > 15
#[serde(with = "short_vec")]
pub lockouts_32: Vec<CompactLockout<u32>>,
/// Part of the proposed tower, votes with 15 >= confirmation_count > 7
#[serde(with = "short_vec")]
pub lockouts_16: Vec<CompactLockout<u16>>,
/// Part of the proposed tower, votes with 7 >= confirmation_count
#[serde(with = "short_vec")]
pub lockouts_8: Vec<CompactLockout<u8>>,
/// Signature of the bank's state at the last slot
pub hash: Hash,
/// Processing timestamp of last slot
pub timestamp: Option<UnixTimestamp>,
}
impl From<Vec<(Slot, u32)>> for CompactVoteStateUpdate {
fn from(recent_slots: Vec<(Slot, u32)>) -> Self {
let lockouts: VecDeque<Lockout> = recent_slots
.into_iter()
.map(|(slot, confirmation_count)| Lockout {
slot,
confirmation_count,
})
.collect();
Self::new(lockouts, None, Hash::default(), None).unwrap()
}
}
impl CompactVoteStateUpdate {
pub fn new(
mut lockouts: VecDeque<Lockout>,
root: Option<Slot>,
hash: Hash,
timestamp: Option<UnixTimestamp>,
) -> Option<Self> {
if lockouts.is_empty() {
return Some(Self::default());
}
let mut cur_slot = root.unwrap_or(0u64);
let mut cur_confirmation_count = 0;
let offset = lockouts
.pop_front()
.map(
|Lockout {
slot,
confirmation_count,
}| {
assert!(confirmation_count < 32);
slot.checked_sub(cur_slot).map(|offset| {
cur_slot = slot;
cur_confirmation_count = confirmation_count;
offset
})
},
)
.expect("Tower should not be empty")?;
let mut lockouts_32 = Vec::new();
let mut lockouts_16 = Vec::new();
let mut lockouts_8 = Vec::new();
for Lockout {
slot,
confirmation_count,
} in lockouts
{
assert!(confirmation_count < 32);
let offset = slot.checked_sub(cur_slot)?;
if cur_confirmation_count > 15 {
lockouts_32.push(CompactLockout {
offset: offset.try_into().unwrap(),
confirmation_count: cur_confirmation_count.try_into().unwrap(),
});
} else if cur_confirmation_count > 7 {
lockouts_16.push(CompactLockout {
offset: offset.try_into().unwrap(),
confirmation_count: cur_confirmation_count.try_into().unwrap(),
});
} else {
lockouts_8.push(CompactLockout {
offset: offset.try_into().unwrap(),
confirmation_count: cur_confirmation_count.try_into().unwrap(),
})
}
cur_slot = slot;
cur_confirmation_count = confirmation_count;
}
// Last vote should be at the top of tower, so we don't have to explicitly store it
assert!(cur_confirmation_count == 1);
Some(Self {
root: root.unwrap_or(u64::MAX),
root_to_first_vote_offset: offset,
lockouts_32,
lockouts_16,
lockouts_8,
hash,
timestamp,
})
}
pub fn root(&self) -> Option<Slot> {
if self.root == u64::MAX {
None
} else {
Some(self.root)
}
}
pub fn slots(&self) -> Vec<Slot> {
std::iter::once(self.root_to_first_vote_offset)
.chain(self.lockouts_32.iter().map(|lockout| lockout.offset.into()))
.chain(self.lockouts_16.iter().map(|lockout| lockout.offset.into()))
.chain(self.lockouts_8.iter().map(|lockout| lockout.offset.into()))
.scan(self.root().unwrap_or(0), |prev_slot, offset| {
prev_slot.checked_add(offset).map(|slot| {
*prev_slot = slot;
slot
})
})
.collect()
}
pub fn uncompact(self) -> Result<VoteStateUpdate, InstructionError> {
let first_slot = self
.root()
.unwrap_or(0)
.checked_add(self.root_to_first_vote_offset)
.ok_or(InstructionError::ArithmeticOverflow)?;
let mut arithmetic_overflow_occured = false;
let lockouts = self
.lockouts_32
.iter()
.map(|lockout| (lockout.offset.into(), lockout.confirmation_count))
.chain(
self.lockouts_16
.iter()
.map(|lockout| (lockout.offset.into(), lockout.confirmation_count)),
)
.chain(
self.lockouts_8
.iter()
.map(|lockout| (lockout.offset.into(), lockout.confirmation_count)),
)
.chain(
// To pick up the last element
std::iter::once((0, 1)),
)
.scan(
first_slot,
|slot, (offset, confirmation_count): (u64, u8)| {
let cur_slot = *slot;
if let Some(new_slot) = slot.checked_add(offset) {
*slot = new_slot;
Some(Lockout {
slot: cur_slot,
confirmation_count: confirmation_count.into(),
})
} else {
arithmetic_overflow_occured = true;
None
}
},
)
.collect();
if arithmetic_overflow_occured {
Err(InstructionError::ArithmeticOverflow)
} else {
Ok(VoteStateUpdate {
lockouts,
root: self.root(),
hash: self.hash,
timestamp: self.timestamp,
})
}
}
}
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
@ -789,9 +567,109 @@ impl VoteState {
}
}
pub mod serde_compact_vote_state_update {
use {
super::*,
crate::{
clock::{Slot, UnixTimestamp},
serde_varint, short_vec,
vote::state::Lockout,
},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
#[derive(Deserialize, Serialize, AbiExample)]
struct LockoutOffset {
#[serde(with = "serde_varint")]
offset: Slot,
confirmation_count: u8,
}
#[derive(Deserialize, Serialize)]
struct CompactVoteStateUpdate {
root: Slot,
#[serde(with = "short_vec")]
lockout_offsets: Vec<LockoutOffset>,
hash: Hash,
timestamp: Option<UnixTimestamp>,
}
pub fn serialize<S>(
vote_state_update: &VoteStateUpdate,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let lockout_offsets = vote_state_update.lockouts.iter().scan(
vote_state_update.root.unwrap_or_default(),
|slot, lockout| {
let offset = match lockout.slot.checked_sub(*slot) {
None => return Some(Err(serde::ser::Error::custom("Invalid vote lockout"))),
Some(offset) => offset,
};
let confirmation_count = match u8::try_from(lockout.confirmation_count) {
Ok(confirmation_count) => confirmation_count,
Err(_) => {
return Some(Err(serde::ser::Error::custom("Invalid confirmation count")))
}
};
let lockout_offset = LockoutOffset {
offset,
confirmation_count,
};
*slot = lockout.slot;
Some(Ok(lockout_offset))
},
);
let compact_vote_state_update = CompactVoteStateUpdate {
root: vote_state_update.root.unwrap_or(Slot::MAX),
lockout_offsets: lockout_offsets.collect::<Result<_, _>>()?,
hash: vote_state_update.hash,
timestamp: vote_state_update.timestamp,
};
compact_vote_state_update.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<VoteStateUpdate, D::Error>
where
D: Deserializer<'de>,
{
let CompactVoteStateUpdate {
root,
lockout_offsets,
hash,
timestamp,
} = CompactVoteStateUpdate::deserialize(deserializer)?;
let root = (root != Slot::MAX).then_some(root);
let lockouts =
lockout_offsets
.iter()
.scan(root.unwrap_or_default(), |slot, lockout_offset| {
*slot = match slot.checked_add(lockout_offset.offset) {
None => {
return Some(Err(serde::de::Error::custom("Invalid lockout offset")))
}
Some(slot) => slot,
};
let lockout = Lockout {
slot: *slot,
confirmation_count: u32::from(lockout_offset.confirmation_count),
};
Some(Ok(lockout))
});
Ok(VoteStateUpdate {
root,
lockouts: lockouts.collect::<Result<_, _>>()?,
hash,
timestamp,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use {super::*, itertools::Itertools, rand::Rng};
#[test]
fn test_vote_serialize() {
@ -1235,4 +1113,50 @@ mod tests {
// golden, may need updating when vote_state grows
assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
}
#[test]
fn test_serde_compact_vote_state_update() {
let mut rng = rand::thread_rng();
for _ in 0..5000 {
run_serde_compact_vote_state_update(&mut rng);
}
}
#[allow(clippy::integer_arithmetic)]
fn run_serde_compact_vote_state_update<R: Rng>(rng: &mut R) {
let lockouts: VecDeque<_> = std::iter::repeat_with(|| Lockout {
slot: 149_303_885 + rng.gen_range(0, 10_000),
confirmation_count: rng.gen_range(0, 33),
})
.take(32)
.sorted_by_key(|lockout| lockout.slot)
.collect();
let root = rng
.gen_ratio(1, 2)
.then(|| lockouts[0].slot - rng.gen_range(0, 1_000));
let timestamp = rng.gen_ratio(1, 2).then(|| rng.gen());
let hash = Hash::from(rng.gen::<[u8; 32]>());
let vote_state_update = VoteStateUpdate {
lockouts,
root,
hash,
timestamp,
};
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
enum VoteInstruction {
#[serde(with = "serde_compact_vote_state_update")]
UpdateVoteState(VoteStateUpdate),
UpdateVoteStateSwitch(
#[serde(with = "serde_compact_vote_state_update")] VoteStateUpdate,
Hash,
),
}
let vote = VoteInstruction::UpdateVoteState(vote_state_update.clone());
let bytes = bincode::serialize(&vote).unwrap();
assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
let hash = Hash::from(rng.gen::<[u8; 32]>());
let vote = VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash);
let bytes = bincode::serialize(&vote).unwrap();
assert_eq!(vote, bincode::deserialize(&bytes).unwrap());
}
}

View File

@ -135,10 +135,7 @@ pub fn parse_vote(
}),
})
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update) => {
let vote_state_update = compact_vote_state_update.uncompact().map_err(|_| {
ParseInstructionError::InstructionNotParsable(ParsableProgram::Vote)
})?;
VoteInstruction::CompactUpdateVoteState(vote_state_update) => {
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
@ -155,10 +152,7 @@ pub fn parse_vote(
}),
})
}
VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, hash) => {
let vote_state_update = compact_vote_state_update.uncompact().map_err(|_| {
ParseInstructionError::InstructionNotParsable(ParsableProgram::Vote)
})?;
VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, hash) => {
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
@ -770,7 +764,7 @@ mod test {
#[test]
fn test_parse_compact_vote_state_update_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
let compact_vote_state_update = vote_state_update.clone();
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();
@ -813,7 +807,7 @@ mod test {
#[test]
fn test_parse_compact_vote_state_update_switch_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let compact_vote_state_update = vote_state_update.clone().compact().unwrap();
let compact_vote_state_update = vote_state_update.clone();
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();