From d821fd29d6e66aeeb4581ea548caf907c54d9ee0 Mon Sep 17 00:00:00 2001 From: carllin Date: Tue, 25 Feb 2020 17:12:01 -0800 Subject: [PATCH] Add versioning (#8348) automerge --- core/src/commitment.rs | 8 +- core/src/consensus.rs | 13 +- core/src/replay_stage.rs | 8 +- programs/stake/src/stake_state.rs | 34 ++-- programs/vote/src/vote_instruction.rs | 3 +- .../src/{vote_state.rs => vote_state/mod.rs} | 168 +++++++++++------- .../vote/src/vote_state/vote_state_0_23_5.rs | 60 +++++++ .../src/vote_state/vote_state_versions.rs | 60 +++++++ runtime/src/bank.rs | 16 +- runtime/src/stakes.rs | 44 ++++- runtime/tests/stake.rs | 6 +- 11 files changed, 318 insertions(+), 102 deletions(-) rename programs/vote/src/{vote_state.rs => vote_state/mod.rs} (91%) create mode 100644 programs/vote/src/vote_state/vote_state_0_23_5.rs create mode 100644 programs/vote/src/vote_state/vote_state_versions.rs diff --git a/core/src/commitment.rs b/core/src/commitment.rs index 80a49f211..a7df5e328 100644 --- a/core/src/commitment.rs +++ b/core/src/commitment.rs @@ -233,7 +233,7 @@ mod tests { use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo}; use solana_sdk::pubkey::Pubkey; use solana_stake_program::stake_state; - use solana_vote_program::vote_state; + use solana_vote_program::vote_state::{self, VoteStateVersions}; #[test] fn test_block_commitment() { @@ -446,13 +446,15 @@ mod tests { let mut vote_state1 = VoteState::from(&vote_account1).unwrap(); vote_state1.process_slot_vote_unchecked(3); vote_state1.process_slot_vote_unchecked(5); - vote_state1.to(&mut vote_account1).unwrap(); + let versioned = VoteStateVersions::Current(Box::new(vote_state1)); + VoteState::to(&versioned, &mut vote_account1).unwrap(); bank.store_account(&pk1, &vote_account1); let mut vote_state2 = VoteState::from(&vote_account2).unwrap(); vote_state2.process_slot_vote_unchecked(9); vote_state2.process_slot_vote_unchecked(10); - vote_state2.to(&mut vote_account2).unwrap(); + let versioned = VoteStateVersions::Current(Box::new(vote_state2)); + VoteState::to(&versioned, &mut vote_account2).unwrap(); bank.store_account(&pk2, &vote_account2); let commitment = AggregateCommitmentService::aggregate_commitment(&ancestors, &bank); diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 6cbe1e899..1a86c7345 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -483,7 +483,10 @@ pub mod test { signature::{Keypair, Signer}, transaction::Transaction, }; - use solana_vote_program::{vote_instruction, vote_state::Vote}; + use solana_vote_program::{ + vote_instruction, + vote_state::{Vote, VoteStateVersions}, + }; use std::collections::{HashMap, VecDeque}; use std::sync::RwLock; use std::{thread::sleep, time::Duration}; @@ -706,9 +709,11 @@ pub mod test { for slot in *votes { vote_state.process_slot_vote_unchecked(*slot); } - vote_state - .serialize(&mut account.data) - .expect("serialize state"); + VoteState::serialize( + &VoteStateVersions::Current(Box::new(vote_state)), + &mut account.data, + ) + .expect("serialize state"); stakes.push((Pubkey::new_rand(), (*lamports, account))); } stakes diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 5d1dea6c6..988fac83a 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -1099,7 +1099,7 @@ pub(crate) mod tests { transaction::TransactionError, }; use solana_stake_program::stake_state; - use solana_vote_program::vote_state::{self, Vote, VoteState}; + use solana_vote_program::vote_state::{self, Vote, VoteState, VoteStateVersions}; use std::{ fs::remove_dir_all, iter, @@ -1134,7 +1134,8 @@ pub(crate) mod tests { let mut vote_account = bank.get_account(&pubkey).unwrap(); let mut vote_state = VoteState::from(&vote_account).unwrap(); vote_state.process_slot_vote_unchecked(slot); - vote_state.to(&mut vote_account).unwrap(); + let versioned = VoteStateVersions::Current(Box::new(vote_state)); + VoteState::to(&versioned, &mut vote_account).unwrap(); bank.store_account(&pubkey, &vote_account); } @@ -1718,7 +1719,8 @@ pub(crate) mod tests { let mut leader_vote_account = bank.get_account(&pubkey).unwrap(); let mut vote_state = VoteState::from(&leader_vote_account).unwrap(); vote_state.process_slot_vote_unchecked(bank.slot()); - vote_state.to(&mut leader_vote_account).unwrap(); + let versioned = VoteStateVersions::Current(Box::new(vote_state)); + VoteState::to(&versioned, &mut leader_vote_account).unwrap(); bank.store_account(&pubkey, &leader_vote_account); } diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 53304c2bc..848dd3cc0 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -14,7 +14,7 @@ use solana_sdk::{ rent::Rent, stake_history::{StakeHistory, StakeHistoryEntry}, }; -use solana_vote_program::vote_state::VoteState; +use solana_vote_program::vote_state::{VoteState, VoteStateVersions}; use std::collections::HashSet; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] @@ -595,7 +595,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { let stake = Stake::new( self.lamports()?.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;) vote_account.unsigned_key(), - &vote_account.state()?, + &State::::state(vote_account)?.convert_to_current(), clock.epoch, config, ); @@ -605,7 +605,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { meta.authorized.check(signers, StakeAuthorize::Staker)?; stake.redelegate( vote_account.unsigned_key(), - &vote_account.state()?, + &State::::state(vote_account)?.convert_to_current(), clock, stake_history, config, @@ -778,7 +778,8 @@ pub fn redeem_rewards( stake_history: Option<&StakeHistory>, ) -> Result<(u64, u64), InstructionError> { if let StakeState::Stake(meta, mut stake) = stake_account.state()? { - let vote_state = vote_account.state()?; + let vote_state: VoteState = + StateMut::::state(vote_account)?.convert_to_current(); if let Some((voters_reward, stakers_reward)) = stake.redeem_rewards(point_value, &vote_state, stake_history) @@ -999,7 +1000,10 @@ mod tests { 100, )); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); - vote_keyed_account.set_state(&vote_state).unwrap(); + let vote_state_credits = vote_state.credits(); + vote_keyed_account + .set_state(&VoteStateVersions::Current(Box::new(vote_state))) + .unwrap(); let stake_pubkey = Pubkey::new_rand(); let stake_lamports = 42; @@ -1057,7 +1061,7 @@ mod tests { deactivation_epoch: std::u64::MAX, ..Delegation::default() }, - credits_observed: vote_state.credits(), + credits_observed: vote_state_credits, ..Stake::default() } ); @@ -1105,7 +1109,7 @@ mod tests { deactivation_epoch: std::u64::MAX, ..Delegation::default() }, - credits_observed: vote_state.credits(), + credits_observed: vote_state_credits, ..Stake::default() } ); @@ -1535,7 +1539,9 @@ mod tests { 100, )); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); - vote_keyed_account.set_state(&VoteState::default()).unwrap(); + vote_keyed_account + .set_state(&VoteStateVersions::Current(Box::new(VoteState::default()))) + .unwrap(); assert_eq!( stake_keyed_account.delegate( &vote_keyed_account, @@ -1624,7 +1630,9 @@ mod tests { 100, )); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); - vote_keyed_account.set_state(&VoteState::default()).unwrap(); + vote_keyed_account + .set_state(&VoteStateVersions::Current(Box::new(VoteState::default()))) + .unwrap(); stake_keyed_account .delegate( @@ -1748,7 +1756,9 @@ mod tests { 100, )); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); - vote_keyed_account.set_state(&VoteState::default()).unwrap(); + vote_keyed_account + .set_state(&VoteStateVersions::Current(Box::new(VoteState::default()))) + .unwrap(); assert_eq!( stake_keyed_account.delegate( &vote_keyed_account, @@ -1857,7 +1867,9 @@ mod tests { 100, )); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); - vote_keyed_account.set_state(&VoteState::default()).unwrap(); + vote_keyed_account + .set_state(&VoteStateVersions::Current(Box::new(VoteState::default()))) + .unwrap(); let signers = vec![stake_pubkey].into_iter().collect(); assert_eq!( stake_keyed_account.delegate( diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index ec33c3536..1aceed926 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -17,6 +17,7 @@ use solana_sdk::{ system_instruction, sysvar::{self, clock::Clock, slot_hashes::SlotHashes, Sysvar}, }; +use std::collections::HashSet; use thiserror::Error; /// Reasons the stake might have had an error @@ -187,7 +188,7 @@ pub fn process_instruction( trace!("process_instruction: {:?}", data); trace!("keyed_accounts: {:?}", keyed_accounts); - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let keyed_accounts = &mut keyed_accounts.iter(); let me = &mut next_keyed_account(keyed_accounts)?; diff --git a/programs/vote/src/vote_state.rs b/programs/vote/src/vote_state/mod.rs similarity index 91% rename from programs/vote/src/vote_state.rs rename to programs/vote/src/vote_state/mod.rs index 7237574ad..b6d6bf27f 100644 --- a/programs/vote/src/vote_state.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -1,4 +1,3 @@ -#![allow(clippy::implicit_hasher)] //! Vote state, vote program //! Receive and processes votes from validators use crate::authorized_voters::AuthorizedVoters; @@ -18,8 +17,13 @@ use solana_sdk::{ slot_hashes::SlotHash, sysvar::clock::Clock, }; +use std::boxed::Box; use std::collections::{HashSet, VecDeque}; +mod vote_state_0_23_5; +pub mod vote_state_versions; +pub use vote_state_versions::*; + // Maximum number of votes to keep around, tightly coupled with epoch_schedule::MIN_SLOTS_PER_EPOCH pub const MAX_LOCKOUT_HISTORY: usize = 31; pub const INITIAL_LOCKOUT: usize = 2; @@ -205,7 +209,7 @@ impl VoteState { pub fn size_of() -> usize { // Upper limit on the size of the Vote State. Equal to // size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY - let vote_state = Self::get_max_sized_vote_state(); + let vote_state = VoteStateVersions::Current(Box::new(Self::get_max_sized_vote_state())); serialized_size(&vote_state).unwrap() as usize } @@ -215,22 +219,26 @@ impl VoteState { } // utility function, used by Stakes, tests - pub fn to(&self, account: &mut Account) -> Option<()> { - Self::serialize(self, &mut account.data).ok() + pub fn to(versioned: &VoteStateVersions, account: &mut Account) -> Option<()> { + Self::serialize(versioned, &mut account.data).ok() } pub fn deserialize(input: &[u8]) -> Result { - deserialize(input).map_err(|_| InstructionError::InvalidAccountData) + deserialize::(&input) + .map(|versioned| versioned.convert_to_current()) + .map_err(|_| InstructionError::InvalidAccountData) } - pub fn serialize(&self, output: &mut [u8]) -> Result<(), InstructionError> { - serialize_into(output, self).map_err(|err| match *err { + pub fn serialize( + versioned: &VoteStateVersions, + output: &mut [u8], + ) -> Result<(), InstructionError> { + serialize_into(output, versioned).map_err(|err| match *err { ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall, _ => InstructionError::GenericError, }) } - // utility function, used by Stakes, tests pub fn credits_from(account: &Account) -> Option { Self::from(account).map(|state| state.credits()) } @@ -540,14 +548,15 @@ impl VoteState { /// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times, /// but will implicitly withdraw authorization from the previously authorized /// key -pub fn authorize( +pub fn authorize( vote_account: &KeyedAccount, authorized: &Pubkey, vote_authorize: VoteAuthorize, - signers: &HashSet, + signers: &HashSet, clock: &Clock, ) -> Result<(), InstructionError> { - let mut vote_state: VoteState = vote_account.state()?; + let mut vote_state: VoteState = + State::::state(vote_account)?.convert_to_current(); // current authorized signer must say "yay" match vote_authorize { @@ -565,17 +574,18 @@ pub fn authorize( } } - vote_account.set_state(&vote_state) + vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state))) } /// Update the node_pubkey, requires signature of the authorized voter -pub fn update_node( +pub fn update_node( vote_account: &KeyedAccount, node_pubkey: &Pubkey, - signers: &HashSet, + signers: &HashSet, clock: &Clock, ) -> Result<(), InstructionError> { - let mut vote_state: VoteState = vote_account.state()?; + let mut vote_state: VoteState = + State::::state(vote_account)?.convert_to_current(); let authorized_voter = vote_state .get_and_update_authorized_voter(clock.epoch) .expect("the clock epoch is monotonically increasing, so authorized voter must be known"); @@ -585,12 +595,12 @@ pub fn update_node( vote_state.node_pubkey = *node_pubkey; - vote_account.set_state(&vote_state) + vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state))) } -fn verify_authorized_signer( +fn verify_authorized_signer( authorized: &Pubkey, - signers: &HashSet, + signers: &HashSet, ) -> Result<(), InstructionError> { if signers.contains(authorized) { Ok(()) @@ -600,13 +610,14 @@ fn verify_authorized_signer( } /// Withdraw funds from the vote account -pub fn withdraw( +pub fn withdraw( vote_account: &KeyedAccount, lamports: u64, to_account: &KeyedAccount, - signers: &HashSet, + signers: &HashSet, ) -> Result<(), InstructionError> { - let vote_state: VoteState = vote_account.state()?; + let vote_state: VoteState = + State::::state(vote_account)?.convert_to_current(); verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; @@ -626,27 +637,31 @@ pub fn initialize_account( vote_init: &VoteInit, clock: &Clock, ) -> Result<(), InstructionError> { - let vote_state: VoteState = vote_account.state()?; + let versioned = State::::state(vote_account)?; - if !vote_state.authorized_voters.is_empty() { + if !versioned.is_uninitialized() { return Err(InstructionError::AccountAlreadyInitialized); } - vote_account.set_state(&VoteState::new(vote_init, clock)) + + vote_account.set_state(&VoteStateVersions::Current(Box::new(VoteState::new( + vote_init, clock, + )))) } -pub fn process_vote( +pub fn process_vote( vote_account: &KeyedAccount, slot_hashes: &[SlotHash], clock: &Clock, vote: &Vote, - signers: &HashSet, + signers: &HashSet, ) -> Result<(), InstructionError> { - let mut vote_state: VoteState = vote_account.state()?; + let versioned = State::::state(vote_account)?; - if vote_state.authorized_voters.is_empty() { + if versioned.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) .expect("the clock epoch is monotonically increasinig, so authorized voter must be known"); @@ -660,7 +675,7 @@ pub fn process_vote( .ok_or_else(|| VoteError::EmptySlots) .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?; } - vote_account.set_state(&vote_state) + vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state))) } // utility function, used by Bank, tests @@ -672,7 +687,7 @@ pub fn create_account( ) -> Account { let mut vote_account = Account::new(lamports, VoteState::size_of(), &id()); - VoteState::new( + let vote_state = VoteState::new( &VoteInit { node_pubkey: *node_pubkey, authorized_voter: *vote_pubkey, @@ -680,9 +695,10 @@ pub fn create_account( commission, }, &Clock::default(), - ) - .to(&mut vote_account) - .unwrap(); + ); + + let versioned = VoteStateVersions::Current(Box::new(vote_state)); + VoteState::to(&versioned, &mut vote_account).unwrap(); vote_account } @@ -771,7 +787,7 @@ mod tests { epoch: Epoch, ) -> Result { let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); process_vote( &keyed_accounts[0], slot_hashes, @@ -782,7 +798,8 @@ mod tests { &vote.clone(), &signers, )?; - vote_account.borrow().state() + StateMut::::state(&*vote_account.borrow()) + .map(|versioned| versioned.convert_to_current()) } /// exercises all the keyed accounts stuff @@ -807,16 +824,22 @@ mod tests { vote_state .votes .resize(MAX_LOCKOUT_HISTORY, Lockout::default()); - assert!(vote_state.serialize(&mut buffer[0..4]).is_err()); - vote_state.serialize(&mut buffer).unwrap(); - assert_eq!(VoteState::deserialize(&buffer).unwrap(), vote_state); + let versioned = VoteStateVersions::Current(Box::new(vote_state)); + assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err()); + VoteState::serialize(&versioned, &mut buffer).unwrap(); + assert_eq!( + VoteStateVersions::Current(Box::new(VoteState::deserialize(&buffer).unwrap())), + versioned + ); } #[test] fn test_voter_registration() { let (vote_pubkey, vote_account) = create_test_account(); - let vote_state: VoteState = vote_account.borrow().state().unwrap(); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); assert_eq!(vote_state.authorized_voters.len(), 1); assert_eq!( *vote_state.authorized_voters.first().unwrap().1, @@ -878,7 +901,7 @@ mod tests { let node_pubkey = Pubkey::new_rand(); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = update_node( &keyed_accounts[0], &node_pubkey, @@ -886,11 +909,13 @@ mod tests { &Clock::default(), ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - let vote_state: VoteState = vote_account.borrow().state().unwrap(); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); assert!(vote_state.node_pubkey != node_pubkey); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = update_node( &keyed_accounts[0], &node_pubkey, @@ -898,16 +923,20 @@ mod tests { &Clock::default(), ); assert_eq!(res, Ok(())); - let vote_state: VoteState = vote_account.borrow().state().unwrap(); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); assert_eq!(vote_state.node_pubkey, node_pubkey); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let mut clock = Clock::default(); clock.epoch += 10; let res = update_node(&keyed_accounts[0], &node_pubkey, &signers, &clock); assert_eq!(res, Ok(())); - let vote_state: VoteState = vote_account.borrow().state().unwrap(); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); assert_eq!(vote_state.node_pubkey, node_pubkey); } @@ -918,7 +947,7 @@ mod tests { // unsigned let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], @@ -934,7 +963,7 @@ mod tests { // signed let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], @@ -950,7 +979,7 @@ mod tests { // another voter, unsigned let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let authorized_voter_pubkey = Pubkey::new_rand(); let res = authorize( &keyed_accounts[0], @@ -966,7 +995,7 @@ mod tests { assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_voter_pubkey, @@ -981,7 +1010,7 @@ mod tests { assert_eq!(res, Ok(())); // Already set an authorized voter earlier for leader_schedule_epoch == 2 - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_voter_pubkey, @@ -1001,7 +1030,7 @@ mod tests { KeyedAccount::new(&vote_pubkey, false, &vote_account), KeyedAccount::new(&authorized_voter_pubkey, true, &authorized_voter_account), ]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_voter_pubkey, @@ -1020,7 +1049,7 @@ mod tests { // authorize another withdrawer // another voter let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let authorized_withdrawer_pubkey = Pubkey::new_rand(); let res = authorize( &keyed_accounts[0], @@ -1041,7 +1070,7 @@ mod tests { KeyedAccount::new(&vote_pubkey, false, &vote_account), KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), ]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_withdrawer_pubkey, @@ -1057,7 +1086,7 @@ mod tests { // not signed by authorized voter let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let vote = Vote::new(vec![2], Hash::default()); let res = process_vote( &keyed_accounts[0], @@ -1078,7 +1107,7 @@ mod tests { KeyedAccount::new(&vote_pubkey, false, &vote_account), KeyedAccount::new(&authorized_voter_pubkey, true, &authorized_voter_account), ]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let vote = Vote::new(vec![2], Hash::default()); let res = process_vote( &keyed_accounts[0], @@ -1111,7 +1140,10 @@ mod tests { fn test_vote_lockout() { let (_vote_pubkey, vote_account) = create_test_account(); - let mut vote_state: VoteState = vote_account.borrow().state().unwrap(); + let mut vote_state: VoteState = + StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); for i in 0..(MAX_LOCKOUT_HISTORY + 1) { vote_state.process_slot_vote_unchecked((INITIAL_LOCKOUT as usize * i) as u64); @@ -1421,7 +1453,7 @@ mod tests { // unsigned request let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = withdraw( &keyed_accounts[0], 0, @@ -1436,7 +1468,7 @@ mod tests { // insufficient funds let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = withdraw( &keyed_accounts[0], 101, @@ -1453,7 +1485,7 @@ mod tests { let to_account = RefCell::new(Account::default()); let lamports = vote_account.borrow().lamports; let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = withdraw( &keyed_accounts[0], lamports, @@ -1470,7 +1502,7 @@ mod tests { // authorize authorized_withdrawer let authorized_withdrawer_pubkey = Pubkey::new_rand(); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_withdrawer_pubkey, @@ -1486,7 +1518,7 @@ mod tests { KeyedAccount::new(&vote_pubkey, false, &vote_account), KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), ]; - let signers = get_signers(keyed_accounts); + let signers: HashSet = get_signers(keyed_accounts); let keyed_accounts = &mut keyed_accounts.iter(); let vote_keyed_account = next_keyed_account(keyed_accounts).unwrap(); let withdrawer_keyed_account = next_keyed_account(keyed_accounts).unwrap(); @@ -1802,21 +1834,25 @@ mod tests { #[test] fn test_vote_state_max_size() { let mut max_sized_data = vec![0; VoteState::size_of()]; - let mut vote_state = VoteState::get_max_sized_vote_state(); + let vote_state = VoteState::get_max_sized_vote_state(); let (start_leader_schedule_epoch, _) = vote_state.authorized_voters.last().unwrap(); let start_current_epoch = start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1; + let mut vote_state = Some(vote_state); for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET { - vote_state - .set_new_authorized_voter( + vote_state.as_mut().map(|vote_state| { + vote_state.set_new_authorized_voter( &Pubkey::new_rand(), i, i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET, |_| Ok(()), ) - .unwrap(); - vote_state.serialize(&mut max_sized_data).unwrap(); + }); + + let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap())); + VoteState::serialize(&versioned, &mut max_sized_data).unwrap(); + vote_state = Some(versioned.convert_to_current()); } } } diff --git a/programs/vote/src/vote_state/vote_state_0_23_5.rs b/programs/vote/src/vote_state/vote_state_0_23_5.rs new file mode 100644 index 000000000..89b99dc2f --- /dev/null +++ b/programs/vote/src/vote_state/vote_state_0_23_5.rs @@ -0,0 +1,60 @@ +use super::*; + +const MAX_ITEMS: usize = 32; + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct VoteState0_23_5 { + /// the node that votes in this account + pub node_pubkey: Pubkey, + + /// the signer for vote transactions + pub authorized_voter: Pubkey, + /// when the authorized voter was set/initialized + pub authorized_voter_epoch: Epoch, + + /// history of prior authorized voters and the epoch ranges for which + /// they were set + pub prior_voters: CircBuf<(Pubkey, Epoch, Epoch, Slot)>, + + /// the signer for withdrawals + pub authorized_withdrawer: Pubkey, + /// percentage (0-100) that represents what part of a rewards + /// payout should be given to this VoteAccount + pub commission: u8, + + pub votes: VecDeque, + pub root_slot: Option, + + /// history of how many credits earned by the end of each epoch + /// each tuple is (Epoch, credits, prev_credits) + pub epoch_credits: Vec<(Epoch, u64, u64)>, + + /// most recent timestamp submitted with a vote + pub last_timestamp: BlockTimestamp, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct CircBuf { + pub buf: [I; MAX_ITEMS], + /// next pointer + pub idx: usize, +} + +impl Default for CircBuf { + fn default() -> Self { + Self { + buf: [I::default(); MAX_ITEMS], + idx: MAX_ITEMS - 1, + } + } +} + +impl CircBuf { + pub fn append(&mut self, item: I) { + // remember prior delegate and when we switched, to support later slashing + self.idx += 1; + self.idx %= MAX_ITEMS; + + self.buf[self.idx] = item; + } +} diff --git a/programs/vote/src/vote_state/vote_state_versions.rs b/programs/vote/src/vote_state/vote_state_versions.rs new file mode 100644 index 000000000..b80d83ee4 --- /dev/null +++ b/programs/vote/src/vote_state/vote_state_versions.rs @@ -0,0 +1,60 @@ +use super::*; +use crate::vote_state::vote_state_0_23_5::VoteState0_23_5; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum VoteStateVersions { + V0_23_5(Box), + Current(Box), +} + +impl VoteStateVersions { + pub fn convert_to_current(self) -> VoteState { + match self { + VoteStateVersions::V0_23_5(state) => { + let authorized_voters = + AuthorizedVoters::new(state.authorized_voter_epoch, state.authorized_voter); + + VoteState { + node_pubkey: state.node_pubkey, + + /// the signer for withdrawals + authorized_withdrawer: state.authorized_withdrawer, + + /// percentage (0-100) that represents what part of a rewards + /// payout should be given to this VoteAccount + commission: state.commission, + + votes: state.votes.clone(), + + root_slot: state.root_slot, + + /// the signer for vote transactions + authorized_voters, + + /// history of prior authorized voters and the epochs for which + /// they were set, the bottom end of the range is inclusive, + /// the top of the range is exclusive + prior_voters: CircBuf::default(), + + /// history of how many credits earned by the end of each epoch + /// each tuple is (Epoch, credits, prev_credits) + epoch_credits: state.epoch_credits.clone(), + + /// most recent timestamp submitted with a vote + last_timestamp: state.last_timestamp.clone(), + } + } + VoteStateVersions::Current(state) => *state, + } + } + + pub fn is_uninitialized(&self) -> bool { + match self { + VoteStateVersions::V0_23_5(vote_state) => { + vote_state.authorized_voter == Pubkey::default() + } + + VoteStateVersions::Current(vote_state) => vote_state.authorized_voters.is_empty(), + } + } +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index da1709985..b1ff81893 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2172,6 +2172,7 @@ mod tests { stake_instruction, stake_state::{self, Authorized, Delegation, Lockup, Stake}, }; + use solana_vote_program::vote_state::VoteStateVersions; use solana_vote_program::{ vote_instruction, vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY}, @@ -3069,11 +3070,20 @@ mod tests { bank.store_account(&archiver_id, &archiver_account); // generate some rewards - let mut vote_state = VoteState::from(&vote_account).unwrap(); + let mut vote_state = Some(VoteState::from(&vote_account).unwrap()); for i in 0..MAX_LOCKOUT_HISTORY + 42 { - vote_state.process_slot_vote_unchecked(i as u64); - vote_state.to(&mut vote_account).unwrap(); + vote_state + .as_mut() + .map(|v| v.process_slot_vote_unchecked(i as u64)); + let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap())); + VoteState::to(&versioned, &mut vote_account).unwrap(); bank.store_account(&vote_id, &vote_account); + match versioned { + VoteStateVersions::Current(v) => { + vote_state = Some(*v); + } + _ => panic!("Has to be of type Current"), + }; } bank.store_account(&vote_id, &vote_account); diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 4b31123d5..ba180330e 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -195,7 +195,9 @@ pub mod tests { use super::*; use solana_sdk::{pubkey::Pubkey, rent::Rent}; use solana_stake_program::stake_state; - use solana_vote_program::vote_state::{self, VoteState, MAX_LOCKOUT_HISTORY}; + use solana_vote_program::vote_state::{ + self, VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY, + }; // set up some dummies for a staked node (( vote ) ( stake )) pub fn create_staked_node_accounts(stake: u64) -> ((Pubkey, Account), (Pubkey, Account)) { @@ -319,31 +321,55 @@ pub mod tests { assert_eq!(stakes.points(), 0); assert_eq!(stakes.claim_points(), 0); - let mut vote_state = VoteState::from(&vote_account).unwrap(); + let mut vote_state = Some(VoteState::from(&vote_account).unwrap()); for i in 0..MAX_LOCKOUT_HISTORY + 42 { - vote_state.process_slot_vote_unchecked(i as u64); - vote_state.to(&mut vote_account).unwrap(); + vote_state + .as_mut() + .map(|v| v.process_slot_vote_unchecked(i as u64)); + let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap())); + VoteState::to(&versioned, &mut vote_account).unwrap(); + match versioned { + VoteStateVersions::Current(v) => { + vote_state = Some(*v); + } + _ => panic!("Has to be of type Current"), + }; stakes.store(&vote_pubkey, &vote_account); - assert_eq!(stakes.points(), vote_state.credits() * stake); + assert_eq!( + stakes.points(), + vote_state.as_ref().unwrap().credits() * stake + ); } vote_account.lamports = 0; stakes.store(&vote_pubkey, &vote_account); - assert_eq!(stakes.points(), vote_state.credits() * stake); + assert_eq!( + stakes.points(), + vote_state.as_ref().unwrap().credits() * stake + ); - assert_eq!(stakes.claim_points(), vote_state.credits() * stake); + assert_eq!( + stakes.claim_points(), + vote_state.as_ref().unwrap().credits() * stake + ); assert_eq!(stakes.claim_points(), 0); assert_eq!(stakes.claim_points(), 0); // points come out of nowhere, but don't care here ;) vote_account.lamports = 1; stakes.store(&vote_pubkey, &vote_account); - assert_eq!(stakes.points(), vote_state.credits() * stake); + assert_eq!( + stakes.points(), + vote_state.as_ref().unwrap().credits() * stake + ); // test going backwards, should never go backwards let old_vote_state = vote_state; let vote_account = vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1); stakes.store(&vote_pubkey, &vote_account); - assert_eq!(stakes.points(), old_vote_state.credits() * stake); + assert_eq!( + stakes.points(), + old_vote_state.as_ref().unwrap().credits() * stake + ); } #[test] diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index f8ddd5def..6388c08c7 100644 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -18,7 +18,7 @@ use solana_stake_program::{ }; use solana_vote_program::{ vote_instruction, - vote_state::{Vote, VoteInit, VoteState}, + vote_state::{Vote, VoteInit, VoteState, VoteStateVersions}, }; use std::sync::Arc; @@ -254,7 +254,9 @@ fn test_stake_account_lifetime() { // Test that votes and credits are there let account = bank.get_account(&vote_pubkey).expect("account not found"); - let vote_state: VoteState = account.state().expect("couldn't unpack account data"); + let vote_state: VoteState = StateMut::::state(&account) + .expect("couldn't unpack account data") + .convert_to_current(); // 1 less vote, as the first vote should have cleared the lockout assert_eq!(vote_state.votes.len(), 31);