Add authorized_voter history (#7643)
* Add authorized_voter history * fixups * coverage * bigger vote state
This commit is contained in:
parent
760a56964f
commit
6b7d9942a7
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue