From b881029de3ba69da9ef189e775e274b3b11294db Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Mon, 9 Sep 2019 18:17:32 -0700 Subject: [PATCH] make voter_pubkey a function of epoch (#5830) * make voter_pubkey a function of epoch * fixups --- cli/src/wallet.rs | 46 +++++----- programs/budget_api/src/budget_instruction.rs | 40 +++++++-- programs/budget_api/src/budget_processor.rs | 15 ++-- programs/budget_api/src/budget_state.rs | 20 ----- programs/stake_api/src/stake_instruction.rs | 47 +++++++++++ programs/stake_api/src/stake_state.rs | 84 +++++++------------ programs/vote_api/src/vote_instruction.rs | 49 +++++++++-- programs/vote_api/src/vote_state.rs | 34 +------- runtime/src/stakes.rs | 6 +- 9 files changed, 189 insertions(+), 152 deletions(-) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index d203da9fb..1efd2dab3 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -6,32 +6,34 @@ use log::*; use num_traits::FromPrimitive; use serde_json; use serde_json::{json, Value}; -use solana_budget_api; -use solana_budget_api::budget_instruction; -use solana_budget_api::budget_state::BudgetError; +use solana_budget_api::budget_instruction::{self, BudgetError}; use solana_client::client_error::ClientError; use solana_client::rpc_client::RpcClient; #[cfg(not(test))] use solana_drone::drone::request_airdrop_transaction; #[cfg(test)] use solana_drone::drone_mock::request_airdrop_transaction; -use solana_sdk::account_utils::State; -use solana_sdk::bpf_loader; -use solana_sdk::fee_calculator::FeeCalculator; -use solana_sdk::hash::Hash; -use solana_sdk::instruction::InstructionError; -use solana_sdk::instruction_processor_utils::DecodeError; -use solana_sdk::loader_instruction; -use solana_sdk::message::Message; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signature}; -use solana_sdk::system_instruction::SystemError; -use solana_sdk::system_transaction; -use solana_sdk::transaction::{Transaction, TransactionError}; -use solana_stake_api::{stake_instruction, stake_state::StakeError}; +use solana_sdk::{ + account_utils::State, + bpf_loader, + fee_calculator::FeeCalculator, + hash::Hash, + instruction::InstructionError, + instruction_processor_utils::DecodeError, + loader_instruction, + message::Message, + pubkey::Pubkey, + signature::{read_keypair, Keypair, KeypairUtil, Signature}, + system_instruction::SystemError, + system_transaction, + transaction::{Transaction, TransactionError}, +}; +use solana_stake_api::stake_instruction::{self, StakeError}; use solana_storage_api::storage_instruction; -use solana_vote_api::vote_instruction; -use solana_vote_api::vote_state::VoteState; +use solana_vote_api::{ + vote_instruction::{self, VoteError}, + vote_state::VoteState, +}; use std::collections::VecDeque; use std::fs::File; use std::io::{Read, Write}; @@ -617,9 +619,9 @@ fn process_authorize_voter( recent_blockhash, ); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; - let signature_str = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_voter_keypair])?; - Ok(signature_str.to_string()) + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_voter_keypair]); + log_instruction_custom_error::(result) } fn process_show_account( diff --git a/programs/budget_api/src/budget_instruction.rs b/programs/budget_api/src/budget_instruction.rs index b41fb0c69..6ccf4dca3 100644 --- a/programs/budget_api/src/budget_instruction.rs +++ b/programs/budget_api/src/budget_instruction.rs @@ -1,13 +1,39 @@ -use crate::budget_expr::BudgetExpr; -use crate::budget_state::BudgetState; -use crate::id; +use crate::{budget_expr::BudgetExpr, budget_state::BudgetState, id}; use bincode::serialized_size; use chrono::prelude::{DateTime, Utc}; +use num_derive::FromPrimitive; use serde_derive::{Deserialize, Serialize}; -use solana_sdk::hash::Hash; -use solana_sdk::instruction::{AccountMeta, Instruction}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction; +use solana_sdk::{ + hash::Hash, + instruction::{AccountMeta, Instruction}, + instruction_processor_utils::DecodeError, + pubkey::Pubkey, + system_instruction, +}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive)] +pub enum BudgetError { + DestinationMissing, +} + +impl DecodeError for BudgetError { + fn type_of() -> &'static str { + "BudgetError" + } +} + +impl std::fmt::Display for BudgetError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + BudgetError::DestinationMissing => "destination missing", + } + ) + } +} +impl std::error::Error for BudgetError {} /// A smart contract. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] diff --git a/programs/budget_api/src/budget_processor.rs b/programs/budget_api/src/budget_processor.rs index b7500298d..682da9eff 100644 --- a/programs/budget_api/src/budget_processor.rs +++ b/programs/budget_api/src/budget_processor.rs @@ -1,14 +1,15 @@ //! budget program -use crate::budget_expr::Witness; -use crate::budget_instruction::BudgetInstruction; -use crate::budget_state::{BudgetError, BudgetState}; +use crate::{ + budget_expr::Witness, + budget_instruction::{BudgetError, BudgetInstruction}, + budget_state::BudgetState, +}; use bincode::deserialize; use chrono::prelude::{DateTime, Utc}; use log::*; -use solana_sdk::account::KeyedAccount; -use solana_sdk::hash::hash; -use solana_sdk::instruction::InstructionError; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{ + account::KeyedAccount, hash::hash, instruction::InstructionError, pubkey::Pubkey, +}; /// Process a Witness Signature. Any payment plans waiting on this signature /// will progress one step. diff --git a/programs/budget_api/src/budget_state.rs b/programs/budget_api/src/budget_state.rs index c51ae264d..0c5c4f276 100644 --- a/programs/budget_api/src/budget_state.rs +++ b/programs/budget_api/src/budget_state.rs @@ -1,28 +1,8 @@ //! budget state use crate::budget_expr::BudgetExpr; use bincode::{self, deserialize, serialize_into}; -use num_derive::FromPrimitive; use serde_derive::{Deserialize, Serialize}; use solana_sdk::instruction::InstructionError; -use solana_sdk::instruction_processor_utils::DecodeError; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive)] -pub enum BudgetError { - DestinationMissing, -} - -impl DecodeError for BudgetError { - fn type_of() -> &'static str { - "BudgetError" - } -} - -impl std::fmt::Display for BudgetError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "error") - } -} -impl std::error::Error for BudgetError {} #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct BudgetState { diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 9da10981f..10d5e9eb0 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -4,15 +4,36 @@ use crate::{ }; use bincode::deserialize; use log::*; +use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::KeyedAccount, clock::Slot, instruction::{AccountMeta, Instruction, InstructionError}, + instruction_processor_utils::DecodeError, pubkey::Pubkey, system_instruction, sysvar, }; +/// Reasons the stake might have had an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +pub enum StakeError { + NoCreditsToRedeem, +} +impl DecodeError for StakeError { + fn type_of() -> &'static str { + "StakeError" + } +} +impl std::fmt::Display for StakeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + StakeError::NoCreditsToRedeem => write!(f, "not enough credits to redeem"), + } + } +} +impl std::error::Error for StakeError {} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum StakeInstruction { /// `Lockup` a stake until the specified slot @@ -454,4 +475,30 @@ mod tests { ); } + #[test] + fn test_custom_error_decode() { + use num_traits::FromPrimitive; + fn pretty_err(err: InstructionError) -> String + where + T: 'static + std::error::Error + DecodeError + FromPrimitive, + { + if let InstructionError::CustomError(code) = err { + let specific_error: T = T::decode_custom_error_to_enum(code).unwrap(); + format!( + "{:?}: {}::{:?} - {}", + err, + T::type_of(), + specific_error, + specific_error, + ) + } else { + "".to_string() + } + } + assert_eq!( + "CustomError(0): StakeError::NoCreditsToRedeem - not enough credits to redeem", + pretty_err::(StakeError::NoCreditsToRedeem.into()) + ) + } + } diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index b5441cb25..65c4002c9 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -3,15 +3,13 @@ //! * keep track of rewards //! * own mining pools -use crate::{config::Config, id}; -use num_derive::{FromPrimitive, ToPrimitive}; +use crate::{config::Config, id, stake_instruction::StakeError}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::{Account, KeyedAccount}, account_utils::State, clock::{Epoch, Slot}, instruction::InstructionError, - instruction_processor_utils::DecodeError, pubkey::Pubkey, sysvar::{ self, @@ -21,6 +19,7 @@ use solana_sdk::{ use solana_vote_api::vote_state::VoteState; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[allow(clippy::large_enum_variant)] pub enum StakeState { Uninitialized, Lockup(Slot), @@ -52,46 +51,46 @@ impl StakeState { } } -/// Reasons the stake might have had an error -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] -pub enum StakeError { - NoCreditsToRedeem, -} -impl DecodeError for StakeError { - fn type_of() -> &'static str { - "StakeError" - } -} -impl std::fmt::Display for StakeError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - StakeError::NoCreditsToRedeem => write!(f, "not enough credits to redeem"), - } - } -} -impl std::error::Error for StakeError {} - #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Stake { + /// most recently delegated vote account pubkey pub voter_pubkey: Pubkey, + /// the epoch when voter_pubkey was most recently set + pub voter_pubkey_epoch: Epoch, + /// credits observed is credits from vote account state when delegated or redeemed pub credits_observed: u64, - pub stake: u64, // stake amount activated - pub activation_epoch: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake - pub deactivation_epoch: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated + /// activated stake amount, set at delegate_stake() time + pub stake: u64, + /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake + pub activation_epoch: Epoch, + /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated + pub deactivation_epoch: Epoch, + /// stake config (warmup, etc.) pub config: Config, + /// the Slot at which this stake becomes available for withdrawal pub lockup: Slot, + /// history of prior delegates and the epoch ranges for which + /// they were set, circular buffer + pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES], + /// next pointer + pub prior_delegates_idx: usize, } +const MAX_PRIOR_DELEGATES: usize = 32; + impl Default for Stake { fn default() -> Self { Self { voter_pubkey: Pubkey::default(), + voter_pubkey_epoch: 0, credits_observed: 0, stake: 0, activation_epoch: 0, deactivation_epoch: std::u64::MAX, config: Config::default(), lockup: 0, + prior_delegates: <[(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES]>::default(), + prior_delegates_idx: 0, } } } @@ -105,6 +104,10 @@ impl Stake { self.stake_activating_and_deactivating(epoch, history).0 } + pub fn voter_pubkey(&self, _epoch: Epoch) -> &Pubkey { + &self.voter_pubkey + } + fn stake_activating_and_deactivating( &self, epoch: Epoch, @@ -405,6 +408,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> { { let vote_state: VoteState = vote_account.state()?; + // the only valid use of current voter_pubkey, redelegation breaks + // rewards redemption for previous voter_pubkey if stake.voter_pubkey != *vote_account.unsigned_key() { return Err(InstructionError::InvalidArgument); } @@ -633,8 +638,7 @@ mod tests { stake: stake_lamports, activation_epoch: clock.epoch, deactivation_epoch: std::u64::MAX, - config: Config::default(), - lockup: 0 + ..Stake::default() }) ); // verify that delegate_stake can't be called twice StakeState::default() @@ -1257,32 +1261,6 @@ mod tests { ); } - #[test] - fn test_custom_error_decode() { - use num_traits::FromPrimitive; - fn pretty_err(err: InstructionError) -> String - where - T: 'static + std::error::Error + DecodeError + FromPrimitive, - { - if let InstructionError::CustomError(code) = err { - let specific_error: T = T::decode_custom_error_to_enum(code).unwrap(); - format!( - "{:?}: {}::{:?} - {}", - err, - T::type_of(), - specific_error, - specific_error, - ) - } else { - "".to_string() - } - } - assert_eq!( - "CustomError(0): StakeError::NoCreditsToRedeem - not enough credits to redeem", - pretty_err::(StakeError::NoCreditsToRedeem.into()) - ) - } - #[test] fn test_stake_state_calculate_rewards() { let mut vote_state = VoteState::default(); diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index 269e0f415..f0a6ffd2c 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -1,17 +1,52 @@ //! Vote program //! Receive and processes votes from validators -use crate::id; -use crate::vote_state::{self, Vote, VoteState}; +use crate::{ + id, + vote_state::{self, Vote, VoteState}, +}; use bincode::deserialize; use log::*; +use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; use solana_metrics::datapoint_warn; -use solana_sdk::account::KeyedAccount; -use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError}; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::system_instruction; -use solana_sdk::sysvar; +use solana_sdk::{ + account::KeyedAccount, + instruction::{AccountMeta, Instruction, InstructionError}, + instruction_processor_utils::DecodeError, + pubkey::Pubkey, + system_instruction, sysvar, +}; + +/// Reasons the stake might have had an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +pub enum VoteError { + VoteTooOld, + SlotsMismatch, + SlotHashMismatch, + EmptySlots, +} +impl DecodeError for VoteError { + fn type_of() -> &'static str { + "VoteError" + } +} + +impl std::fmt::Display for VoteError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + VoteError::VoteTooOld => "vote already recorded or not in slot hashes history", + VoteError::SlotsMismatch => "vote slots do not match bank history", + VoteError::SlotHashMismatch => "vote hash does not match bank hash", + VoteError::EmptySlots => "vote has no slots, invalid", + } + ) + } +} +impl std::error::Error for VoteError {} #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum VoteInstruction { diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 94b67a83f..f1788157e 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -1,9 +1,8 @@ //! Vote state, vote program //! Receive and processes votes from validators -use crate::id; +use crate::{id, vote_instruction::VoteError}; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; use log::*; -use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::sysvar::slot_hashes::SlotHash; use solana_sdk::{ @@ -12,7 +11,6 @@ use solana_sdk::{ clock::{Epoch, Slot}, hash::Hash, instruction::InstructionError, - instruction_processor_utils::DecodeError, pubkey::Pubkey, sysvar::clock::Clock, }; @@ -26,36 +24,6 @@ pub const INITIAL_LOCKOUT: usize = 2; // smaller numbers makes pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64; -/// Reasons the stake might have had an error -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] -pub enum VoteError { - VoteTooOld, - SlotsMismatch, - SlotHashMismatch, - EmptySlots, -} -impl DecodeError for VoteError { - fn type_of() -> &'static str { - "VoteError" - } -} - -impl std::fmt::Display for VoteError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - VoteError::VoteTooOld => "vote already recorded or not in slot hashes history", - VoteError::SlotsMismatch => "vote slots do not match bank history", - VoteError::SlotHashMismatch => "vote hash does not match bank hash", - VoteError::EmptySlots => "vote has no slots, invalid", - } - ) - } -} -impl std::error::Error for VoteError {} - #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Vote { /// A stack of votes starting with the oldest vote diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index b4550145b..411867fd7 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -85,7 +85,7 @@ impl Stakes { .iter() .map(|(_, stake_account)| { StakeState::stake_from(stake_account).map_or(0, |stake| { - if stake.voter_pubkey == *voter_pubkey { + if stake.voter_pubkey(epoch) == voter_pubkey { stake.stake(epoch, stake_history) } else { 0 @@ -129,7 +129,7 @@ impl Stakes { let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| { StakeState::stake_from(old_account).map(|stake| { ( - stake.voter_pubkey, + *stake.voter_pubkey(self.epoch), stake.stake(self.epoch, Some(&self.stake_history)), ) }) @@ -137,7 +137,7 @@ impl Stakes { let stake = StakeState::stake_from(account).map(|stake| { ( - stake.voter_pubkey, + *stake.voter_pubkey(self.epoch), if account.lamports != 0 { stake.stake(self.epoch, Some(&self.stake_history)) } else {