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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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)",
"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)",
"solana-config-api 0.18.0-pre1",
"solana-logger 0.18.0-pre1",
"solana-metrics 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)?;
}
// add the reward pools
// add genesis stuff from storage and stake
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())?;
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-sdk = { path = "../../sdk", 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]
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 stake_instruction;
pub mod stake_state;
@ -11,3 +12,12 @@ solana_sdk::solana_name_id!(
STAKE_PROGRAM_ID,
"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
//! * keep track of rewards
//! * own mining pools
use crate::stake_state::create_rewards_pool;
use crate::stake_state::StakeState;
use rand::{thread_rng, Rng};
use solana_sdk::genesis_block::Builder;
use solana_sdk::hash::{hash, Hash};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::{
account::Account,
hash::{hash, Hash},
pubkey::Pubkey,
};
// base rewards pool ID
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
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 {
let mut id = Hash::new(&ID);
@ -40,24 +31,31 @@ pub fn random_id() -> Pubkey {
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)]
mod tests {
use super::*;
use solana_sdk::genesis_block::Builder;
#[test]
fn test() {
let builder = Builder::new();
let genesis_block = genesis(builder).build();
let accounts = genesis();
for _i in 0..NUM_REWARDS_POOLS {
let id = random_id();
assert!(genesis_block
.rewards_pools
.iter()
.position(|x| x.0 == id)
.is_some());
assert!(accounts.iter().position(|x| x.0 == id).is_some());
}
}
}

View File

@ -1,5 +1,7 @@
use crate::id;
use crate::stake_state::{StakeAccount, StakeState};
use crate::{
config, id,
stake_state::{StakeAccount, StakeState},
};
use bincode::deserialize;
use log::*;
use serde_derive::{Deserialize, Serialize};
@ -18,6 +20,7 @@ pub enum StakeInstruction {
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature
/// 1 - VoteAccount to which this Stake will be delegated
/// 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,
/// 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_credit_only(*vote_pubkey, 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)
}
@ -139,12 +143,17 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
StakeInstruction::DelegateStake(stake) => {
if rest.len() != 2 {
if rest.len() != 3 {
Err(InstructionError::InvalidInstructionData)?;
}
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 => {
if rest.len() != 4 {
@ -206,6 +215,8 @@ mod tests {
sysvar::rewards::create_account(1, 0.0, 0.0)
} else if sysvar::stake_history::check_id(&meta.pubkey) {
sysvar::stake_history::create_account(1, &StakeHistory::default())
} else if config::check_id(&meta.pubkey) {
config::create_account(1, &config::Config::default())
} else {
Account::default()
}
@ -299,6 +310,11 @@ mod tests {
false,
&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(),
),

View File

@ -3,7 +3,7 @@
//! * keep track of rewards
//! * own mining pools
use crate::id;
use crate::{config::Config, id};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::{Account, KeyedAccount},
@ -56,10 +56,9 @@ pub struct Stake {
pub stake: u64, // stake amount activated
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 config: Config,
}
pub const STAKE_WARMUP_RATE: f64 = 0.15;
impl Default for Stake {
fn default() -> Self {
Self {
@ -68,6 +67,7 @@ impl Default for Stake {
stake: 0,
activated: 0,
deactivated: std::u64::MAX,
config: Config::default(),
}
}
}
@ -109,7 +109,7 @@ impl Stake {
// portion of activating stake in this epoch I'm entitled to
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 {
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 {
stake,
activated,
voter_pubkey: *voter_pubkey,
credits_observed: vote_state.credits(),
config: *config,
..Stake::default()
}
}
@ -231,6 +238,7 @@ pub trait StakeAccount {
vote_account: &KeyedAccount,
stake: u64,
clock: &sysvar::clock::Clock,
config: &Config,
) -> Result<(), InstructionError>;
fn deactivate_stake(
&mut self,
@ -259,6 +267,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account: &KeyedAccount,
new_stake: u64,
clock: &sysvar::clock::Clock,
config: &Config,
) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
@ -274,6 +283,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account.unsigned_key(),
&vote_account.state()?,
clock.epoch,
config,
);
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
pub fn new_stake_history_entry<'a, I>(
epoch: Epoch,
@ -429,11 +435,6 @@ pub fn create_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)]
mod tests {
use super::*;
@ -506,14 +507,19 @@ mod tests {
}
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)
);
// signed keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_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());
// verify that delegate_stake() looks right, compare against hand-rolled
@ -526,19 +532,30 @@ mod tests {
stake: stake_lamports,
activated: clock.epoch,
deactivated: std::u64::MAX,
config: Config::default()
})
);
// verify that delegate_stake can't be called twice StakeState::default()
// signed keyed account
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)
);
// verify can only stake up to account lamports
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
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)
);
@ -546,7 +563,7 @@ mod tests {
stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, 0, &clock)
.delegate_stake(&vote_keyed_account, 0, &clock, &Config::default())
.is_err());
}
@ -608,7 +625,9 @@ mod tests {
break;
}
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;
}
}
@ -651,7 +670,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
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(())
);
@ -723,7 +747,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
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(())
);
@ -810,7 +839,12 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
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(())
);
@ -944,7 +978,12 @@ mod tests {
rewards.validator_point_value = 100.0;
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 =
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
@ -972,7 +1011,12 @@ mod tests {
// delegate the stake
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, stake_lamports, &clock)
.delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
)
.is_ok());
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
builder = solana_stake_api::rewards_pools::genesis(builder);
builder = solana_stake_api::genesis(builder);
builder = solana_storage_api::rewards_pools::genesis(builder);
GenesisBlockInfo {