add stake_api config account (#5531)

This commit is contained in:
Rob Walker
2019-08-15 14:35:48 -07:00
committed by GitHub
parent e4519d6447
commit 88ea950652
9 changed files with 215 additions and 56 deletions

1
Cargo.lock generated
View File

@ -3725,6 +3725,7 @@ dependencies = [
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-config-api 0.18.0-pre1",
"solana-logger 0.18.0-pre1", "solana-logger 0.18.0-pre1",
"solana-metrics 0.18.0-pre1", "solana-metrics 0.18.0-pre1",
"solana-sdk 0.18.0-pre1", "solana-sdk 0.18.0-pre1",

View File

@ -331,9 +331,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
builder = append_primordial_accounts(file, AccountFileFormat::Keypair, builder)?; builder = append_primordial_accounts(file, AccountFileFormat::Keypair, builder)?;
} }
// add the reward pools // add genesis stuff from storage and stake
builder = solana_storage_api::rewards_pools::genesis(builder); builder = solana_storage_api::rewards_pools::genesis(builder);
builder = solana_stake_api::rewards_pools::genesis(builder); builder = solana_stake_api::genesis(builder);
create_new_ledger(&ledger_path, &builder.build())?; create_new_ledger(&ledger_path, &builder.build())?;
Ok(()) Ok(())

View File

@ -18,6 +18,7 @@ solana-logger = { path = "../../logger", version = "0.18.0-pre1" }
solana-metrics = { path = "../../metrics", version = "0.18.0-pre1" } solana-metrics = { path = "../../metrics", version = "0.18.0-pre1" }
solana-sdk = { path = "../../sdk", version = "0.18.0-pre1" } solana-sdk = { path = "../../sdk", version = "0.18.0-pre1" }
solana-vote-api = { path = "../vote_api", version = "0.18.0-pre1" } solana-vote-api = { path = "../vote_api", version = "0.18.0-pre1" }
solana-config-api = { path = "../config_api", version = "0.18.0-pre1" }
[lib] [lib]
crate-type = ["lib"] crate-type = ["lib"]

View File

@ -0,0 +1,89 @@
//! config for staking
//! carries variables that the stake program cares about
use bincode::{deserialize, serialized_size};
use serde_derive::{Deserialize, Serialize};
use solana_config_api::{create_config_account, get_config_data, ConfigState};
use solana_sdk::{
account::{Account, KeyedAccount},
instruction::InstructionError,
pubkey::Pubkey,
};
// stake config ID
const ID: [u8; 32] = [
6, 161, 216, 23, 165, 2, 5, 11, 104, 7, 145, 230, 206, 109, 184, 142, 30, 91, 113, 80, 246, 31,
198, 121, 10, 78, 180, 209, 0, 0, 0, 0,
];
solana_sdk::solana_name_id!(ID, "StakeConfig11111111111111111111111111111111");
// means that no more tha
pub const DEFAULT_WARMUP_RATE: f64 = 0.15;
pub const DEFAULT_COOLDOWN_RATE: f64 = 0.15;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config {
pub warmup_rate: f64,
pub cooldown_rate: f64,
pub slash_penalty: u8,
}
impl Config {
pub fn from(account: &Account) -> Option<Self> {
get_config_data(&account.data)
.ok()
.and_then(|data| deserialize(data).ok())
}
}
impl Default for Config {
fn default() -> Self {
Self {
warmup_rate: DEFAULT_WARMUP_RATE,
cooldown_rate: DEFAULT_COOLDOWN_RATE,
slash_penalty: DEFAULT_SLASH_PENALTY,
}
}
}
impl ConfigState for Config {
fn max_space() -> u64 {
serialized_size(&Config::default()).unwrap()
}
}
pub fn genesis() -> (Pubkey, Account) {
(id(), create_config_account(vec![], &Config::default(), 100))
}
pub fn create_account(lamports: u64, config: &Config) -> Account {
create_config_account(vec![], config, lamports)
}
pub fn from_keyed_account(account: &KeyedAccount) -> Result<Config, InstructionError> {
if !check_id(account.unsigned_key()) {
return Err(InstructionError::InvalidArgument);
}
Config::from(account.account).ok_or(InstructionError::InvalidArgument)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut account = create_account(1, &Config::default());
assert_eq!(Config::from(&account), Some(Config::default()));
assert_eq!(
from_keyed_account(&KeyedAccount::new(&Pubkey::default(), false, &mut account)),
Err(InstructionError::InvalidArgument)
);
let (pubkey, mut account) = genesis();
assert_eq!(
from_keyed_account(&KeyedAccount::new(&pubkey, false, &mut account)),
Ok(Config::default())
);
}
}

View File

@ -1,3 +1,4 @@
pub mod config;
pub mod rewards_pools; pub mod rewards_pools;
pub mod stake_instruction; pub mod stake_instruction;
pub mod stake_state; pub mod stake_state;
@ -11,3 +12,12 @@ solana_sdk::solana_name_id!(
STAKE_PROGRAM_ID, STAKE_PROGRAM_ID,
"Stake11111111111111111111111111111111111111" "Stake11111111111111111111111111111111111111"
); );
use solana_sdk::genesis_block::Builder;
pub fn genesis(mut builder: Builder) -> Builder {
for (pubkey, account) in crate::rewards_pools::genesis().iter() {
builder = builder.rewards_pool(*pubkey, account.clone());
}
builder.accounts(&[crate::config::genesis()])
}

View File

@ -2,12 +2,13 @@
//! * initialize genesis with rewards pools //! * initialize genesis with rewards pools
//! * keep track of rewards //! * keep track of rewards
//! * own mining pools //! * own mining pools
use crate::stake_state::StakeState;
use crate::stake_state::create_rewards_pool;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use solana_sdk::genesis_block::Builder; use solana_sdk::{
use solana_sdk::hash::{hash, Hash}; account::Account,
use solana_sdk::pubkey::Pubkey; hash::{hash, Hash},
pubkey::Pubkey,
};
// base rewards pool ID // base rewards pool ID
const ID: [u8; 32] = [ const ID: [u8; 32] = [
@ -20,16 +21,6 @@ solana_sdk::solana_name_id!(ID, "StakeRewards1111111111111111111111111111111");
// to cut down on collisions for redemptions, we make multiple accounts // to cut down on collisions for redemptions, we make multiple accounts
pub const NUM_REWARDS_POOLS: usize = 256; pub const NUM_REWARDS_POOLS: usize = 256;
pub fn genesis(mut builder: Builder) -> Builder {
let mut pubkey = id();
for _i in 0..NUM_REWARDS_POOLS {
builder = builder.rewards_pool(pubkey, create_rewards_pool());
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
}
builder
}
pub fn random_id() -> Pubkey { pub fn random_id() -> Pubkey {
let mut id = Hash::new(&ID); let mut id = Hash::new(&ID);
@ -40,24 +31,31 @@ pub fn random_id() -> Pubkey {
Pubkey::new(id.as_ref()) Pubkey::new(id.as_ref())
} }
pub fn genesis() -> Vec<(Pubkey, Account)> {
let mut accounts = Vec::with_capacity(NUM_REWARDS_POOLS);
let mut pubkey = id();
for _i in 0..NUM_REWARDS_POOLS {
accounts.push((
pubkey,
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap(),
));
pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref());
}
accounts
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::genesis_block::Builder;
#[test] #[test]
fn test() { fn test() {
let builder = Builder::new(); let accounts = genesis();
let genesis_block = genesis(builder).build();
for _i in 0..NUM_REWARDS_POOLS { for _i in 0..NUM_REWARDS_POOLS {
let id = random_id(); let id = random_id();
assert!(genesis_block assert!(accounts.iter().position(|x| x.0 == id).is_some());
.rewards_pools
.iter()
.position(|x| x.0 == id)
.is_some());
} }
} }
} }

View File

@ -1,5 +1,7 @@
use crate::id; use crate::{
use crate::stake_state::{StakeAccount, StakeState}; config, id,
stake_state::{StakeAccount, StakeState},
};
use bincode::deserialize; use bincode::deserialize;
use log::*; use log::*;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -18,6 +20,7 @@ pub enum StakeInstruction {
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature /// 0 - Uninitialized StakeAccount to be delegated <= must have this signature
/// 1 - VoteAccount to which this Stake will be delegated /// 1 - VoteAccount to which this Stake will be delegated
/// 2 - Clock sysvar Account that carries clock bank epoch /// 2 - Clock sysvar Account that carries clock bank epoch
/// 3 - Config Account that carries stake config
/// ///
/// The u64 is the portion of the Stake account balance to be activated, /// The u64 is the portion of the Stake account balance to be activated,
/// must be less than StakeAccount.lamports /// must be less than StakeAccount.lamports
@ -96,6 +99,7 @@ pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -
AccountMeta::new(*stake_pubkey, true), AccountMeta::new(*stake_pubkey, true),
AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false), AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false),
]; ];
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas) Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
} }
@ -139,12 +143,17 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts // TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
StakeInstruction::DelegateStake(stake) => { StakeInstruction::DelegateStake(stake) => {
if rest.len() != 2 { if rest.len() != 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
let vote = &rest[0]; let vote = &rest[0];
me.delegate_stake(vote, stake, &sysvar::clock::from_keyed_account(&rest[1])?) me.delegate_stake(
vote,
stake,
&sysvar::clock::from_keyed_account(&rest[1])?,
&config::from_keyed_account(&rest[2])?,
)
} }
StakeInstruction::RedeemVoteCredits => { StakeInstruction::RedeemVoteCredits => {
if rest.len() != 4 { if rest.len() != 4 {
@ -206,6 +215,8 @@ mod tests {
sysvar::rewards::create_account(1, 0.0, 0.0) sysvar::rewards::create_account(1, 0.0, 0.0)
} else if sysvar::stake_history::check_id(&meta.pubkey) { } else if sysvar::stake_history::check_id(&meta.pubkey) {
sysvar::stake_history::create_account(1, &StakeHistory::default()) sysvar::stake_history::create_account(1, &StakeHistory::default())
} else if config::check_id(&meta.pubkey) {
config::create_account(1, &config::Config::default())
} else { } else {
Account::default() Account::default()
} }
@ -299,6 +310,11 @@ mod tests {
false, false,
&mut sysvar::clock::create_account(1, 0, 0, 0, 0) &mut sysvar::clock::create_account(1, 0, 0, 0, 0)
), ),
KeyedAccount::new(
&config::id(),
false,
&mut config::create_account(1, &config::Config::default())
),
], ],
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(), &serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
), ),

View File

@ -3,7 +3,7 @@
//! * keep track of rewards //! * keep track of rewards
//! * own mining pools //! * own mining pools
use crate::id; use crate::{config::Config, id};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::{Account, KeyedAccount}, account::{Account, KeyedAccount},
@ -56,10 +56,9 @@ pub struct Stake {
pub stake: u64, // stake amount activated pub stake: u64, // stake amount activated
pub activated: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake pub activated: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake
pub deactivated: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated pub deactivated: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub config: Config,
} }
pub const STAKE_WARMUP_RATE: f64 = 0.15;
impl Default for Stake { impl Default for Stake {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -68,6 +67,7 @@ impl Default for Stake {
stake: 0, stake: 0,
activated: 0, activated: 0,
deactivated: std::u64::MAX, deactivated: std::u64::MAX,
config: Config::default(),
} }
} }
} }
@ -109,7 +109,7 @@ impl Stake {
// portion of activating stake in this epoch I'm entitled to // portion of activating stake in this epoch I'm entitled to
effective_stake += effective_stake +=
(weight * entry.effective as f64 * STAKE_WARMUP_RATE) as u64; (weight * entry.effective as f64 * self.config.warmup_rate) as u64;
if effective_stake >= self.stake { if effective_stake >= self.stake {
effective_stake = self.stake; effective_stake = self.stake;
@ -210,12 +210,19 @@ impl Stake {
} }
} }
fn new(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState, activated: Epoch) -> Self { fn new(
stake: u64,
voter_pubkey: &Pubkey,
vote_state: &VoteState,
activated: Epoch,
config: &Config,
) -> Self {
Self { Self {
stake, stake,
activated, activated,
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
config: *config,
..Stake::default() ..Stake::default()
} }
} }
@ -231,6 +238,7 @@ pub trait StakeAccount {
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
stake: u64, stake: u64,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn deactivate_stake( fn deactivate_stake(
&mut self, &mut self,
@ -259,6 +267,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
new_stake: u64, new_stake: u64,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if self.signer_key().is_none() { if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
@ -274,6 +283,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
config,
); );
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(stake))
@ -381,10 +391,6 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
} }
} }
//find_min<'a, I>(vals: I) -> Option<&'a u32>
//where
// I: Iterator<Item = &'a u32>,
// utility function, used by runtime::Stakes, tests // utility function, used by runtime::Stakes, tests
pub fn new_stake_history_entry<'a, I>( pub fn new_stake_history_entry<'a, I>(
epoch: Epoch, epoch: Epoch,
@ -429,11 +435,6 @@ pub fn create_stake_account(
stake_account stake_account
} }
// utility function, used by Bank, tests, genesis
pub fn create_rewards_pool() -> Account {
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -506,14 +507,19 @@ mod tests {
} }
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, 0, &clock), stake_keyed_account.delegate_stake(&vote_keyed_account, 0, &clock, &Config::default()),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
// signed keyed account // signed keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, stake_lamports, &clock) .delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
)
.is_ok()); .is_ok());
// verify that delegate_stake() looks right, compare against hand-rolled // verify that delegate_stake() looks right, compare against hand-rolled
@ -526,19 +532,30 @@ mod tests {
stake: stake_lamports, stake: stake_lamports,
activated: clock.epoch, activated: clock.epoch,
deactivated: std::u64::MAX, deactivated: std::u64::MAX,
config: Config::default()
}) })
); );
// verify that delegate_stake can't be called twice StakeState::default() // verify that delegate_stake can't be called twice StakeState::default()
// signed keyed account // signed keyed account
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &clock), stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
// verify can only stake up to account lamports // verify can only stake up to account lamports
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports + 1, &clock), stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports + 1,
&clock,
&Config::default()
),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -546,7 +563,7 @@ mod tests {
stake_keyed_account.set_state(&stake_state).unwrap(); stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, 0, &clock) .delegate_stake(&vote_keyed_account, 0, &clock, &Config::default())
.is_err()); .is_err());
} }
@ -608,7 +625,9 @@ mod tests {
break; break;
} }
assert!(epoch < epochs); // should have warmed everything up by this time assert!(epoch < epochs); // should have warmed everything up by this time
assert!(delta as f64 / prev_total_effective_stake as f64 <= STAKE_WARMUP_RATE); assert!(
delta as f64 / prev_total_effective_stake as f64 <= Config::default().warmup_rate
);
prev_total_effective_stake = total_effective_stake; prev_total_effective_stake = total_effective_stake;
} }
} }
@ -651,7 +670,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &clock), stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Ok(()) Ok(())
); );
@ -723,7 +747,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &clock), stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Ok(()) Ok(())
); );
@ -810,7 +839,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &future), stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports,
&future,
&Config::default()
),
Ok(()) Ok(())
); );
@ -944,7 +978,12 @@ mod tests {
rewards.validator_point_value = 100.0; rewards.validator_point_value = 100.0;
let rewards_pool_pubkey = Pubkey::new_rand(); let rewards_pool_pubkey = Pubkey::new_rand();
let mut rewards_pool_account = create_rewards_pool(); let mut rewards_pool_account = Account::new_data(
std::u64::MAX,
&StakeState::RewardsPool,
&crate::rewards_pools::id(),
)
.unwrap();
let mut rewards_pool_keyed_account = let mut rewards_pool_keyed_account =
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account); KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
@ -972,7 +1011,12 @@ mod tests {
// delegate the stake // delegate the stake
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, stake_lamports, &clock) .delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
)
.is_ok()); .is_ok());
let stake_history = create_stake_history_from_stakes( let stake_history = create_stake_history_from_stakes(

View File

@ -74,7 +74,7 @@ pub fn create_genesis_block_with_leader(
]) ])
.fee_calculator(FeeCalculator::new(0)); // most tests don't want fees .fee_calculator(FeeCalculator::new(0)); // most tests don't want fees
builder = solana_stake_api::rewards_pools::genesis(builder); builder = solana_stake_api::genesis(builder);
builder = solana_storage_api::rewards_pools::genesis(builder); builder = solana_storage_api::rewards_pools::genesis(builder);
GenesisBlockInfo { GenesisBlockInfo {