This commit is contained in:
Rob Walker
2019-06-19 11:54:52 -07:00
committed by GitHub
parent 8bd1c57448
commit 41fbdc6e08
4 changed files with 168 additions and 100 deletions

View File

@ -121,9 +121,8 @@ pub(crate) mod tests {
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::transaction::Transaction; use solana_sdk::transaction::Transaction;
use solana_stake_api::stake_instruction; use solana_stake_api::stake_instruction;
use solana_stake_api::stake_state::Stake;
use solana_vote_api::vote_instruction; use solana_vote_api::vote_instruction;
use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc; use std::sync::Arc;
fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank { fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank {
@ -144,8 +143,14 @@ pub(crate) mod tests {
let mut expected = HashMap::new(); let mut expected = HashMap::new();
assert_eq!(vote_account_stakes_at_epoch(&bank, 10), None); assert_eq!(vote_account_stakes_at_epoch(&bank, 10), None);
let leader_stake = Stake {
stake: BOOTSTRAP_LEADER_LAMPORTS,
epoch: 0,
..Stake::default()
};
// First epoch has the bootstrap leader // First epoch has the bootstrap leader
expected.insert(voting_keypair.pubkey(), BOOTSTRAP_LEADER_LAMPORTS); expected.insert(voting_keypair.pubkey(), leader_stake.stake(0));
let expected = Some(expected); let expected = Some(expected);
assert_eq!(vote_account_stakes_at_epoch(&bank, 0), expected); assert_eq!(vote_account_stakes_at_epoch(&bank, 0), expected);
@ -205,7 +210,12 @@ pub(crate) mod tests {
#[test] #[test]
fn test_epoch_stakes_and_lockouts() { fn test_epoch_stakes_and_lockouts() {
let stake = 42; let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
let leader_stake = Stake {
stake: BOOTSTRAP_LEADER_LAMPORTS,
..Stake::default()
};
let validator = Keypair::new(); let validator = Keypair::new();
let GenesisBlockInfo { let GenesisBlockInfo {
@ -233,6 +243,11 @@ pub(crate) mod tests {
stake, stake,
); );
let other_stake = Stake {
stake,
..Stake::default()
};
// soonest slot that could be a new epoch is 1 // soonest slot that could be a new epoch is 1
let mut slot = 1; let mut slot = 1;
let mut epoch = bank.get_stakers_epoch(0); let mut epoch = bank.get_stakers_epoch(0);
@ -240,17 +255,20 @@ pub(crate) mod tests {
while bank.get_stakers_epoch(slot) == epoch { while bank.get_stakers_epoch(slot) == epoch {
slot += 1; slot += 1;
} }
epoch = bank.get_stakers_epoch(slot); epoch = bank.get_stakers_epoch(slot);
let bank = new_from_parent(&Arc::new(bank), slot); let bank = new_from_parent(&Arc::new(bank), slot);
let result: Vec<_> = epoch_stakes_and_lockouts(&bank, 0); let result: Vec<_> = epoch_stakes_and_lockouts(&bank, 0);
assert_eq!(result, vec![(BOOTSTRAP_LEADER_LAMPORTS, None)]); assert_eq!(result, vec![(leader_stake.stake(0), None)]);
let result: HashSet<_> = HashSet::from_iter(epoch_stakes_and_lockouts(&bank, epoch)); let mut result: Vec<_> = epoch_stakes_and_lockouts(&bank, epoch);
let expected: HashSet<_> = result.sort();
HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (stake, None)]); let mut expected = vec![
(leader_stake.stake(bank.epoch()), None),
(other_stake.stake(bank.epoch()), None),
];
expected.sort();
assert_eq!(result, expected); assert_eq!(result, expected);
} }

View File

@ -49,26 +49,10 @@ impl StakeState {
account.state().ok() account.state().ok()
} }
// utility function, used by Stakes, tests pub fn stake_from(account: &Account) -> Option<Stake> {
pub fn voter_pubkey_from(account: &Account) -> Option<Pubkey> {
Self::from(account).and_then(|state: Self| state.voter_pubkey())
}
// utility function, used by Stakes, tests
pub fn voter_pubkey_and_stake_from(account: &Account) -> Option<(Pubkey, u64)> {
Self::from(account).and_then(|state: Self| state.voter_pubkey_and_stake())
}
pub fn stake_from(account: &Account) -> Option<(Stake)> {
Self::from(account).and_then(|state: Self| state.stake()) Self::from(account).and_then(|state: Self| state.stake())
} }
pub fn voter_pubkey(&self) -> Option<Pubkey> {
match self {
StakeState::Stake(stake) => Some(stake.voter_pubkey),
_ => None,
}
}
pub fn stake(&self) -> Option<Stake> { pub fn stake(&self) -> Option<Stake> {
match self { match self {
StakeState::Stake(stake) => Some(stake.clone()), StakeState::Stake(stake) => Some(stake.clone()),
@ -76,13 +60,6 @@ impl StakeState {
} }
} }
pub fn voter_pubkey_and_stake(&self) -> Option<(Pubkey, u64)> {
match self {
StakeState::Stake(stake) => Some((stake.voter_pubkey, stake.stake)),
_ => None,
}
}
pub fn calculate_rewards( pub fn calculate_rewards(
credits_observed: u64, credits_observed: u64,
stake: u64, stake: u64,
@ -121,27 +98,26 @@ pub struct Stake {
pub epoch: u64, // epoch the stake was activated pub epoch: u64, // epoch the stake was activated
pub prev_stake: u64, // for warmup, cooldown pub prev_stake: u64, // for warmup, cooldown
} }
const STAKE_WARMUP_COOLDOWN_EPOCHS: u64 = 3; pub const STAKE_WARMUP_EPOCHS: u64 = 3;
impl Stake { impl Stake {
pub fn stake(&mut self, epoch: u64) -> u64 { pub fn stake(&self, epoch: u64) -> u64 {
// prev_stake for stuff in the past // prev_stake for stuff in the past
if epoch < self.epoch { if epoch < self.epoch {
return self.prev_stake; return self.prev_stake;
} }
if epoch - self.epoch >= STAKE_WARMUP_COOLDOWN_EPOCHS { if epoch - self.epoch >= STAKE_WARMUP_EPOCHS {
return self.stake; return self.stake;
} }
if self.stake != 0 { if self.stake != 0 {
// warmup // warmup
// 1/3rd, then 2/3rds... // 1/3rd, then 2/3rds...
(self.stake / STAKE_WARMUP_COOLDOWN_EPOCHS) * (epoch - self.epoch + 1) (self.stake / STAKE_WARMUP_EPOCHS) * (epoch - self.epoch + 1)
} else if self.prev_stake != 0 { } else if self.prev_stake != 0 {
// cool down // cool down
// 3/3rds, then 2/3rds... // 3/3rds, then 2/3rds...
self.prev_stake self.prev_stake - ((self.prev_stake / STAKE_WARMUP_EPOCHS) * (epoch - self.epoch))
- ((self.prev_stake / STAKE_WARMUP_COOLDOWN_EPOCHS) * (epoch - self.epoch))
} else { } else {
0 0
} }
@ -307,7 +283,7 @@ pub fn create_stake_account(
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake: lamports, stake: lamports,
epoch: 0, epoch: 0,
prev_stake: lamports, prev_stake: 0,
})) }))
.expect("set_state"); .expect("set_state");
@ -414,25 +390,24 @@ mod tests {
fn test_stake_stake() { fn test_stake_stake() {
let mut stake = Stake::default(); let mut stake = Stake::default();
assert_eq!(stake.stake(0), 0); assert_eq!(stake.stake(0), 0);
let staked = STAKE_WARMUP_COOLDOWN_EPOCHS; let staked = STAKE_WARMUP_EPOCHS;
stake.delegate(staked, &Pubkey::default(), &VoteState::default(), 1); stake.delegate(staked, &Pubkey::default(), &VoteState::default(), 1);
// test warmup // test warmup
for i in 0..STAKE_WARMUP_COOLDOWN_EPOCHS { for i in 0..STAKE_WARMUP_EPOCHS {
assert_eq!(stake.stake(i), i); assert_eq!(stake.stake(i), i);
} }
assert_eq!(stake.stake(STAKE_WARMUP_COOLDOWN_EPOCHS * 42), staked); assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), staked);
stake.deactivate(STAKE_WARMUP_COOLDOWN_EPOCHS); stake.deactivate(STAKE_WARMUP_EPOCHS);
// test cooldown // test cooldown
for i in STAKE_WARMUP_COOLDOWN_EPOCHS..STAKE_WARMUP_COOLDOWN_EPOCHS * 2 { for i in STAKE_WARMUP_EPOCHS..STAKE_WARMUP_EPOCHS * 2 {
assert_eq!( assert_eq!(
stake.stake(i), stake.stake(i),
staked staked - (staked / STAKE_WARMUP_EPOCHS) * (i - STAKE_WARMUP_EPOCHS)
- (staked / STAKE_WARMUP_COOLDOWN_EPOCHS) * (i - STAKE_WARMUP_COOLDOWN_EPOCHS)
); );
} }
assert_eq!(stake.stake(STAKE_WARMUP_COOLDOWN_EPOCHS * 42), 0); assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), 0);
} }
#[test] #[test]

View File

@ -321,7 +321,7 @@ impl Bank {
self.transaction_count self.transaction_count
.store(parent.transaction_count() as usize, Ordering::Relaxed); .store(parent.transaction_count() as usize, Ordering::Relaxed);
self.stakes = RwLock::new(parent.stakes.read().unwrap().clone()); self.stakes = RwLock::new(parent.stakes.read().unwrap().clone_with_epoch(self.epoch()));
self.storage_accounts = RwLock::new(parent.storage_accounts.read().unwrap().clone()); self.storage_accounts = RwLock::new(parent.storage_accounts.read().unwrap().clone());
self.tick_height.store( self.tick_height.store(

View File

@ -17,17 +17,42 @@ pub struct Stakes {
/// unclaimed points. /// unclaimed points.
// a point is a credit multiplied by the stake // a point is a credit multiplied by the stake
points: u64, points: u64,
/// current epoch, used to calculate current stake
epoch: u64,
} }
impl Stakes { impl Stakes {
pub fn clone_with_epoch(&self, epoch: u64) -> Self {
if self.epoch == epoch {
self.clone()
} else {
Stakes {
stake_accounts: self.stake_accounts.clone(),
points: self.points,
epoch,
vote_accounts: self
.vote_accounts
.iter()
.map(|(pubkey, (_stake, account))| {
(
*pubkey,
(self.calculate_stake(pubkey, epoch), account.clone()),
)
})
.collect(),
}
}
}
// sum the stakes that point to the given voter_pubkey // sum the stakes that point to the given voter_pubkey
fn calculate_stake(&self, voter_pubkey: &Pubkey) -> u64 { fn calculate_stake(&self, voter_pubkey: &Pubkey, epoch: u64) -> u64 {
self.stake_accounts self.stake_accounts
.iter() .iter()
.map(|(_, stake_account)| { .map(|(_, stake_account)| {
StakeState::stake_from(stake_account).map_or(0, |stake| { StakeState::stake_from(stake_account).map_or(0, |stake| {
if stake.voter_pubkey == *voter_pubkey { if stake.voter_pubkey == *voter_pubkey {
stake.stake stake.stake(epoch)
} else { } else {
0 0
} }
@ -47,7 +72,7 @@ impl Stakes {
} else { } else {
let old = self.vote_accounts.get(pubkey); let old = self.vote_accounts.get(pubkey);
let stake = old.map_or_else(|| self.calculate_stake(pubkey), |v| v.0); let stake = old.map_or_else(|| self.calculate_stake(pubkey, self.epoch), |v| v.0);
// count any increase in points, can only go forward // count any increase in points, can only go forward
let old_credits = old let old_credits = old
@ -62,23 +87,28 @@ impl Stakes {
} }
} else if solana_stake_api::check_id(&account.owner) { } else if solana_stake_api::check_id(&account.owner) {
// old_stake is stake lamports and voter_pubkey from the pre-store() version // old_stake is stake lamports and voter_pubkey from the pre-store() version
let old_stake = self let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| {
.stake_accounts StakeState::stake_from(old_account)
.get(pubkey) .map(|stake| (stake.voter_pubkey, stake.stake(self.epoch)))
.and_then(|old_account| StakeState::voter_pubkey_and_stake_from(old_account)); });
let stake = if account.lamports != 0 { let stake = StakeState::stake_from(account).map(|stake| {
StakeState::voter_pubkey_and_stake_from(account) (
} else { stake.voter_pubkey,
StakeState::voter_pubkey_from(account).map(|voter_pubkey| (voter_pubkey, 0)) if account.lamports != 0 {
}; stake.stake(self.epoch)
} else {
0
},
)
});
// if adjustments need to be made... // if adjustments need to be made...
if stake != old_stake { if stake != old_stake {
if let Some((old_voter_pubkey, old_stake)) = old_stake { if let Some((voter_pubkey, stake)) = old_stake {
self.vote_accounts self.vote_accounts
.entry(old_voter_pubkey) .entry(voter_pubkey)
.and_modify(|e| e.0 -= old_stake); .and_modify(|e| e.0 -= stake);
} }
if let Some((voter_pubkey, stake)) = stake { if let Some((voter_pubkey, stake)) = stake {
self.vote_accounts self.vote_accounts
@ -140,7 +170,7 @@ impl Stakes {
pub mod tests { pub mod tests {
use super::*; use super::*;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_stake_api::stake_state; use solana_stake_api::stake_state::{self, STAKE_WARMUP_EPOCHS};
use solana_vote_api::vote_state::{self, VoteState, MAX_LOCKOUT_HISTORY}; use solana_vote_api::vote_state::{self, VoteState, MAX_LOCKOUT_HISTORY};
// set up some dummies for a staked node (( vote ) ( stake )) // set up some dummies for a staked node (( vote ) ( stake ))
@ -163,43 +193,47 @@ pub mod tests {
#[test] #[test]
fn test_stakes_basic() { fn test_stakes_basic() {
let mut stakes = Stakes::default(); for i in 0..STAKE_WARMUP_EPOCHS + 1 {
let mut stakes = Stakes::default();
stakes.epoch = i;
let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account);
let stake = StakeState::stake_from(&stake_account).unwrap();
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, stake.stake(i));
}
{ stake_account.lamports = 42;
let vote_accounts = stakes.vote_accounts(); stakes.store(&stake_pubkey, &stake_account);
assert!(vote_accounts.get(&vote_pubkey).is_some()); {
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10); let vote_accounts = stakes.vote_accounts();
} assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, stake.stake(i)); // stays old stake, because only 10 is activated
}
stake_account.lamports = 42; // activate more
stakes.store(&stake_pubkey, &stake_account); let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey);
{ stakes.store(&stake_pubkey, &stake_account);
let vote_accounts = stakes.vote_accounts(); let stake = StakeState::stake_from(&stake_account).unwrap();
assert!(vote_accounts.get(&vote_pubkey).is_some()); {
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10); // stays old stake, because only 10 is activated let vote_accounts = stakes.vote_accounts();
} assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, stake.stake(i)); // now stake of 42 is activated
}
// activate more stake_account.lamports = 0;
let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey); stakes.store(&stake_pubkey, &stake_account);
stakes.store(&stake_pubkey, &stake_account); {
{ let vote_accounts = stakes.vote_accounts();
let vote_accounts = stakes.vote_accounts(); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 42); // now stake of 42 is activated }
}
stake_account.lamports = 0;
stakes.store(&stake_pubkey, &stake_account);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
} }
} }
@ -216,7 +250,7 @@ pub mod tests {
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account);
let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) = let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) =
create_staked_node_accounts(11); create_staked_node_accounts(20);
stakes.store(&vote11_pubkey, &vote11_account); stakes.store(&vote11_pubkey, &vote11_account);
stakes.store(&stake11_pubkey, &stake11_account); stakes.store(&stake11_pubkey, &stake11_account);
@ -229,6 +263,8 @@ pub mod tests {
#[test] #[test]
fn test_stakes_points() { fn test_stakes_points() {
let mut stakes = Stakes::default(); let mut stakes = Stakes::default();
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
let stake = 42; let stake = 42;
assert_eq!(stakes.points(), 0); assert_eq!(stakes.points(), 0);
assert_eq!(stakes.claim_points(), 0); assert_eq!(stakes.claim_points(), 0);
@ -273,6 +309,7 @@ pub mod tests {
#[test] #[test]
fn test_stakes_vote_account_disappear_reappear() { fn test_stakes_vote_account_disappear_reappear() {
let mut stakes = Stakes::default(); let mut stakes = Stakes::default();
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
@ -306,6 +343,7 @@ pub mod tests {
#[test] #[test]
fn test_stakes_change_delegate() { fn test_stakes_change_delegate() {
let mut stakes = Stakes::default(); let mut stakes = Stakes::default();
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
@ -319,10 +357,15 @@ pub mod tests {
// delegates to vote_pubkey // delegates to vote_pubkey
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account);
let stake = StakeState::stake_from(&stake_account).unwrap();
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10); assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch)
);
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0); assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0);
} }
@ -335,12 +378,16 @@ pub mod tests {
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0); assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 10); assert_eq!(
vote_accounts.get(&vote_pubkey2).unwrap().0,
stake.stake(stakes.epoch)
);
} }
} }
#[test] #[test]
fn test_stakes_multiple_stakers() { fn test_stakes_multiple_stakers() {
let mut stakes = Stakes::default(); let mut stakes = Stakes::default();
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
@ -359,10 +406,38 @@ pub mod tests {
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 20); assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 20);
} }
} }
#[test]
fn test_clone_with_epoch() {
let mut stakes = Stakes::default();
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account);
stakes.store(&stake_pubkey, &stake_account);
let stake = StakeState::stake_from(&stake_account).unwrap();
{
let vote_accounts = stakes.vote_accounts();
assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch)
);
}
let stakes = stakes.clone_with_epoch(3);
{
let vote_accounts = stakes.vote_accounts();
assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch)
);
}
}
#[test] #[test]
fn test_stakes_not_delegate() { fn test_stakes_not_delegate() {
let mut stakes = Stakes::default(); let mut stakes = Stakes::default();
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);