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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 149 additions and 33 deletions

View File

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

View File

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

View File

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

View File

@ -99,12 +99,49 @@ pub struct BlockTimestamp {
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)]
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
@ -131,10 +168,11 @@ pub struct VoteState {
}
impl VoteState {
pub fn new(vote_init: &VoteInit) -> Self {
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_withdrawer: vote_init.authorized_withdrawer,
commission: vote_init.commission,
..VoteState::default()
@ -383,6 +421,7 @@ pub fn authorize(
authorized: &Pubkey,
vote_authorize: VoteAuthorize,
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError> {
let mut vote_state: VoteState = vote_account.state()?;
@ -390,7 +429,19 @@ pub fn authorize(
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,
clock.epoch,
clock.slot,
));
vote_state.authorized_voter = *authorized;
vote_state.authorized_voter_epoch = clock.epoch;
}
VoteAuthorize::Withdrawer => {
verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
@ -453,13 +504,14 @@ pub fn withdraw(
pub fn initialize_account(
vote_account: &mut KeyedAccount,
vote_init: &VoteInit,
clock: &Clock,
) -> Result<(), InstructionError> {
let vote_state: VoteState = vote_account.state()?;
if vote_state.authorized_voter != Pubkey::default() {
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(
@ -483,8 +535,7 @@ pub fn process_vote(
.iter()
.max()
.ok_or_else(|| VoteError::EmptySlots)
.and_then(|slot| vote_state.process_timestamp(*slot, timestamp))
.map_err(|err| InstructionError::CustomError(err as u32))?;
.and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
}
vote_account.set_state(&vote_state)
}
@ -498,12 +549,15 @@ pub fn create_account(
) -> Account {
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
VoteState::new(&VoteInit {
node_pubkey: *node_pubkey,
authorized_voter: *vote_pubkey,
authorized_withdrawer: *vote_pubkey,
commission,
})
VoteState::new(
&VoteInit {
node_pubkey: *node_pubkey,
authorized_voter: *vote_pubkey,
authorized_withdrawer: *vote_pubkey,
commission,
},
&Clock::default(),
)
.to(&mut vote_account)
.unwrap();
@ -525,12 +579,15 @@ mod tests {
impl VoteState {
pub fn new_for_test(auth_pubkey: &Pubkey) -> Self {
Self::new(&VoteInit {
node_pubkey: Pubkey::new_rand(),
authorized_voter: *auth_pubkey,
authorized_withdrawer: *auth_pubkey,
commission: 0,
})
Self::new(
&VoteInit {
node_pubkey: Pubkey::new_rand(),
authorized_voter: *auth_pubkey,
authorized_withdrawer: *auth_pubkey,
commission: 0,
},
&Clock::default(),
)
}
}
@ -551,6 +608,7 @@ mod tests {
authorized_withdrawer: vote_account_pubkey,
commission: 0,
},
&Clock::default(),
);
assert_eq!(res, Ok(()));
@ -563,6 +621,7 @@ mod tests {
authorized_withdrawer: vote_account_pubkey,
commission: 0,
},
&Clock::default(),
);
assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized));
}
@ -738,6 +797,10 @@ mod tests {
&authorized_voter_pubkey,
VoteAuthorize::Voter,
&signers,
&Clock {
epoch: 1,
..Clock::default()
},
);
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
@ -748,6 +811,19 @@ mod tests {
&authorized_voter_pubkey,
VoteAuthorize::Voter,
&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(()));
@ -767,6 +843,7 @@ mod tests {
&authorized_voter_pubkey,
VoteAuthorize::Voter,
&signers,
&Clock::default(),
);
assert_eq!(res, Ok(()));
@ -780,6 +857,7 @@ mod tests {
&authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer,
&signers,
&Clock::default(),
);
assert_eq!(res, Ok(()));
@ -795,6 +873,7 @@ mod tests {
&authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer,
&signers,
&Clock::default(),
);
assert_eq!(res, Ok(()));
@ -1207,6 +1286,7 @@ mod tests {
&authorized_withdrawer_pubkey,
VoteAuthorize::Withdrawer,
&signers,
&Clock::default(),
);
assert_eq!(res, Ok(()));
@ -1269,6 +1349,18 @@ mod tests {
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]
fn test_vote_process_timestamp() {
let (slot, timestamp) = (15, 1575412285);