parent
b18e4057bb
commit
39282be486
|
@ -457,7 +457,7 @@ pub fn process_show_vote_account(
|
||||||
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
||||||
);
|
);
|
||||||
println!("Validator Identity: {}", vote_state.node_pubkey);
|
println!("Validator Identity: {}", vote_state.node_pubkey);
|
||||||
println!("Authorized Voter: {}", vote_state.authorized_voter);
|
println!("Authorized Voter: {:?}", vote_state.authorized_voters());
|
||||||
println!(
|
println!(
|
||||||
"Authorized Withdrawer: {}",
|
"Authorized Withdrawer: {}",
|
||||||
vote_state.authorized_withdrawer
|
vote_state.authorized_withdrawer
|
||||||
|
|
|
@ -35,6 +35,7 @@ use solana_metrics::datapoint_info;
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{Slot, DEFAULT_SLOTS_PER_TURN},
|
clock::{Slot, DEFAULT_SLOTS_PER_TURN},
|
||||||
|
epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
|
||||||
genesis_config::GenesisConfig,
|
genesis_config::GenesisConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
@ -562,6 +563,14 @@ fn new_banks_from_blockstore(
|
||||||
error!("Failed to load genesis from {:?}: {}", blockstore_path, err);
|
error!("Failed to load genesis from {:?}: {}", blockstore_path, err);
|
||||||
process::exit(1);
|
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();
|
let genesis_hash = genesis_config.hash();
|
||||||
info!("genesis hash: {}", genesis_hash);
|
info!("genesis hash: {}", genesis_hash);
|
||||||
|
|
||||||
|
|
|
@ -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<u64, Pubkey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Pubkey> {
|
||||||
|
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<Pubkey> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod authorized_voters;
|
||||||
pub mod vote_instruction;
|
pub mod vote_instruction;
|
||||||
pub mod vote_state;
|
pub mod vote_state;
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,11 @@ pub fn update_node(
|
||||||
authorized_voter_pubkey: &Pubkey,
|
authorized_voter_pubkey: &Pubkey,
|
||||||
node_pubkey: &Pubkey,
|
node_pubkey: &Pubkey,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let account_metas =
|
let account_metas = vec![
|
||||||
vec![AccountMeta::new(*vote_pubkey, false)].with_signer(authorized_voter_pubkey);
|
AccountMeta::new(*vote_pubkey, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
]
|
||||||
|
.with_signer(authorized_voter_pubkey);
|
||||||
|
|
||||||
Instruction::new(
|
Instruction::new(
|
||||||
id(),
|
id(),
|
||||||
|
@ -205,9 +208,12 @@ pub fn process_instruction(
|
||||||
&signers,
|
&signers,
|
||||||
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
),
|
),
|
||||||
VoteInstruction::UpdateNode(node_pubkey) => {
|
VoteInstruction::UpdateNode(node_pubkey) => vote_state::update_node(
|
||||||
vote_state::update_node(me, &node_pubkey, &signers)
|
me,
|
||||||
}
|
&node_pubkey,
|
||||||
|
&signers,
|
||||||
|
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
),
|
||||||
VoteInstruction::Vote(vote) => {
|
VoteInstruction::Vote(vote) => {
|
||||||
inc_new_counter_info!("vote-native", 1);
|
inc_new_counter_info!("vote-native", 1);
|
||||||
vote_state::process_vote(
|
vote_state::process_vote(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(clippy::implicit_hasher)]
|
#![allow(clippy::implicit_hasher)]
|
||||||
//! Vote state, vote program
|
//! Vote state, vote program
|
||||||
//! Receive and processes votes from validators
|
//! Receive and processes votes from validators
|
||||||
|
use crate::authorized_voters::AuthorizedVoters;
|
||||||
use crate::{id, vote_instruction::VoteError};
|
use crate::{id, vote_instruction::VoteError};
|
||||||
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
@ -9,6 +10,7 @@ use solana_sdk::{
|
||||||
account::{Account, KeyedAccount},
|
account::{Account, KeyedAccount},
|
||||||
account_utils::State,
|
account_utils::State,
|
||||||
clock::{Epoch, Slot, UnixTimestamp},
|
clock::{Epoch, Slot, UnixTimestamp},
|
||||||
|
epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::InstructionError,
|
instruction::InstructionError,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
@ -104,9 +106,10 @@ const MAX_ITEMS: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||||
pub struct CircBuf<I> {
|
pub struct CircBuf<I> {
|
||||||
pub buf: [I; MAX_ITEMS],
|
buf: [I; MAX_ITEMS],
|
||||||
/// next pointer
|
/// next pointer
|
||||||
pub idx: usize,
|
idx: usize,
|
||||||
|
is_empty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Default + Copy> Default for CircBuf<I> {
|
impl<I: Default + Copy> Default for CircBuf<I> {
|
||||||
|
@ -114,6 +117,7 @@ impl<I: Default + Copy> Default for CircBuf<I> {
|
||||||
Self {
|
Self {
|
||||||
buf: [I::default(); MAX_ITEMS],
|
buf: [I::default(); MAX_ITEMS],
|
||||||
idx: MAX_ITEMS - 1,
|
idx: MAX_ITEMS - 1,
|
||||||
|
is_empty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,6 +129,19 @@ impl<I> CircBuf<I> {
|
||||||
self.idx %= MAX_ITEMS;
|
self.idx %= MAX_ITEMS;
|
||||||
|
|
||||||
self.buf[self.idx] = item;
|
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
|
/// the node that votes in this account
|
||||||
pub node_pubkey: Pubkey,
|
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
|
/// the signer for withdrawals
|
||||||
pub authorized_withdrawer: Pubkey,
|
pub authorized_withdrawer: Pubkey,
|
||||||
/// percentage (0-100) that represents what part of a rewards
|
/// percentage (0-100) that represents what part of a rewards
|
||||||
|
@ -151,6 +159,14 @@ pub struct VoteState {
|
||||||
pub votes: VecDeque<Lockout>,
|
pub votes: VecDeque<Lockout>,
|
||||||
pub root_slot: Option<u64>,
|
pub root_slot: Option<u64>,
|
||||||
|
|
||||||
|
/// 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
|
/// history of how many credits earned by the end of each epoch
|
||||||
/// each tuple is (Epoch, credits, prev_credits)
|
/// each tuple is (Epoch, credits, prev_credits)
|
||||||
epoch_credits: Vec<(Epoch, u64, u64)>,
|
epoch_credits: Vec<(Epoch, u64, u64)>,
|
||||||
|
@ -163,14 +179,25 @@ impl VoteState {
|
||||||
pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
|
pub fn new(vote_init: &VoteInit, clock: &Clock) -> Self {
|
||||||
Self {
|
Self {
|
||||||
node_pubkey: vote_init.node_pubkey,
|
node_pubkey: vote_init.node_pubkey,
|
||||||
authorized_voter: vote_init.authorized_voter,
|
authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter),
|
||||||
authorized_voter_epoch: clock.epoch,
|
|
||||||
authorized_withdrawer: vote_init.authorized_withdrawer,
|
authorized_withdrawer: vote_init.authorized_withdrawer,
|
||||||
commission: vote_init.commission,
|
commission: vote_init.commission,
|
||||||
..VoteState::default()
|
..VoteState::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_authorized_voter(&self, epoch: u64) -> Option<Pubkey> {
|
||||||
|
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 {
|
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
|
||||||
rent.minimum_balance(VoteState::size_of())
|
rent.minimum_balance(VoteState::size_of())
|
||||||
}
|
}
|
||||||
|
@ -178,10 +205,7 @@ impl VoteState {
|
||||||
pub fn size_of() -> usize {
|
pub fn size_of() -> usize {
|
||||||
// Upper limit on the size of the Vote State. Equal to
|
// Upper limit on the size of the Vote State. Equal to
|
||||||
// size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
|
// size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
|
||||||
let mut vote_state = Self::default();
|
let vote_state = Self::get_max_sized_vote_state();
|
||||||
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];
|
|
||||||
serialized_size(&vote_state).unwrap() as usize
|
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(
|
fn check_slots_are_valid(
|
||||||
&self,
|
&self,
|
||||||
vote: &Vote,
|
vote: &Vote,
|
||||||
|
@ -272,6 +310,7 @@ impl VoteState {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote(
|
pub fn process_vote(
|
||||||
&mut self,
|
&mut self,
|
||||||
vote: &Vote,
|
vote: &Vote,
|
||||||
|
@ -386,6 +425,80 @@ impl VoteState {
|
||||||
&self.epoch_credits
|
&self.epoch_credits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_new_authorized_voter<F>(
|
||||||
|
&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<Pubkey> {
|
||||||
|
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) {
|
fn pop_expired_votes(&mut self, slot: Slot) {
|
||||||
loop {
|
loop {
|
||||||
if self.votes.back().map_or(false, |v| v.is_expired(slot)) {
|
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"
|
// current authorized signer must say "yay"
|
||||||
match vote_authorize {
|
match vote_authorize {
|
||||||
VoteAuthorize::Voter => {
|
VoteAuthorize::Voter => {
|
||||||
verify_authorized_signer(&vote_state.authorized_voter, signers)?;
|
vote_state.set_new_authorized_voter(
|
||||||
// only one re-authorization supported per epoch
|
authorized,
|
||||||
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,
|
|
||||||
clock.epoch,
|
clock.epoch,
|
||||||
clock.slot,
|
clock.leader_schedule_epoch + 1,
|
||||||
));
|
|epoch_authorized_voter| verify_authorized_signer(&epoch_authorized_voter, signers),
|
||||||
vote_state.authorized_voter = *authorized;
|
)?;
|
||||||
vote_state.authorized_voter_epoch = clock.epoch;
|
|
||||||
}
|
}
|
||||||
VoteAuthorize::Withdrawer => {
|
VoteAuthorize::Withdrawer => {
|
||||||
verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
|
verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
|
||||||
|
@ -468,11 +573,15 @@ pub fn update_node(
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
node_pubkey: &Pubkey,
|
node_pubkey: &Pubkey,
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
|
clock: &Clock,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut vote_state: VoteState = vote_account.state()?;
|
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"
|
// 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;
|
vote_state.node_pubkey = *node_pubkey;
|
||||||
|
|
||||||
|
@ -519,7 +628,7 @@ pub fn initialize_account(
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let vote_state: VoteState = vote_account.state()?;
|
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);
|
return Err(InstructionError::AccountAlreadyInitialized);
|
||||||
}
|
}
|
||||||
vote_account.set_state(&VoteState::new(vote_init, clock))
|
vote_account.set_state(&VoteState::new(vote_init, clock))
|
||||||
|
@ -534,11 +643,14 @@ pub fn process_vote(
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut vote_state: VoteState = vote_account.state()?;
|
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);
|
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)?;
|
vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
|
||||||
if let Some(timestamp) = vote.timestamp {
|
if let Some(timestamp) = vote.timestamp {
|
||||||
|
@ -705,7 +817,11 @@ mod tests {
|
||||||
let (vote_pubkey, vote_account) = create_test_account();
|
let (vote_pubkey, vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote_state: VoteState = vote_account.borrow().state().unwrap();
|
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());
|
assert!(vote_state.votes.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,14 +879,33 @@ mod tests {
|
||||||
|
|
||||||
let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)];
|
let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)];
|
||||||
let signers = get_signers(keyed_accounts);
|
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));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
let vote_state: VoteState = vote_account.borrow().state().unwrap();
|
let vote_state: VoteState = vote_account.borrow().state().unwrap();
|
||||||
assert!(vote_state.node_pubkey != node_pubkey);
|
assert!(vote_state.node_pubkey != node_pubkey);
|
||||||
|
|
||||||
let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)];
|
let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)];
|
||||||
let signers = get_signers(keyed_accounts);
|
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(()));
|
assert_eq!(res, Ok(()));
|
||||||
let vote_state: VoteState = vote_account.borrow().state().unwrap();
|
let vote_state: VoteState = vote_account.borrow().state().unwrap();
|
||||||
assert_eq!(vote_state.node_pubkey, node_pubkey);
|
assert_eq!(vote_state.node_pubkey, node_pubkey);
|
||||||
|
@ -787,7 +922,11 @@ mod tests {
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&keyed_accounts[0],
|
&keyed_accounts[0],
|
||||||
&[(*vote.slots.last().unwrap(), vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 1,
|
||||||
|
leader_schedule_epoch: 2,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
&vote,
|
&vote,
|
||||||
&signers,
|
&signers,
|
||||||
);
|
);
|
||||||
|
@ -799,7 +938,11 @@ mod tests {
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&keyed_accounts[0],
|
&keyed_accounts[0],
|
||||||
&[(*vote.slots.last().unwrap(), vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 1,
|
||||||
|
leader_schedule_epoch: 2,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
&vote,
|
&vote,
|
||||||
&signers,
|
&signers,
|
||||||
);
|
);
|
||||||
|
@ -816,6 +959,7 @@ mod tests {
|
||||||
&signers,
|
&signers,
|
||||||
&Clock {
|
&Clock {
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
|
leader_schedule_epoch: 2,
|
||||||
..Clock::default()
|
..Clock::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -828,10 +972,16 @@ mod tests {
|
||||||
&authorized_voter_pubkey,
|
&authorized_voter_pubkey,
|
||||||
VoteAuthorize::Voter,
|
VoteAuthorize::Voter,
|
||||||
&signers,
|
&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(
|
let res = authorize(
|
||||||
&keyed_accounts[0],
|
&keyed_accounts[0],
|
||||||
&authorized_voter_pubkey,
|
&authorized_voter_pubkey,
|
||||||
|
@ -839,10 +989,11 @@ mod tests {
|
||||||
&signers,
|
&signers,
|
||||||
&Clock {
|
&Clock {
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
|
leader_schedule_epoch: 2,
|
||||||
..Clock::default()
|
..Clock::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into()));
|
||||||
|
|
||||||
// verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;)
|
// verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;)
|
||||||
let authorized_voter_account = RefCell::new(Account::default());
|
let authorized_voter_account = RefCell::new(Account::default());
|
||||||
|
@ -856,7 +1007,13 @@ mod tests {
|
||||||
&authorized_voter_pubkey,
|
&authorized_voter_pubkey,
|
||||||
VoteAuthorize::Voter,
|
VoteAuthorize::Voter,
|
||||||
&signers,
|
&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(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
|
@ -870,7 +1027,11 @@ mod tests {
|
||||||
&authorized_withdrawer_pubkey,
|
&authorized_withdrawer_pubkey,
|
||||||
VoteAuthorize::Withdrawer,
|
VoteAuthorize::Withdrawer,
|
||||||
&signers,
|
&signers,
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 3,
|
||||||
|
leader_schedule_epoch: 4,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
|
@ -886,7 +1047,11 @@ mod tests {
|
||||||
&authorized_withdrawer_pubkey,
|
&authorized_withdrawer_pubkey,
|
||||||
VoteAuthorize::Withdrawer,
|
VoteAuthorize::Withdrawer,
|
||||||
&signers,
|
&signers,
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 3,
|
||||||
|
leader_schedule_epoch: 4,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
|
@ -897,7 +1062,11 @@ mod tests {
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&keyed_accounts[0],
|
&keyed_accounts[0],
|
||||||
&[(*vote.slots.last().unwrap(), vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 3,
|
||||||
|
leader_schedule_epoch: 4,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
&vote,
|
&vote,
|
||||||
&signers,
|
&signers,
|
||||||
);
|
);
|
||||||
|
@ -914,7 +1083,11 @@ mod tests {
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&keyed_accounts[0],
|
&keyed_accounts[0],
|
||||||
&[(*vote.slots.last().unwrap(), vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock {
|
||||||
|
epoch: 3,
|
||||||
|
leader_schedule_epoch: 4,
|
||||||
|
..Clock::default()
|
||||||
|
},
|
||||||
&vote,
|
&vote,
|
||||||
&signers,
|
&signers,
|
||||||
);
|
);
|
||||||
|
@ -1425,4 +1598,225 @@ mod tests {
|
||||||
vote_state.last_timestamp = BlockTimestamp::default();
|
vote_state.last_timestamp = BlockTimestamp::default();
|
||||||
assert_eq!(vote_state.process_timestamp(0, timestamp), Ok(()));
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,11 @@ pub use crate::clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH};
|
||||||
/// the beginning of epoch X - 1.
|
/// the beginning of epoch X - 1.
|
||||||
pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH;
|
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
|
/// based on MAX_LOCKOUT_HISTORY from vote_program
|
||||||
pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
|
pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
|
||||||
|
|
||||||
|
|
|
@ -399,10 +399,32 @@ fn check_vote_account(
|
||||||
|
|
||||||
let found_vote_account = solana_vote_program::vote_state::VoteState::from(&found_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 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!(
|
return Err(format!(
|
||||||
"account's authorized voter ({}) does not match to the given voting keypair ({}).",
|
"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 {
|
if found_vote_account.node_pubkey != *node_pubkey {
|
||||||
|
|
Loading…
Reference in New Issue