add global stake warmup (#5483)
* add global stake warmup * integrate stake history into runtime * fixup core tests * fixup * remove existing cooldown tests for now
This commit is contained in:
@ -27,22 +27,24 @@ use solana_measure::measure::Measure;
|
||||
use solana_metrics::{
|
||||
datapoint_info, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_info,
|
||||
};
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::genesis_block::GenesisBlock;
|
||||
use solana_sdk::hash::{hashv, Hash};
|
||||
use solana_sdk::inflation::Inflation;
|
||||
use solana_sdk::native_loader;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signature};
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::sysvar::{
|
||||
clock, fees, rewards,
|
||||
slot_hashes::{self, SlotHashes},
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
fee_calculator::FeeCalculator,
|
||||
genesis_block::GenesisBlock,
|
||||
hash::{hashv, Hash},
|
||||
inflation::Inflation,
|
||||
native_loader,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signature},
|
||||
system_transaction,
|
||||
sysvar::{
|
||||
clock, fees, rewards,
|
||||
slot_hashes::{self, SlotHashes},
|
||||
stake_history,
|
||||
},
|
||||
timing::{duration_as_ns, get_segment_from_slot, Epoch, Slot, MAX_RECENT_BLOCKHASHES},
|
||||
transaction::{Result, Transaction, TransactionError},
|
||||
};
|
||||
use solana_sdk::timing::{duration_as_ns, get_segment_from_slot, Slot, MAX_RECENT_BLOCKHASHES};
|
||||
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufReader, Cursor, Error as IOError, Read};
|
||||
use std::path::Path;
|
||||
@ -251,6 +253,7 @@ impl Bank {
|
||||
for epoch in 0..=bank.get_stakers_epoch(bank.slot) {
|
||||
bank.epoch_stakes.insert(epoch, stakes.clone());
|
||||
}
|
||||
bank.update_stake_history(None);
|
||||
}
|
||||
bank.update_clock();
|
||||
bank
|
||||
@ -330,6 +333,7 @@ impl Bank {
|
||||
});
|
||||
|
||||
new.update_rewards(parent.epoch());
|
||||
new.update_stake_history(Some(parent.epoch()));
|
||||
new.update_clock();
|
||||
new.update_fees();
|
||||
new
|
||||
@ -401,8 +405,19 @@ impl Bank {
|
||||
self.store_account(&fees::id(), &fees::create_account(1, &self.fee_calculator));
|
||||
}
|
||||
|
||||
fn update_stake_history(&self, epoch: Option<Epoch>) {
|
||||
if epoch == Some(self.epoch()) {
|
||||
return;
|
||||
}
|
||||
// if I'm the first Bank in an epoch, ensure stake_history is updated
|
||||
self.store_account(
|
||||
&stake_history::id(),
|
||||
&stake_history::create_account(1, self.stakes.read().unwrap().history()),
|
||||
);
|
||||
}
|
||||
|
||||
// update reward for previous epoch
|
||||
fn update_rewards(&mut self, epoch: u64) {
|
||||
fn update_rewards(&mut self, epoch: Epoch) {
|
||||
if epoch == self.epoch() {
|
||||
return;
|
||||
}
|
||||
@ -626,7 +641,7 @@ impl Bank {
|
||||
if parents.is_empty() {
|
||||
self.last_blockhash_with_fee_calculator()
|
||||
} else {
|
||||
let index = cmp::min(NUM_BLOCKHASH_CONFIRMATIONS, parents.len() - 1);
|
||||
let index = NUM_BLOCKHASH_CONFIRMATIONS.min(parents.len() - 1);
|
||||
parents[index].last_blockhash_with_fee_calculator()
|
||||
}
|
||||
}
|
||||
@ -1294,7 +1309,7 @@ impl Bank {
|
||||
}
|
||||
|
||||
/// Return the number of slots per epoch for the given epoch
|
||||
pub fn get_slots_in_epoch(&self, epoch: u64) -> u64 {
|
||||
pub fn get_slots_in_epoch(&self, epoch: Epoch) -> u64 {
|
||||
self.epoch_schedule.get_slots_in_epoch(epoch)
|
||||
}
|
||||
|
||||
@ -1352,7 +1367,7 @@ impl Bank {
|
||||
|
||||
/// vote accounts for the specific epoch along with the stake
|
||||
/// attributed to each account
|
||||
pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
||||
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
||||
self.epoch_stakes.get(&epoch).map(Stakes::vote_accounts)
|
||||
}
|
||||
|
||||
@ -2428,6 +2443,7 @@ mod tests {
|
||||
|
||||
let leader_stake = Stake {
|
||||
stake: leader_lamports,
|
||||
activated: std::u64::MAX, // bootstrap
|
||||
..Stake::default()
|
||||
};
|
||||
|
||||
@ -2442,7 +2458,7 @@ mod tests {
|
||||
// epoch_stakes are a snapshot at the stakers_slot_offset boundary
|
||||
// in the prior epoch (0 in this case)
|
||||
assert_eq!(
|
||||
leader_stake.stake(0),
|
||||
leader_stake.stake(0, None),
|
||||
vote_accounts.unwrap().get(&leader_vote_account).unwrap().0
|
||||
);
|
||||
|
||||
@ -2458,7 +2474,7 @@ mod tests {
|
||||
|
||||
assert!(child.epoch_vote_accounts(epoch).is_some());
|
||||
assert_eq!(
|
||||
leader_stake.stake(child.epoch()),
|
||||
leader_stake.stake(child.epoch(), None),
|
||||
child
|
||||
.epoch_vote_accounts(epoch)
|
||||
.unwrap()
|
||||
@ -2476,7 +2492,7 @@ mod tests {
|
||||
);
|
||||
assert!(child.epoch_vote_accounts(epoch).is_some());
|
||||
assert_eq!(
|
||||
leader_stake.stake(child.epoch()),
|
||||
leader_stake.stake(child.epoch(), None),
|
||||
child
|
||||
.epoch_vote_accounts(epoch)
|
||||
.unwrap()
|
||||
|
@ -2,8 +2,9 @@
|
||||
//! node stakes
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::sysvar::stake_history::StakeHistory;
|
||||
use solana_sdk::timing::Epoch;
|
||||
use solana_stake_api::stake_state::StakeState;
|
||||
use solana_stake_api::stake_state::{new_stake_history_entry, StakeState};
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -21,13 +22,36 @@ pub struct Stakes {
|
||||
|
||||
/// current epoch, used to calculate current stake
|
||||
epoch: Epoch,
|
||||
|
||||
/// history of staking levels
|
||||
stake_history: StakeHistory,
|
||||
}
|
||||
|
||||
impl Stakes {
|
||||
pub fn history(&self) -> &StakeHistory {
|
||||
&self.stake_history
|
||||
}
|
||||
pub fn clone_with_epoch(&self, epoch: Epoch) -> Self {
|
||||
if self.epoch == epoch {
|
||||
self.clone()
|
||||
} else {
|
||||
let mut stake_history = self.stake_history.clone();
|
||||
|
||||
stake_history.add(
|
||||
self.epoch,
|
||||
new_stake_history_entry(
|
||||
self.epoch,
|
||||
self.stake_accounts
|
||||
.iter()
|
||||
.filter_map(|(_pubkey, stake_account)| {
|
||||
StakeState::stake_from(stake_account)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.iter(),
|
||||
Some(&self.stake_history),
|
||||
),
|
||||
);
|
||||
|
||||
Stakes {
|
||||
stake_accounts: self.stake_accounts.clone(),
|
||||
points: self.points,
|
||||
@ -38,22 +62,31 @@ impl Stakes {
|
||||
.map(|(pubkey, (_stake, account))| {
|
||||
(
|
||||
*pubkey,
|
||||
(self.calculate_stake(pubkey, epoch), account.clone()),
|
||||
(
|
||||
self.calculate_stake(pubkey, epoch, Some(&stake_history)),
|
||||
account.clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
stake_history,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sum the stakes that point to the given voter_pubkey
|
||||
fn calculate_stake(&self, voter_pubkey: &Pubkey, epoch: Epoch) -> u64 {
|
||||
fn calculate_stake(
|
||||
&self,
|
||||
voter_pubkey: &Pubkey,
|
||||
epoch: Epoch,
|
||||
stake_history: Option<&StakeHistory>,
|
||||
) -> u64 {
|
||||
self.stake_accounts
|
||||
.iter()
|
||||
.map(|(_, stake_account)| {
|
||||
StakeState::stake_from(stake_account).map_or(0, |stake| {
|
||||
if stake.voter_pubkey == *voter_pubkey {
|
||||
stake.stake(epoch)
|
||||
stake.stake(epoch, stake_history)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@ -75,7 +108,10 @@ impl Stakes {
|
||||
} else {
|
||||
let old = self.vote_accounts.get(pubkey);
|
||||
|
||||
let stake = old.map_or_else(|| self.calculate_stake(pubkey, self.epoch), |v| v.0);
|
||||
let stake = old.map_or_else(
|
||||
|| self.calculate_stake(pubkey, self.epoch, Some(&self.stake_history)),
|
||||
|v| v.0,
|
||||
);
|
||||
|
||||
// count any increase in points, can only go forward
|
||||
let old_credits = old
|
||||
@ -91,15 +127,19 @@ impl Stakes {
|
||||
} else if solana_stake_api::check_id(&account.owner) {
|
||||
// old_stake is stake lamports and voter_pubkey from the pre-store() version
|
||||
let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| {
|
||||
StakeState::stake_from(old_account)
|
||||
.map(|stake| (stake.voter_pubkey, stake.stake(self.epoch)))
|
||||
StakeState::stake_from(old_account).map(|stake| {
|
||||
(
|
||||
stake.voter_pubkey,
|
||||
stake.stake(self.epoch, Some(&self.stake_history)),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let stake = StakeState::stake_from(account).map(|stake| {
|
||||
(
|
||||
stake.voter_pubkey,
|
||||
if account.lamports != 0 {
|
||||
stake.stake(self.epoch)
|
||||
stake.stake(self.epoch, Some(&self.stake_history))
|
||||
} else {
|
||||
0
|
||||
},
|
||||
@ -165,7 +205,7 @@ impl Stakes {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_stake_api::stake_state::{self, STAKE_WARMUP_EPOCHS};
|
||||
use solana_stake_api::stake_state;
|
||||
use solana_vote_api::vote_state::{self, VoteState, MAX_LOCKOUT_HISTORY};
|
||||
|
||||
// set up some dummies for a staked node (( vote ) ( stake ))
|
||||
@ -188,7 +228,7 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stakes_basic() {
|
||||
for i in 0..STAKE_WARMUP_EPOCHS + 1 {
|
||||
for i in 0..4 {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = i;
|
||||
|
||||
@ -201,7 +241,10 @@ pub mod tests {
|
||||
{
|
||||
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));
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(i, None)
|
||||
);
|
||||
}
|
||||
|
||||
stake_account.lamports = 42;
|
||||
@ -209,7 +252,10 @@ pub mod tests {
|
||||
{
|
||||
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
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(i, None)
|
||||
); // stays old stake, because only 10 is activated
|
||||
}
|
||||
|
||||
// activate more
|
||||
@ -219,7 +265,10 @@ pub mod tests {
|
||||
{
|
||||
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
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(i, None)
|
||||
); // now stake of 42 is activated
|
||||
}
|
||||
|
||||
stake_account.lamports = 0;
|
||||
@ -258,7 +307,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_stakes_points() {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
|
||||
stakes.epoch = 4;
|
||||
|
||||
let stake = 42;
|
||||
assert_eq!(stakes.points(), 0);
|
||||
@ -304,7 +353,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_stakes_vote_account_disappear_reappear() {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
|
||||
stakes.epoch = 4;
|
||||
|
||||
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
|
||||
create_staked_node_accounts(10);
|
||||
@ -338,7 +387,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_stakes_change_delegate() {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
|
||||
stakes.epoch = 4;
|
||||
|
||||
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
|
||||
create_staked_node_accounts(10);
|
||||
@ -359,7 +408,7 @@ pub mod tests {
|
||||
assert!(vote_accounts.get(&vote_pubkey).is_some());
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(stakes.epoch)
|
||||
stake.stake(stakes.epoch, Some(&stakes.stake_history))
|
||||
);
|
||||
assert!(vote_accounts.get(&vote_pubkey2).is_some());
|
||||
assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0);
|
||||
@ -375,14 +424,14 @@ pub mod tests {
|
||||
assert!(vote_accounts.get(&vote_pubkey2).is_some());
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey2).unwrap().0,
|
||||
stake.stake(stakes.epoch)
|
||||
stake.stake(stakes.epoch, Some(&stakes.stake_history))
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_stakes_multiple_stakers() {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
|
||||
stakes.epoch = 4;
|
||||
|
||||
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
|
||||
create_staked_node_accounts(10);
|
||||
@ -416,7 +465,7 @@ pub mod tests {
|
||||
let vote_accounts = stakes.vote_accounts();
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(stakes.epoch)
|
||||
stake.stake(stakes.epoch, Some(&stakes.stake_history))
|
||||
);
|
||||
}
|
||||
let stakes = stakes.clone_with_epoch(3);
|
||||
@ -424,7 +473,7 @@ pub mod tests {
|
||||
let vote_accounts = stakes.vote_accounts();
|
||||
assert_eq!(
|
||||
vote_accounts.get(&vote_pubkey).unwrap().0,
|
||||
stake.stake(stakes.epoch)
|
||||
stake.stake(stakes.epoch, Some(&stakes.stake_history))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -432,7 +481,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_stakes_not_delegate() {
|
||||
let mut stakes = Stakes::default();
|
||||
stakes.epoch = STAKE_WARMUP_EPOCHS + 1;
|
||||
stakes.epoch = 4;
|
||||
|
||||
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
|
||||
create_staked_node_accounts(10);
|
||||
|
Reference in New Issue
Block a user