From 39282be48626f761e4be63b0f79eda50229491f3 Mon Sep 17 00:00:00 2001 From: carllin Date: Mon, 24 Feb 2020 19:27:04 -0800 Subject: [PATCH] Determine vote_state ahead of time (#8303) automerge --- cli/src/vote.rs | 2 +- core/src/validator.rs | 9 + programs/vote/src/authorized_voters.rs | 102 ++++++ programs/vote/src/lib.rs | 1 + programs/vote/src/vote_instruction.rs | 16 +- programs/vote/src/vote_state.rs | 488 ++++++++++++++++++++++--- sdk/src/epoch_schedule.rs | 5 + validator/src/main.rs | 26 +- 8 files changed, 594 insertions(+), 55 deletions(-) create mode 100644 programs/vote/src/authorized_voters.rs diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 33c1c6db40..684784dd27 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -457,7 +457,7 @@ pub fn process_show_vote_account( build_balance_message(vote_account.lamports, use_lamports_unit, true) ); println!("Validator Identity: {}", vote_state.node_pubkey); - println!("Authorized Voter: {}", vote_state.authorized_voter); + println!("Authorized Voter: {:?}", vote_state.authorized_voters()); println!( "Authorized Withdrawer: {}", vote_state.authorized_withdrawer diff --git a/core/src/validator.rs b/core/src/validator.rs index e78face1f0..4468f14264 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -35,6 +35,7 @@ use solana_metrics::datapoint_info; use solana_runtime::bank::Bank; use solana_sdk::{ clock::{Slot, DEFAULT_SLOTS_PER_TURN}, + epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET, genesis_config::GenesisConfig, hash::Hash, pubkey::Pubkey, @@ -562,6 +563,14 @@ fn new_banks_from_blockstore( error!("Failed to load genesis from {:?}: {}", blockstore_path, err); process::exit(1); }); + + // This needs to be limited otherwise the state in the VoteAccount data + // grows too large + let leader_schedule_slot_offset = genesis_config.epoch_schedule.leader_schedule_slot_offset; + let slots_per_epoch = genesis_config.epoch_schedule.slots_per_epoch; + let leader_epoch_offset = (leader_schedule_slot_offset + slots_per_epoch - 1) / slots_per_epoch; + assert!(leader_epoch_offset <= MAX_LEADER_SCHEDULE_EPOCH_OFFSET); + let genesis_hash = genesis_config.hash(); info!("genesis hash: {}", genesis_hash); diff --git a/programs/vote/src/authorized_voters.rs b/programs/vote/src/authorized_voters.rs new file mode 100644 index 0000000000..9643148024 --- /dev/null +++ b/programs/vote/src/authorized_voters.rs @@ -0,0 +1,102 @@ +use log::*; +use serde_derive::{Deserialize, Serialize}; +use solana_sdk::pubkey::Pubkey; +use std::collections::BTreeMap; + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct AuthorizedVoters { + authorized_voters: BTreeMap, +} + +impl AuthorizedVoters { + pub fn new(epoch: u64, pubkey: Pubkey) -> Self { + let mut authorized_voters = BTreeMap::new(); + authorized_voters.insert(epoch, pubkey); + Self { authorized_voters } + } + + pub fn get_authorized_voter(&self, epoch: u64) -> Option { + self.get_or_calculate_authorized_voter_for_epoch(epoch) + .map(|(pubkey, _)| pubkey) + } + + pub fn get_and_cache_authorized_voter_for_epoch(&mut self, epoch: u64) -> Option { + let res = self.get_or_calculate_authorized_voter_for_epoch(epoch); + + res.map(|(pubkey, existed)| { + if !existed { + self.authorized_voters.insert(epoch, pubkey); + } + pubkey + }) + } + + pub fn insert(&mut self, epoch: u64, authorized_voter: Pubkey) { + self.authorized_voters.insert(epoch, authorized_voter); + } + + pub fn purge_authorized_voters(&mut self, current_epoch: u64) -> bool { + // Iterate through the keys in order, filtering out the ones + // less than the current epoch + let expired_keys: Vec<_> = self + .authorized_voters + .range(0..current_epoch) + .map(|(authorized_epoch, _)| *authorized_epoch) + .collect(); + + for key in expired_keys { + self.authorized_voters.remove(&key); + } + + // Have to uphold this invariant b/c this is + // 1) The check for whether the vote state is initialized + // 2) How future authorized voters for uninitialized epochs are set + // by this function + assert!(!self.authorized_voters.is_empty()); + true + } + + pub fn is_empty(&self) -> bool { + self.authorized_voters.is_empty() + } + + pub fn first(&self) -> Option<(&u64, &Pubkey)> { + self.authorized_voters.iter().next() + } + + pub fn last(&self) -> Option<(&u64, &Pubkey)> { + self.authorized_voters.iter().next_back() + } + + pub fn len(&self) -> usize { + self.authorized_voters.len() + } + + pub fn contains(&self, epoch: u64) -> bool { + self.authorized_voters.get(&epoch).is_some() + } + + // Returns the authorized voter at the given epoch if the epoch is >= the + // current epoch, and a bool indicating whether the entry for this epoch + // exists in the self.authorized_voter map + fn get_or_calculate_authorized_voter_for_epoch(&self, epoch: u64) -> Option<(Pubkey, bool)> { + let res = self.authorized_voters.get(&epoch); + if res.is_none() { + // If no authorized voter has been set yet for this epoch, + // this must mean the authorized voter remains unchanged + // from the latest epoch before this one + let res = self.authorized_voters.range(0..epoch).next_back(); + + if res.is_none() { + warn!( + "Tried to query for the authorized voter of an epoch earlier + than the current epoch. Earlier epochs have been purged" + ); + } + + res.map(|(_, pubkey)| (*pubkey, false)) + } else { + res.map(|pubkey| (*pubkey, true)) + } + } +} diff --git a/programs/vote/src/lib.rs b/programs/vote/src/lib.rs index b001d1e1b3..481584fcb6 100644 --- a/programs/vote/src/lib.rs +++ b/programs/vote/src/lib.rs @@ -1,3 +1,4 @@ +pub mod authorized_voters; pub mod vote_instruction; pub mod vote_state; diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index f9006fd9b9..ec33c35364 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -138,8 +138,11 @@ pub fn update_node( authorized_voter_pubkey: &Pubkey, node_pubkey: &Pubkey, ) -> Instruction { - let account_metas = - vec![AccountMeta::new(*vote_pubkey, false)].with_signer(authorized_voter_pubkey); + let account_metas = vec![ + AccountMeta::new(*vote_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + ] + .with_signer(authorized_voter_pubkey); Instruction::new( id(), @@ -205,9 +208,12 @@ pub fn process_instruction( &signers, &Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?, ), - VoteInstruction::UpdateNode(node_pubkey) => { - vote_state::update_node(me, &node_pubkey, &signers) - } + VoteInstruction::UpdateNode(node_pubkey) => vote_state::update_node( + me, + &node_pubkey, + &signers, + &Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?, + ), VoteInstruction::Vote(vote) => { inc_new_counter_info!("vote-native", 1); vote_state::process_vote( diff --git a/programs/vote/src/vote_state.rs b/programs/vote/src/vote_state.rs index 323dfc05a5..7237574ad3 100644 --- a/programs/vote/src/vote_state.rs +++ b/programs/vote/src/vote_state.rs @@ -1,6 +1,7 @@ #![allow(clippy::implicit_hasher)] //! Vote state, vote program //! Receive and processes votes from validators +use crate::authorized_voters::AuthorizedVoters; use crate::{id, vote_instruction::VoteError}; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; use log::*; @@ -9,6 +10,7 @@ use solana_sdk::{ account::{Account, KeyedAccount}, account_utils::State, clock::{Epoch, Slot, UnixTimestamp}, + epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET, hash::Hash, instruction::InstructionError, pubkey::Pubkey, @@ -104,9 +106,10 @@ const MAX_ITEMS: usize = 32; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct CircBuf { - pub buf: [I; MAX_ITEMS], + buf: [I; MAX_ITEMS], /// next pointer - pub idx: usize, + idx: usize, + is_empty: bool, } impl Default for CircBuf { @@ -114,6 +117,7 @@ impl Default for CircBuf { Self { buf: [I::default(); MAX_ITEMS], idx: MAX_ITEMS - 1, + is_empty: true, } } } @@ -125,6 +129,19 @@ impl CircBuf { self.idx %= MAX_ITEMS; self.buf[self.idx] = item; + self.is_empty = false; + } + + pub fn buf(&self) -> &[I; MAX_ITEMS] { + &self.buf + } + + pub fn last(&self) -> Option<&I> { + if !self.is_empty { + Some(&self.buf[self.idx]) + } else { + None + } } } @@ -133,15 +150,6 @@ pub struct VoteState { /// 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 @@ -151,6 +159,14 @@ pub struct VoteState { pub votes: VecDeque, pub root_slot: Option, + /// the signer for vote transactions + authorized_voters: AuthorizedVoters, + + /// 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<(Pubkey, Epoch, Epoch)>, + /// history of how many credits earned by the end of each epoch /// each tuple is (Epoch, credits, prev_credits) epoch_credits: Vec<(Epoch, u64, u64)>, @@ -163,14 +179,25 @@ impl VoteState { pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self { Self { node_pubkey: vote_init.node_pubkey, - authorized_voter: vote_init.authorized_voter, - authorized_voter_epoch: clock.epoch, + authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter), authorized_withdrawer: vote_init.authorized_withdrawer, commission: vote_init.commission, ..VoteState::default() } } + pub fn get_authorized_voter(&self, epoch: u64) -> Option { + self.authorized_voters.get_authorized_voter(epoch) + } + + pub fn authorized_voters(&self) -> &AuthorizedVoters { + &self.authorized_voters + } + + pub fn prior_voters(&mut self) -> &CircBuf<(Pubkey, Epoch, Epoch)> { + &self.prior_voters + } + pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 { rent.minimum_balance(VoteState::size_of()) } @@ -178,10 +205,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 mut vote_state = Self::default(); - vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]); - vote_state.root_slot = Some(std::u64::MAX); - vote_state.epoch_credits = vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY]; + let vote_state = Self::get_max_sized_vote_state(); serialized_size(&vote_state).unwrap() as usize } @@ -225,6 +249,20 @@ impl VoteState { } } } + + fn get_max_sized_vote_state() -> VoteState { + let mut vote_state = Self::default(); + vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]); + vote_state.root_slot = Some(std::u64::MAX); + vote_state.epoch_credits = vec![(0, 0, 0); MAX_EPOCH_CREDITS_HISTORY]; + let mut authorized_voters = AuthorizedVoters::default(); + for i in 0..=MAX_LEADER_SCHEDULE_EPOCH_OFFSET { + authorized_voters.insert(i, Pubkey::new_rand()); + } + vote_state.authorized_voters = authorized_voters; + vote_state + } + fn check_slots_are_valid( &self, vote: &Vote, @@ -272,6 +310,7 @@ impl VoteState { } Ok(()) } + pub fn process_vote( &mut self, vote: &Vote, @@ -386,6 +425,80 @@ impl VoteState { &self.epoch_credits } + fn set_new_authorized_voter( + &mut self, + authorized_pubkey: &Pubkey, + current_epoch: u64, + target_epoch: u64, + verify: F, + ) -> Result<(), InstructionError> + where + F: Fn(Pubkey) -> Result<(), InstructionError>, + { + let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch).expect( + "the clock epoch is monotonically increasing, so authorized voter must be known", + ); + + verify(epoch_authorized_voter)?; + + // The offset in slots `n` on which the target_epoch + // (default value `DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET`) is + // calculated is the number of slots available from the + // first slot `S` of an epoch in which to set a new voter for + // the epoch at `S` + `n` + if self.authorized_voters.contains(target_epoch) { + return Err(VoteError::TooSoonToReauthorize.into()); + } + + // Get the latest authorized_voter + let (latest_epoch, latest_authorized_pubkey) = self.authorized_voters.last().expect( + "Earlier call to `get_and_update_authorized_voter()` guarantees + at least the voter for `epoch` exists in the map", + ); + + // If we're not setting the same pubkey as authorized pubkey again, + // then update the list of prior voters to mark the expiration + // of the old authorized pubkey + if latest_authorized_pubkey != authorized_pubkey { + // Update the epoch ranges of authorized pubkeys that will be expired + let epoch_of_last_authorized_switch = + self.prior_voters.last().map(|range| range.2).unwrap_or(0); + + // target_epoch must: + // 1) Be monotonically increasing due to the clock always + // moving forward + // 2) not be equal to latest epoch otherwise this + // function would have returned TooSoonToReauthorize error + // above + assert!(target_epoch > *latest_epoch); + + // Commit the new state + self.prior_voters.append(( + *latest_authorized_pubkey, + epoch_of_last_authorized_switch, + target_epoch, + )); + } + + self.authorized_voters + .insert(target_epoch, *authorized_pubkey); + + Ok(()) + } + + fn get_and_update_authorized_voter(&mut self, current_epoch: u64) -> Option { + let pubkey = self + .authorized_voters + .get_and_cache_authorized_voter_for_epoch(current_epoch) + .expect( + "Internal functions should + only call this will monotonically increasing current_epoch", + ); + self.authorized_voters + .purge_authorized_voters(current_epoch); + Some(pubkey) + } + fn pop_expired_votes(&mut self, slot: Slot) { loop { if self.votes.back().map_or(false, |v| v.is_expired(slot)) { @@ -439,20 +552,12 @@ pub fn authorize( // current authorized signer must say "yay" match vote_authorize { VoteAuthorize::Voter => { - verify_authorized_signer(&vote_state.authorized_voter, signers)?; - // only one re-authorization supported per epoch - if vote_state.authorized_voter_epoch == clock.epoch { - return Err(VoteError::TooSoonToReauthorize.into()); - } - // remember prior - vote_state.prior_voters.append(( - vote_state.authorized_voter, - vote_state.authorized_voter_epoch, + vote_state.set_new_authorized_voter( + authorized, clock.epoch, - clock.slot, - )); - vote_state.authorized_voter = *authorized; - vote_state.authorized_voter_epoch = clock.epoch; + clock.leader_schedule_epoch + 1, + |epoch_authorized_voter| verify_authorized_signer(&epoch_authorized_voter, signers), + )?; } VoteAuthorize::Withdrawer => { verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; @@ -468,11 +573,15 @@ pub fn update_node( vote_account: &KeyedAccount, node_pubkey: &Pubkey, signers: &HashSet, + clock: &Clock, ) -> Result<(), InstructionError> { let mut vote_state: VoteState = vote_account.state()?; + 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"); // current authorized voter must say "yay" - verify_authorized_signer(&vote_state.authorized_voter, signers)?; + verify_authorized_signer(&authorized_voter, signers)?; vote_state.node_pubkey = *node_pubkey; @@ -519,7 +628,7 @@ pub fn initialize_account( ) -> Result<(), InstructionError> { let vote_state: VoteState = vote_account.state()?; - if vote_state.authorized_voter != Pubkey::default() { + if !vote_state.authorized_voters.is_empty() { return Err(InstructionError::AccountAlreadyInitialized); } vote_account.set_state(&VoteState::new(vote_init, clock)) @@ -534,11 +643,14 @@ pub fn process_vote( ) -> Result<(), InstructionError> { let mut vote_state: VoteState = vote_account.state()?; - if vote_state.authorized_voter == Pubkey::default() { + if vote_state.authorized_voters.is_empty() { return Err(InstructionError::UninitializedAccount); } - verify_authorized_signer(&vote_state.authorized_voter, signers)?; + 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"); + verify_authorized_signer(&authorized_voter, signers)?; vote_state.process_vote(vote, slot_hashes, clock.epoch)?; if let Some(timestamp) = vote.timestamp { @@ -705,7 +817,11 @@ mod tests { let (vote_pubkey, vote_account) = create_test_account(); let vote_state: VoteState = vote_account.borrow().state().unwrap(); - assert_eq!(vote_state.authorized_voter, vote_pubkey); + assert_eq!(vote_state.authorized_voters.len(), 1); + assert_eq!( + *vote_state.authorized_voters.first().unwrap().1, + vote_pubkey + ); assert!(vote_state.votes.is_empty()); } @@ -763,14 +879,33 @@ mod tests { let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; let signers = get_signers(keyed_accounts); - let res = update_node(&keyed_accounts[0], &node_pubkey, &signers); + let res = update_node( + &keyed_accounts[0], + &node_pubkey, + &signers, + &Clock::default(), + ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); let vote_state: VoteState = vote_account.borrow().state().unwrap(); assert!(vote_state.node_pubkey != node_pubkey); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; let signers = get_signers(keyed_accounts); - let res = update_node(&keyed_accounts[0], &node_pubkey, &signers); + let res = update_node( + &keyed_accounts[0], + &node_pubkey, + &signers, + &Clock::default(), + ); + assert_eq!(res, Ok(())); + let vote_state: VoteState = vote_account.borrow().state().unwrap(); + 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 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(); assert_eq!(vote_state.node_pubkey, node_pubkey); @@ -787,7 +922,11 @@ mod tests { let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock::default(), + &Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }, &vote, &signers, ); @@ -799,7 +938,11 @@ mod tests { let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock::default(), + &Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }, &vote, &signers, ); @@ -816,6 +959,7 @@ mod tests { &signers, &Clock { epoch: 1, + leader_schedule_epoch: 2, ..Clock::default() }, ); @@ -828,10 +972,16 @@ mod tests { &authorized_voter_pubkey, VoteAuthorize::Voter, &signers, - &Clock::default(), + &Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }, ); - assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into())); + assert_eq!(res, Ok(())); + // Already set an authorized voter earlier for leader_schedule_epoch == 2 + let signers = get_signers(keyed_accounts); let res = authorize( &keyed_accounts[0], &authorized_voter_pubkey, @@ -839,10 +989,11 @@ mod tests { &signers, &Clock { epoch: 1, + leader_schedule_epoch: 2, ..Clock::default() }, ); - assert_eq!(res, Ok(())); + assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into())); // verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) let authorized_voter_account = RefCell::new(Account::default()); @@ -856,7 +1007,13 @@ mod tests { &authorized_voter_pubkey, VoteAuthorize::Voter, &signers, - &Clock::default(), + &Clock { + // The authorized voter was set when leader_schedule_epoch == 2, so will + // take effect when epoch == 3 + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }, ); assert_eq!(res, Ok(())); @@ -870,7 +1027,11 @@ mod tests { &authorized_withdrawer_pubkey, VoteAuthorize::Withdrawer, &signers, - &Clock::default(), + &Clock { + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }, ); assert_eq!(res, Ok(())); @@ -886,7 +1047,11 @@ mod tests { &authorized_withdrawer_pubkey, VoteAuthorize::Withdrawer, &signers, - &Clock::default(), + &Clock { + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }, ); assert_eq!(res, Ok(())); @@ -897,7 +1062,11 @@ mod tests { let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock::default(), + &Clock { + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }, &vote, &signers, ); @@ -914,7 +1083,11 @@ mod tests { let res = process_vote( &keyed_accounts[0], &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock::default(), + &Clock { + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }, &vote, &signers, ); @@ -1425,4 +1598,225 @@ mod tests { vote_state.last_timestamp = BlockTimestamp::default(); assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(())); } + + #[test] + fn test_get_and_update_authorized_voter() { + let original_voter = Pubkey::new_rand(); + let mut vote_state = VoteState::new( + &VoteInit { + node_pubkey: original_voter, + authorized_voter: original_voter, + authorized_withdrawer: original_voter, + commission: 0, + }, + &Clock::default(), + ); + + // If no new authorized voter was set, the same authorized voter + // is locked into the next epoch + assert_eq!( + vote_state.get_and_update_authorized_voter(1).unwrap(), + original_voter + ); + + // Try to get the authorized voter for epoch 5, implies + // the authorized voter for epochs 1-4 were unchanged + assert_eq!( + vote_state.get_and_update_authorized_voter(5).unwrap(), + original_voter + ); + + // Authorized voter for expired epoch 0..5 should have been + // purged and no longer queryable + assert_eq!(vote_state.authorized_voters.len(), 1); + for i in 0..5 { + assert!(vote_state + .authorized_voters + .get_authorized_voter(i) + .is_none()); + } + + // Set an authorized voter change at slot 7 + let new_authorized_voter = Pubkey::new_rand(); + vote_state + .set_new_authorized_voter(&new_authorized_voter, 5, 7, |_| Ok(())) + .unwrap(); + + // Try to get the authorized voter for epoch 6, unchanged + assert_eq!( + vote_state.get_and_update_authorized_voter(6).unwrap(), + original_voter + ); + + // Try to get the authorized voter for epoch 7 and onwards, should + // be the new authorized voter + for i in 7..10 { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + new_authorized_voter + ); + } + assert_eq!(vote_state.authorized_voters.len(), 1); + } + + #[test] + fn test_set_new_authorized_voter() { + let original_voter = Pubkey::new_rand(); + let epoch_offset = 15; + let mut vote_state = VoteState::new( + &VoteInit { + node_pubkey: original_voter, + authorized_voter: original_voter, + authorized_withdrawer: original_voter, + commission: 0, + }, + &Clock::default(), + ); + + assert!(vote_state.prior_voters.last().is_none()); + + let new_voter = Pubkey::new_rand(); + // Set a new authorized voter + vote_state + .set_new_authorized_voter(&new_voter, 0, 0 + epoch_offset, |_| Ok(())) + .unwrap(); + + assert_eq!(vote_state.prior_voters.idx, 0); + assert_eq!( + vote_state.prior_voters.last(), + Some(&(original_voter, 0, 0 + epoch_offset)) + ); + + // Trying to set authorized voter for same epoch again should fail + assert_eq!( + vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(())), + Err(VoteError::TooSoonToReauthorize.into()) + ); + + // Setting the same authorized voter again should succeed + vote_state + .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, |_| Ok(())) + .unwrap(); + + // Set a third and fourth authorized voter + let new_voter2 = Pubkey::new_rand(); + vote_state + .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, |_| Ok(())) + .unwrap(); + assert_eq!(vote_state.prior_voters.idx, 1); + assert_eq!( + vote_state.prior_voters.last(), + Some(&(new_voter, epoch_offset, 3 + epoch_offset)) + ); + + let new_voter3 = Pubkey::new_rand(); + vote_state + .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, |_| Ok(())) + .unwrap(); + assert_eq!(vote_state.prior_voters.idx, 2); + assert_eq!( + vote_state.prior_voters.last(), + Some(&(new_voter2, 3 + epoch_offset, 6 + epoch_offset)) + ); + + // Check can set back to original voter + vote_state + .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, |_| Ok(())) + .unwrap(); + + // Run with these voters for a while, check the ranges of authorized + // voters is correct + for i in 9..epoch_offset { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + original_voter + ); + } + for i in epoch_offset..3 + epoch_offset { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + new_voter + ); + } + for i in 3 + epoch_offset..6 + epoch_offset { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + new_voter2 + ); + } + for i in 6 + epoch_offset..9 + epoch_offset { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + new_voter3 + ); + } + for i in 9 + epoch_offset..=10 + epoch_offset { + assert_eq!( + vote_state.get_and_update_authorized_voter(i).unwrap(), + original_voter + ); + } + } + + #[test] + fn test_authorized_voter_is_locked_within_epoch() { + let original_voter = Pubkey::new_rand(); + let mut vote_state = VoteState::new( + &VoteInit { + node_pubkey: original_voter, + authorized_voter: original_voter, + authorized_withdrawer: original_voter, + commission: 0, + }, + &Clock::default(), + ); + + // Test that it's not possible to set a new authorized + // voter within the same epoch, even if none has been + // explicitly set before + let new_voter = Pubkey::new_rand(); + assert_eq!( + vote_state.set_new_authorized_voter(&new_voter, 1, 1, |_| Ok(())), + Err(VoteError::TooSoonToReauthorize.into()) + ); + + assert_eq!(vote_state.get_authorized_voter(1), Some(original_voter)); + + // Set a new authorized voter for a future epoch + assert_eq!( + vote_state.set_new_authorized_voter(&new_voter, 1, 2, |_| Ok(())), + Ok(()) + ); + + // Test that it's not possible to set a new authorized + // voter within the same epoch, even if none has been + // explicitly set before + assert_eq!( + vote_state.set_new_authorized_voter(&original_voter, 3, 3, |_| Ok(())), + Err(VoteError::TooSoonToReauthorize.into()) + ); + + assert_eq!(vote_state.get_authorized_voter(3), Some(new_voter)); + } + + #[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 (start_leader_schedule_epoch, _) = vote_state.authorized_voters.last().unwrap(); + let start_current_epoch = + start_leader_schedule_epoch - MAX_LEADER_SCHEDULE_EPOCH_OFFSET + 1; + + for i in start_current_epoch..start_current_epoch + 2 * MAX_LEADER_SCHEDULE_EPOCH_OFFSET { + 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(); + } + } } diff --git a/sdk/src/epoch_schedule.rs b/sdk/src/epoch_schedule.rs index b2ce557af3..fb622ead1f 100644 --- a/sdk/src/epoch_schedule.rs +++ b/sdk/src/epoch_schedule.rs @@ -8,6 +8,11 @@ pub use crate::clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}; /// the beginning of epoch X - 1. pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH; +/// The maximum number of slots before an epoch starts to calculate the leader schedule. +/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at +/// the beginning of epoch X - 1. +pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3; + /// based on MAX_LOCKOUT_HISTORY from vote_program pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32; diff --git a/validator/src/main.rs b/validator/src/main.rs index fbc61301c2..244e82cd0f 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -399,10 +399,32 @@ fn check_vote_account( let found_vote_account = solana_vote_program::vote_state::VoteState::from(&found_vote_account); if let Some(found_vote_account) = found_vote_account { - if found_vote_account.authorized_voter != *voting_pubkey { + if found_vote_account.authorized_voters().is_empty() { + return Err("Vote account not yet initialized".to_string()); + } + + let epoch_info = rpc_client + .get_epoch_info() + .map_err(|err| format!("Failed to get epoch info: {}", err.to_string()))?; + + let mut authorized_voter; + authorized_voter = found_vote_account.get_authorized_voter(epoch_info.epoch); + if authorized_voter.is_none() { + // Must have gotten a clock on the boundary + authorized_voter = found_vote_account.get_authorized_voter(epoch_info.epoch + 1); + } + + let authorized_voter = authorized_voter.expect( + "Could not get the authorized voter, which only happens if the + client gets an epoch earlier than the current epoch, + but the received epoch should not be off by more than + one epoch", + ); + + if authorized_voter != *voting_pubkey { return Err(format!( "account's authorized voter ({}) does not match to the given voting keypair ({}).", - found_vote_account.authorized_voter, voting_pubkey + authorized_voter, voting_pubkey )); } if found_vote_account.node_pubkey != *node_pubkey {