Add authorized_voter history (#7643)

* Add authorized_voter history

* fixups

* coverage

* bigger vote state
This commit is contained in:
Rob Walker
2019-12-30 19:57:53 -08:00
committed by GitHub
parent 760a56964f
commit 6b7d9942a7
4 changed files with 149 additions and 33 deletions

View File

@ -455,7 +455,7 @@ mod test {
let mut stakes = vec![]; let mut stakes = vec![];
for (lamports, votes) in stake_votes { for (lamports, votes) in stake_votes {
let mut account = Account::default(); let mut account = Account::default();
account.data = vec![0; 1024]; account.data = vec![0; VoteState::size_of()];
account.lamports = *lamports; account.lamports = *lamports;
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for slot in *votes { for slot in *votes {

View File

@ -104,6 +104,7 @@ pub(crate) mod tests {
create_genesis_config, GenesisConfigInfo, BOOTSTRAP_LEADER_LAMPORTS, create_genesis_config, GenesisConfigInfo, BOOTSTRAP_LEADER_LAMPORTS,
}; };
use solana_sdk::{ use solana_sdk::{
clock::Clock,
instruction::Instruction, instruction::Instruction,
pubkey::Pubkey, pubkey::Pubkey,
signature::{Keypair, KeypairUtil}, signature::{Keypair, KeypairUtil},
@ -318,10 +319,13 @@ pub(crate) mod tests {
for i in 0..3 { for i in 0..3 {
stakes.push(( stakes.push((
i, i,
VoteState::new(&VoteInit { VoteState::new(
node_pubkey: node1, &VoteInit {
..VoteInit::default() node_pubkey: node1,
}), ..VoteInit::default()
},
&Clock::default(),
),
)); ));
} }
@ -330,10 +334,13 @@ pub(crate) mod tests {
stakes.push(( stakes.push((
5, 5,
VoteState::new(&VoteInit { VoteState::new(
node_pubkey: node2, &VoteInit {
..VoteInit::default() node_pubkey: node2,
}), ..VoteInit::default()
},
&Clock::default(),
),
)); ));
let result = to_staked_nodes(stakes.into_iter()); let result = to_staked_nodes(stakes.into_iter());

View File

@ -36,7 +36,11 @@ pub enum VoteError {
#[error("vote timestamp not recent")] #[error("vote timestamp not recent")]
TimestampTooOld, TimestampTooOld,
#[error("authorized voter has already been changed this epoch")]
TooSoonToReauthorize,
} }
impl<E> DecodeError<E> for VoteError { impl<E> DecodeError<E> for VoteError {
fn type_of() -> &'static str { fn type_of() -> &'static str {
"VoteError" "VoteError"
@ -65,7 +69,8 @@ pub enum VoteInstruction {
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction { fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(*vote_pubkey, false), AccountMeta::new(*vote_pubkey, false),
AccountMeta::new(sysvar::rent::id(), false), AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
]; ];
Instruction::new( Instruction::new(
id(), id(),
@ -115,7 +120,11 @@ pub fn authorize(
new_authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
vote_authorize: VoteAuthorize, vote_authorize: VoteAuthorize,
) -> Instruction { ) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_pubkey, false)].with_signer(authorized_pubkey); let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
]
.with_signer(authorized_pubkey);
Instruction::new( Instruction::new(
id(), id(),
@ -183,11 +192,19 @@ pub fn process_instruction(
match limited_deserialize(data)? { match limited_deserialize(data)? {
VoteInstruction::InitializeAccount(vote_init) => { VoteInstruction::InitializeAccount(vote_init) => {
sysvar::rent::verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?; sysvar::rent::verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?;
vote_state::initialize_account(me, &vote_init) vote_state::initialize_account(
} me,
VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { &vote_init,
vote_state::authorize(me, &voter_pubkey, vote_authorize, &signers) &Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
)
} }
VoteInstruction::Authorize(voter_pubkey, vote_authorize) => vote_state::authorize(
me,
&voter_pubkey,
vote_authorize,
&signers,
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
),
VoteInstruction::UpdateNode(node_pubkey) => { VoteInstruction::UpdateNode(node_pubkey) => {
vote_state::update_node(me, &node_pubkey, &signers) vote_state::update_node(me, &node_pubkey, &signers)
} }
@ -307,8 +324,8 @@ mod tests {
fn test_minimum_balance() { fn test_minimum_balance() {
let rent = solana_sdk::rent::Rent::default(); let rent = solana_sdk::rent::Rent::default();
let minimum_balance = rent.minimum_balance(VoteState::size_of()); let minimum_balance = rent.minimum_balance(VoteState::size_of());
// vote state cheaper than "my $0.02" ;) // golden, may need updating when vote_state grows
assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.02) assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
} }
#[test] #[test]

View File

@ -99,12 +99,49 @@ pub struct BlockTimestamp {
pub timestamp: UnixTimestamp, pub timestamp: UnixTimestamp,
} }
// this is how many epochs a voter can be remembered for slashing
const MAX_ITEMS: usize = 32;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CircBuf<I> {
pub buf: [I; MAX_ITEMS],
/// next pointer
pub idx: usize,
}
impl<I: Default + Copy> Default for CircBuf<I> {
fn default() -> Self {
Self {
buf: [I::default(); MAX_ITEMS],
idx: MAX_ITEMS - 1,
}
}
}
impl<I> CircBuf<I> {
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;
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VoteState { 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 /// the signer for vote transactions
pub authorized_voter: Pubkey, 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
@ -131,10 +168,11 @@ pub struct VoteState {
} }
impl VoteState { impl VoteState {
pub fn new(vote_init: &VoteInit) -> 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_voter: 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()
@ -383,6 +421,7 @@ pub fn authorize(
authorized: &Pubkey, authorized: &Pubkey,
vote_authorize: VoteAuthorize, vote_authorize: VoteAuthorize,
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()?;
@ -390,7 +429,19 @@ pub fn authorize(
match vote_authorize { match vote_authorize {
VoteAuthorize::Voter => { VoteAuthorize::Voter => {
verify_authorized_signer(&vote_state.authorized_voter, signers)?; 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,
clock.epoch,
clock.slot,
));
vote_state.authorized_voter = *authorized; 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)?;
@ -453,13 +504,14 @@ pub fn withdraw(
pub fn initialize_account( pub fn initialize_account(
vote_account: &mut KeyedAccount, vote_account: &mut KeyedAccount,
vote_init: &VoteInit, vote_init: &VoteInit,
clock: &Clock,
) -> 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_voter != Pubkey::default() {
return Err(InstructionError::AccountAlreadyInitialized); return Err(InstructionError::AccountAlreadyInitialized);
} }
vote_account.set_state(&VoteState::new(vote_init)) vote_account.set_state(&VoteState::new(vote_init, clock))
} }
pub fn process_vote( pub fn process_vote(
@ -483,8 +535,7 @@ pub fn process_vote(
.iter() .iter()
.max() .max()
.ok_or_else(|| VoteError::EmptySlots) .ok_or_else(|| VoteError::EmptySlots)
.and_then(|slot| vote_state.process_timestamp(*slot, timestamp)) .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
.map_err(|err| InstructionError::CustomError(err as u32))?;
} }
vote_account.set_state(&vote_state) vote_account.set_state(&vote_state)
} }
@ -498,12 +549,15 @@ pub fn create_account(
) -> Account { ) -> Account {
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id()); let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
VoteState::new(&VoteInit { VoteState::new(
node_pubkey: *node_pubkey, &VoteInit {
authorized_voter: *vote_pubkey, node_pubkey: *node_pubkey,
authorized_withdrawer: *vote_pubkey, authorized_voter: *vote_pubkey,
commission, authorized_withdrawer: *vote_pubkey,
}) commission,
},
&Clock::default(),
)
.to(&mut vote_account) .to(&mut vote_account)
.unwrap(); .unwrap();
@ -525,12 +579,15 @@ mod tests {
impl VoteState { impl VoteState {
pub fn new_for_test(auth_pubkey: &Pubkey) -> Self { pub fn new_for_test(auth_pubkey: &Pubkey) -> Self {
Self::new(&VoteInit { Self::new(
node_pubkey: Pubkey::new_rand(), &VoteInit {
authorized_voter: *auth_pubkey, node_pubkey: Pubkey::new_rand(),
authorized_withdrawer: *auth_pubkey, authorized_voter: *auth_pubkey,
commission: 0, authorized_withdrawer: *auth_pubkey,
}) commission: 0,
},
&Clock::default(),
)
} }
} }
@ -551,6 +608,7 @@ mod tests {
authorized_withdrawer: vote_account_pubkey, authorized_withdrawer: vote_account_pubkey,
commission: 0, commission: 0,
}, },
&Clock::default(),
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -563,6 +621,7 @@ mod tests {
authorized_withdrawer: vote_account_pubkey, authorized_withdrawer: vote_account_pubkey,
commission: 0, commission: 0,
}, },
&Clock::default(),
); );
assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized)); assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized));
} }
@ -738,6 +797,10 @@ mod tests {
&authorized_voter_pubkey, &authorized_voter_pubkey,
VoteAuthorize::Voter, VoteAuthorize::Voter,
&signers, &signers,
&Clock {
epoch: 1,
..Clock::default()
},
); );
assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
@ -748,6 +811,19 @@ mod tests {
&authorized_voter_pubkey, &authorized_voter_pubkey,
VoteAuthorize::Voter, VoteAuthorize::Voter,
&signers, &signers,
&Clock::default(),
);
assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into()));
let res = authorize(
&mut keyed_accounts[0],
&authorized_voter_pubkey,
VoteAuthorize::Voter,
&signers,
&Clock {
epoch: 1,
..Clock::default()
},
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -767,6 +843,7 @@ mod tests {
&authorized_voter_pubkey, &authorized_voter_pubkey,
VoteAuthorize::Voter, VoteAuthorize::Voter,
&signers, &signers,
&Clock::default(),
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -780,6 +857,7 @@ mod tests {
&authorized_withdrawer_pubkey, &authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer, VoteAuthorize::Withdrawer,
&signers, &signers,
&Clock::default(),
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -795,6 +873,7 @@ mod tests {
&authorized_withdrawer_pubkey, &authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer, VoteAuthorize::Withdrawer,
&signers, &signers,
&Clock::default(),
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -1207,6 +1286,7 @@ mod tests {
&authorized_withdrawer_pubkey, &authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer, VoteAuthorize::Withdrawer,
&signers, &signers,
&Clock::default(),
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
@ -1269,6 +1349,18 @@ mod tests {
assert_eq!(vote_state.epoch_credits().len(), 1); assert_eq!(vote_state.epoch_credits().len(), 1);
} }
#[test]
fn test_vote_state_increment_credits() {
let mut vote_state = VoteState::default();
let credits = (MAX_EPOCH_CREDITS_HISTORY + 2) as u64;
for i in 0..credits {
vote_state.increment_credits(i as u64);
}
assert_eq!(vote_state.credits(), credits);
assert!(vote_state.epoch_credits().len() <= MAX_EPOCH_CREDITS_HISTORY);
}
#[test] #[test]
fn test_vote_process_timestamp() { fn test_vote_process_timestamp() {
let (slot, timestamp) = (15, 1575412285); let (slot, timestamp) = (15, 1575412285);