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:
parent
f6fbc47b2d
commit
4f22ee8f9b
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(_)
|
||||
|
|
|
@ -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![
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue