Add vote instructions that directly update on chain vote state (#21531)
* Add vote state instructions UpdateVoteState and UpdateVoteStateSwitch * cargo tree * extract vote state version conversion to common fn
This commit is contained in:
65
programs/bpf/Cargo.lock
generated
65
programs/bpf/Cargo.lock
generated
@ -562,6 +562,16 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
|
||||
dependencies = [
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "2.1.0"
|
||||
@ -825,6 +835,15 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
@ -1062,6 +1081,17 @@ dependencies = [
|
||||
"wasi 0.10.1+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghost"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.21.0"
|
||||
@ -1309,6 +1339,16 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1367fed6750ff2a5bcb967a631528303bb85631f167a75eb1bf7762d57eb7678"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"ghost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.3.0"
|
||||
@ -3476,6 +3516,7 @@ dependencies = [
|
||||
"solana-program-runtime",
|
||||
"solana-sdk",
|
||||
"thiserror",
|
||||
"typetag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3949,6 +3990,30 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "typetag"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4080564c5b2241b5bff53ab610082234e0c57b0417f4bd10596f183001505b8a"
|
||||
dependencies = [
|
||||
"erased-serde",
|
||||
"inventory",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"typetag-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typetag-impl"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e60147782cc30833c05fba3bab1d9b5771b2685a2557672ac96fa5d154099c0e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
|
@ -22,6 +22,7 @@ solana-logger = { path = "../../logger", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../../metrics", version = "=1.10.0" }
|
||||
solana-program-runtime = { path = "../../program-runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "=1.10.0" }
|
||||
typetag = "0.1"
|
||||
thiserror = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
|
@ -4,7 +4,7 @@
|
||||
use {
|
||||
crate::{
|
||||
id,
|
||||
vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState},
|
||||
vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate},
|
||||
},
|
||||
log::*,
|
||||
num_derive::{FromPrimitive, ToPrimitive},
|
||||
@ -156,6 +156,24 @@ pub enum VoteInstruction {
|
||||
/// 2. `[SIGNER]` Vote or withdraw authority
|
||||
/// 3. `[SIGNER]` New vote or withdraw authority
|
||||
AuthorizeChecked(VoteAuthorize),
|
||||
|
||||
/// Update the onchain vote state for the signer.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[Write]` Vote account to vote with
|
||||
/// 1. `[]` Slot hashes sysvar
|
||||
/// 2. `[]` Clock sysvar
|
||||
/// 3. `[SIGNER]` Vote authority
|
||||
UpdateVoteState(VoteStateUpdate),
|
||||
|
||||
/// Update the onchain vote state for the signer along with a switching proof.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[Write]` Vote account to vote with
|
||||
/// 1. `[]` Slot hashes sysvar
|
||||
/// 2. `[]` Clock sysvar
|
||||
/// 3. `[SIGNER]` Vote authority
|
||||
UpdateVoteStateSwitch(VoteStateUpdate, Hash),
|
||||
}
|
||||
|
||||
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
|
||||
@ -313,6 +331,45 @@ pub fn vote_switch(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_vote_state(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_voter_pubkey: &Pubkey,
|
||||
vote_state_update: VoteStateUpdate,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
|
||||
];
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&VoteInstruction::UpdateVoteState(vote_state_update),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_vote_state_switch(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_voter_pubkey: &Pubkey,
|
||||
vote_state_update: VoteStateUpdate,
|
||||
proof_hash: Hash,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
|
||||
];
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&VoteInstruction::UpdateVoteStateSwitch(vote_state_update, proof_hash),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn withdraw(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_withdrawer_pubkey: &Pubkey,
|
||||
@ -406,6 +463,23 @@ pub fn process_instruction(
|
||||
&signers,
|
||||
)
|
||||
}
|
||||
VoteInstruction::UpdateVoteState(vote_state_update)
|
||||
| VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => {
|
||||
inc_new_counter_info!("vote-state-native", 1);
|
||||
vote_state::process_vote_state_update(
|
||||
me,
|
||||
&from_keyed_account::<SlotHashes>(keyed_account_at_index(
|
||||
keyed_accounts,
|
||||
first_instruction_account + 1,
|
||||
)?)?,
|
||||
&from_keyed_account::<Clock>(keyed_account_at_index(
|
||||
keyed_accounts,
|
||||
first_instruction_account + 2,
|
||||
)?)?,
|
||||
&vote_state_update,
|
||||
&signers,
|
||||
)
|
||||
}
|
||||
VoteInstruction::Withdraw(lamports) => {
|
||||
let to = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let rent_sysvar = if invoke_context
|
||||
@ -544,6 +618,14 @@ mod tests {
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountOwner),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction_as_one_arg(&update_vote_state(
|
||||
&invalid_vote_state_pubkey(),
|
||||
&Pubkey::default(),
|
||||
VoteStateUpdate::default(),
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountOwner),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -553,7 +635,7 @@ mod tests {
|
||||
&Pubkey::new_unique(),
|
||||
&Pubkey::new_unique(),
|
||||
&VoteInit::default(),
|
||||
100,
|
||||
101,
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction_as_one_arg(&instructions[1]),
|
||||
@ -585,6 +667,25 @@ mod tests {
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction_as_one_arg(&update_vote_state(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
VoteStateUpdate::default(),
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
process_instruction_as_one_arg(&update_vote_state_switch(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
VoteStateUpdate::default(),
|
||||
Hash::default(),
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
process_instruction_as_one_arg(&update_validator_identity(
|
||||
&Pubkey::new_unique(),
|
||||
|
@ -19,9 +19,11 @@ use {
|
||||
sysvar::clock::Clock,
|
||||
},
|
||||
std::{
|
||||
any::Any,
|
||||
boxed::Box,
|
||||
cmp::Ordering,
|
||||
collections::{HashSet, VecDeque},
|
||||
fmt::Debug,
|
||||
},
|
||||
};
|
||||
|
||||
@ -36,9 +38,69 @@ pub const INITIAL_LOCKOUT: usize = 2;
|
||||
// Maximum number of credits history to keep around
|
||||
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
|
||||
|
||||
// Offset of VoteState::prior_voters, for determining initialization status without deserialization
|
||||
// Offset of VoteState::pri : Clone + Debug {or_voters, for determining initialization status without deserialization
|
||||
const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82;
|
||||
|
||||
// VoteTransactionClone hack is done so that we can derive clone on the tower that uses the
|
||||
// VoteTransaction trait object. Clone doesn't work here because it returns Self which is not
|
||||
// allowed for trait objects
|
||||
#[typetag::serde{tag = "type"}]
|
||||
pub trait VoteTransaction: VoteTransactionClone + Debug + Send {
|
||||
fn slot(&self, i: usize) -> Slot;
|
||||
fn len(&self) -> usize;
|
||||
fn hash(&self) -> Hash;
|
||||
fn timestamp(&self) -> Option<UnixTimestamp>;
|
||||
fn last_voted_slot(&self) -> Option<Slot>;
|
||||
fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)>;
|
||||
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>);
|
||||
|
||||
fn slots(&self) -> Vec<Slot> {
|
||||
(0..self.len()).map(|i| self.slot(i)).collect()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
// Have to manually implement because deriving PartialEq returns Self
|
||||
fn eq(&self, other: &dyn VoteTransaction) -> bool;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait VoteTransactionClone {
|
||||
fn clone_box(&self) -> Box<dyn VoteTransaction>;
|
||||
}
|
||||
|
||||
impl<T> VoteTransactionClone for T
|
||||
where
|
||||
T: VoteTransaction + Clone + 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn VoteTransaction> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn VoteTransaction> {
|
||||
fn clone(&self) -> Box<dyn VoteTransaction> {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
// Have to manually implement because derive returns Self
|
||||
impl<'a, 'b> PartialEq<dyn VoteTransaction + 'b> for dyn VoteTransaction + 'a {
|
||||
fn eq(&self, other: &(dyn VoteTransaction + 'b)) -> bool {
|
||||
VoteTransaction::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
// This is needed because of weirdness in the derive PartialEq macro
|
||||
// See rust issue #31740 for more info
|
||||
impl PartialEq<&Self> for Box<dyn VoteTransaction> {
|
||||
fn eq(&self, other: &&Self) -> bool {
|
||||
VoteTransaction::eq(self.as_ref(), other.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")]
|
||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
pub struct Vote {
|
||||
@ -58,17 +120,51 @@ impl Vote {
|
||||
timestamp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_voted_slot(&self) -> Option<Slot> {
|
||||
#[typetag::serde]
|
||||
impl VoteTransaction for Vote {
|
||||
fn slot(&self, i: usize) -> Slot {
|
||||
self.slots[i]
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
|
||||
fn hash(&self) -> Hash {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Option<UnixTimestamp> {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
fn last_voted_slot(&self) -> Option<Slot> {
|
||||
self.slots.last().copied()
|
||||
}
|
||||
|
||||
pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
|
||||
fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
|
||||
self.slots.last().copied().map(|slot| (slot, self.hash))
|
||||
}
|
||||
|
||||
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
|
||||
self.timestamp = ts
|
||||
}
|
||||
|
||||
fn eq(&self, other: &dyn VoteTransaction) -> bool {
|
||||
other
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |x| x == self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Copy, Clone, AbiExample)]
|
||||
pub struct Lockout {
|
||||
pub slot: Slot,
|
||||
pub confirmation_count: u32,
|
||||
@ -99,6 +195,74 @@ impl Lockout {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||
pub struct VoteStateUpdate {
|
||||
/// The proposed tower
|
||||
pub lockouts: VecDeque<Lockout>,
|
||||
/// The proposed root
|
||||
pub root: Option<Slot>,
|
||||
/// signature of the bank's state at the last slot
|
||||
pub hash: Hash,
|
||||
/// processing timestamp of last slot
|
||||
pub timestamp: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
impl VoteStateUpdate {
|
||||
pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
|
||||
Self {
|
||||
lockouts,
|
||||
root,
|
||||
hash,
|
||||
timestamp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[typetag::serde]
|
||||
impl VoteTransaction for VoteStateUpdate {
|
||||
fn slot(&self, i: usize) -> Slot {
|
||||
self.lockouts[i].slot
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.lockouts.len()
|
||||
}
|
||||
|
||||
fn hash(&self) -> Hash {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Option<UnixTimestamp> {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
fn last_voted_slot(&self) -> Option<Slot> {
|
||||
self.lockouts.back().copied().map(|lockout| lockout.slot)
|
||||
}
|
||||
|
||||
fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
|
||||
self.lockouts
|
||||
.back()
|
||||
.copied()
|
||||
.map(|lockout| (lockout.slot, self.hash))
|
||||
}
|
||||
|
||||
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
|
||||
self.timestamp = ts
|
||||
}
|
||||
|
||||
fn eq(&self, other: &dyn VoteTransaction) -> bool {
|
||||
other
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |x| x == self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct VoteInit {
|
||||
pub node_pubkey: Pubkey,
|
||||
@ -301,7 +465,7 @@ impl VoteState {
|
||||
|
||||
fn check_slots_are_valid(
|
||||
&self,
|
||||
vote: &Vote,
|
||||
vote: &(impl VoteTransaction + Debug),
|
||||
slot_hashes: &[(Slot, Hash)],
|
||||
) -> Result<(), VoteError> {
|
||||
// index into the vote's slots, sarting at the newest
|
||||
@ -320,19 +484,19 @@ impl VoteState {
|
||||
//
|
||||
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
|
||||
// the oldest/smallest vote
|
||||
while i < vote.slots.len() && j > 0 {
|
||||
while i < vote.len() && j > 0 {
|
||||
// 1) increment `i` to find the smallest slot `s` in `vote.slots`
|
||||
// where `s` >= `last_voted_slot`
|
||||
if self
|
||||
.last_voted_slot()
|
||||
.map_or(false, |last_voted_slot| vote.slots[i] <= last_voted_slot)
|
||||
.map_or(false, |last_voted_slot| vote.slot(i) <= last_voted_slot)
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) Find the hash for this slot `s`.
|
||||
if vote.slots[i] != slot_hashes[j - 1].0 {
|
||||
if vote.slot(i) != slot_hashes[j - 1].0 {
|
||||
// Decrement `j` to find newer slots
|
||||
j -= 1;
|
||||
continue;
|
||||
@ -354,7 +518,7 @@ impl VoteState {
|
||||
);
|
||||
return Err(VoteError::VoteTooOld);
|
||||
}
|
||||
if i != vote.slots.len() {
|
||||
if i != vote.len() {
|
||||
// This means there existed some slot for which we couldn't find
|
||||
// a matching slot hash in step 2)
|
||||
info!(
|
||||
@ -364,13 +528,16 @@ impl VoteState {
|
||||
inc_new_counter_info!("dropped-vote-slot", 1);
|
||||
return Err(VoteError::SlotsMismatch);
|
||||
}
|
||||
if slot_hashes[j].1 != vote.hash {
|
||||
if slot_hashes[j].1 != vote.hash() {
|
||||
// This means the newest vote in the slot has a match that
|
||||
// doesn't match the expected hash for that slot on this
|
||||
// fork
|
||||
warn!(
|
||||
"{} dropped vote {:?} failed to match hash {} {}",
|
||||
self.node_pubkey, vote, vote.hash, slot_hashes[j].1
|
||||
self.node_pubkey,
|
||||
vote,
|
||||
vote.hash(),
|
||||
slot_hashes[j].1
|
||||
);
|
||||
inc_new_counter_info!("dropped-vote-hash", 1);
|
||||
return Err(VoteError::SlotHashMismatch);
|
||||
@ -947,13 +1114,11 @@ pub fn initialize_account<S: std::hash::BuildHasher>(
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
fn verify_and_get_vote_state<S: std::hash::BuildHasher>(
|
||||
vote_account: &KeyedAccount,
|
||||
slot_hashes: &[SlotHash],
|
||||
clock: &Clock,
|
||||
vote: &Vote,
|
||||
signers: &HashSet<Pubkey, S>,
|
||||
) -> Result<(), InstructionError> {
|
||||
) -> Result<VoteState, InstructionError> {
|
||||
let versioned = State::<VoteStateVersions>::state(vote_account)?;
|
||||
|
||||
if versioned.is_uninitialized() {
|
||||
@ -964,6 +1129,18 @@ pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
|
||||
verify_authorized_signer(&authorized_voter, signers)?;
|
||||
|
||||
Ok(vote_state)
|
||||
}
|
||||
|
||||
pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
vote_account: &KeyedAccount,
|
||||
slot_hashes: &[SlotHash],
|
||||
clock: &Clock,
|
||||
vote: &Vote,
|
||||
signers: &HashSet<Pubkey, S>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
||||
|
||||
vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
|
||||
if let Some(timestamp) = vote.timestamp {
|
||||
vote.slots
|
||||
@ -975,6 +1152,25 @@ pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
|
||||
}
|
||||
|
||||
pub fn process_vote_state_update<S: std::hash::BuildHasher>(
|
||||
vote_account: &KeyedAccount,
|
||||
slot_hashes: &[SlotHash],
|
||||
clock: &Clock,
|
||||
vote_state_update: &VoteStateUpdate,
|
||||
signers: &HashSet<Pubkey, S>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
||||
|
||||
vote_state.check_slots_are_valid(vote_state_update, slot_hashes)?;
|
||||
vote_state.process_new_vote_state(
|
||||
vote_state_update.lockouts.clone(),
|
||||
vote_state_update.root,
|
||||
vote_state_update.timestamp,
|
||||
clock.epoch,
|
||||
)?;
|
||||
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
|
||||
}
|
||||
|
||||
pub fn create_account_with_authorized(
|
||||
node_pubkey: &Pubkey,
|
||||
authorized_voter: &Pubkey,
|
||||
@ -1194,13 +1390,12 @@ mod tests {
|
||||
vote_state
|
||||
.votes
|
||||
.resize(MAX_LOCKOUT_HISTORY, Lockout::default());
|
||||
vote_state.root_slot = Some(1);
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err());
|
||||
VoteState::serialize(&versioned, &mut buffer).unwrap();
|
||||
assert_eq!(
|
||||
VoteStateVersions::new_current(VoteState::deserialize(&buffer).unwrap()),
|
||||
versioned
|
||||
);
|
||||
let des = VoteState::deserialize(&buffer).unwrap();
|
||||
assert_eq!(des, versioned.convert_to_current(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use {
|
||||
crate::{
|
||||
vote_instruction::{self, VoteInstruction},
|
||||
vote_state::Vote,
|
||||
vote_state::{Vote, VoteTransaction},
|
||||
},
|
||||
solana_sdk::{
|
||||
clock::Slot,
|
||||
@ -14,14 +14,30 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
pub type ParsedVote = (Pubkey, Vote, Option<Hash>);
|
||||
pub type ParsedVote = (Pubkey, Box<dyn VoteTransaction>, Option<Hash>);
|
||||
|
||||
fn parse_vote(vote_ix: &CompiledInstruction, vote_key: &Pubkey) -> Option<ParsedVote> {
|
||||
let vote_instruction = limited_deserialize(&vote_ix.data).ok();
|
||||
vote_instruction.and_then(|vote_instruction| match vote_instruction {
|
||||
VoteInstruction::Vote(vote) => Some((*vote_key, vote, None)),
|
||||
VoteInstruction::VoteSwitch(vote, hash) => Some((*vote_key, vote, Some(hash))),
|
||||
_ => None,
|
||||
vote_instruction.and_then(|vote_instruction| {
|
||||
let result: Option<ParsedVote> = match vote_instruction {
|
||||
VoteInstruction::Vote(vote) => Some((*vote_key, Box::new(vote), None)),
|
||||
VoteInstruction::VoteSwitch(vote, hash) => {
|
||||
Some((*vote_key, Box::new(vote), Some(hash)))
|
||||
}
|
||||
VoteInstruction::UpdateVoteState(vote_state_update) => {
|
||||
Some((*vote_key, Box::new(vote_state_update), None))
|
||||
}
|
||||
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
|
||||
Some((*vote_key, Box::new(vote_state_update), Some(hash)))
|
||||
}
|
||||
VoteInstruction::Authorize(_, _)
|
||||
| VoteInstruction::AuthorizeChecked(_)
|
||||
| VoteInstruction::InitializeAccount(_)
|
||||
| VoteInstruction::UpdateCommission(_)
|
||||
| VoteInstruction::UpdateValidatorIdentity
|
||||
| VoteInstruction::Withdraw(_) => None,
|
||||
};
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,7 +139,10 @@ mod test {
|
||||
);
|
||||
let (key, vote, hash) = parse_vote_transaction(&vote_tx).unwrap();
|
||||
assert_eq!(hash, input_hash);
|
||||
assert_eq!(vote, Vote::new(vec![42], bank_hash));
|
||||
assert_eq!(
|
||||
*vote.as_any().downcast_ref::<Vote>().unwrap(),
|
||||
Vote::new(vec![42], bank_hash)
|
||||
);
|
||||
assert_eq!(key, vote_keypair.pubkey());
|
||||
|
||||
// Test bad program id fails
|
||||
|
Reference in New Issue
Block a user