solana/runtime/src/stakes.rs

615 lines
22 KiB
Rust
Raw Normal View History

//! Stakes serve as a cache of stake and vote accounts to derive
//! node stakes
use crate::vote_account::{ArcVoteAccount, VoteAccounts};
2020-01-28 17:03:20 -08:00
use solana_sdk::{
2021-03-09 14:31:33 -08:00
account::{AccountSharedData, ReadableAccount},
clock::Epoch,
pubkey::Pubkey,
sysvar::stake_history::StakeHistory,
2020-01-28 17:03:20 -08:00
};
2019-11-25 13:14:32 -08:00
use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState};
use solana_vote_program::vote_state::VoteState;
use std::{borrow::Borrow, collections::HashMap};
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)]
pub struct Stakes {
/// vote accounts
vote_accounts: VoteAccounts,
2019-11-25 13:14:32 -08:00
/// stake_delegations
stake_delegations: HashMap<Pubkey, Delegation>,
/// unused
unused: u64,
2019-06-19 11:54:52 -07:00
/// 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, next_epoch: Epoch, fix_stake_deactivate: bool) -> Self {
let prev_epoch = self.epoch;
if prev_epoch == next_epoch {
2019-06-19 11:54:52 -07:00
self.clone()
} else {
// wrap up the prev epoch by adding new stake history entry for the prev epoch
let mut stake_history_upto_prev_epoch = self.stake_history.clone();
stake_history_upto_prev_epoch.add(
prev_epoch,
new_stake_history_entry(
prev_epoch,
2019-11-25 13:14:32 -08:00
self.stake_delegations
.iter()
2019-11-25 13:14:32 -08:00
.map(|(_pubkey, stake_delegation)| stake_delegation),
Some(&self.stake_history),
fix_stake_deactivate,
),
);
// refresh the stake distribution of vote accounts for the next epoch, using new stake history
let vote_accounts_for_next_epoch = self
.vote_accounts
.iter()
.map(|(pubkey, (_ /*stake*/, account))| {
let stake = self.calculate_stake(
pubkey,
next_epoch,
Some(&stake_history_upto_prev_epoch),
fix_stake_deactivate,
);
(*pubkey, (stake, account.clone()))
})
.collect();
2019-06-19 11:54:52 -07:00
Stakes {
2019-11-25 13:14:32 -08:00
stake_delegations: self.stake_delegations.clone(),
unused: self.unused,
epoch: next_epoch,
stake_history: stake_history_upto_prev_epoch,
vote_accounts: vote_accounts_for_next_epoch,
2019-06-19 11:54:52 -07:00
}
}
}
// sum the stakes that point to the given voter_pubkey
fn calculate_stake(
&self,
voter_pubkey: &Pubkey,
epoch: Epoch,
stake_history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 {
2019-11-25 13:14:32 -08:00
self.stake_delegations
.iter()
2019-11-25 13:14:32 -08:00
.map(|(_, stake_delegation)| {
if &stake_delegation.voter_pubkey == voter_pubkey {
stake_delegation.stake(epoch, stake_history, fix_stake_deactivate)
2019-11-25 13:14:32 -08:00
} else {
0
}
})
.sum()
}
pub fn vote_balance_and_staked(&self) -> u64 {
self.stake_delegations
.iter()
.map(|(_, stake_delegation)| stake_delegation.stake)
.sum::<u64>()
+ self
.vote_accounts
.iter()
.map(|(_pubkey, (_staked, vote_account))| vote_account.lamports())
.sum::<u64>()
}
2021-03-09 13:06:07 -08:00
pub fn is_stake(account: &AccountSharedData) -> bool {
solana_vote_program::check_id(&account.owner)
|| solana_stake_program::check_id(&account.owner)
2021-03-09 14:31:33 -08:00
&& account.data().len() >= std::mem::size_of::<StakeState>()
}
pub fn store(
&mut self,
pubkey: &Pubkey,
2021-03-09 13:06:07 -08:00
account: &AccountSharedData,
fix_stake_deactivate: bool,
check_vote_init: bool,
) -> Option<ArcVoteAccount> {
if solana_vote_program::check_id(&account.owner) {
// unconditionally remove existing at first; there is no dependent calculated state for
// votes, not like stakes (stake codepath maintains calculated stake value grouped by
// delegated vote pubkey)
let old = self.vote_accounts.remove(pubkey);
// when account is removed (lamports == 0 or data uninitialized), don't read so that
// given `pubkey` can be used for any owner in the future, while not affecting Stakes.
if account.lamports != 0
2021-03-09 14:31:33 -08:00
&& !(check_vote_init && VoteState::is_uninitialized_no_deser(&account.data()))
{
let stake = old.as_ref().map_or_else(
|| {
self.calculate_stake(
pubkey,
self.epoch,
Some(&self.stake_history),
fix_stake_deactivate,
)
},
|v| v.0,
);
self.vote_accounts
.insert(*pubkey, (stake, ArcVoteAccount::from(account.clone())));
}
old.map(|(_, account)| account)
} else if solana_stake_program::check_id(&account.owner) {
// old_stake is stake lamports and voter_pubkey from the pre-store() version
2019-11-25 13:14:32 -08:00
let old_stake = self.stake_delegations.get(pubkey).map(|delegation| {
(
delegation.voter_pubkey,
delegation.stake(self.epoch, Some(&self.stake_history), fix_stake_deactivate),
2019-11-25 13:14:32 -08:00
)
2019-06-19 11:54:52 -07:00
});
2019-11-25 13:14:32 -08:00
let delegation = StakeState::delegation_from(account);
let stake = delegation.map(|delegation| {
2019-06-19 11:54:52 -07:00
(
2019-11-25 13:14:32 -08:00
delegation.voter_pubkey,
2019-06-19 11:54:52 -07:00
if account.lamports != 0 {
delegation.stake(
self.epoch,
Some(&self.stake_history),
fix_stake_deactivate,
)
2019-06-19 11:54:52 -07:00
} else {
// when account is removed (lamports == 0), this special `else` clause ensures
// resetting cached stake value below, even if the account happens to be
// still staked for some (odd) reason
2019-06-19 11:54:52 -07:00
0
},
)
});
// if adjustments need to be made...
if stake != old_stake {
2019-06-19 11:54:52 -07:00
if let Some((voter_pubkey, stake)) = old_stake {
self.vote_accounts.sub_stake(&voter_pubkey, stake);
}
if let Some((voter_pubkey, stake)) = stake {
self.vote_accounts.add_stake(&voter_pubkey, stake);
}
}
if account.lamports == 0 {
// when account is removed (lamports == 0), remove it from Stakes as well
// so that given `pubkey` can be used for any owner in the future, while not
// affecting Stakes.
2019-11-25 13:14:32 -08:00
self.stake_delegations.remove(pubkey);
} else if let Some(delegation) = delegation {
self.stake_delegations.insert(*pubkey, delegation);
}
None
} else {
// there is no need to remove possibly existing Stakes cache entries with given
// `pubkey` because this isn't possible, first of all.
// Runtime always enforces an intermediary write of account.lamports == 0,
// when not-System111-owned account.owner is swapped.
None
}
}
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, ArcVoteAccount)> {
self.vote_accounts.borrow()
}
pub fn stake_delegations(&self) -> &HashMap<Pubkey, Delegation> {
&self.stake_delegations
}
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> {
self.vote_accounts.staked_nodes()
}
pub fn highest_staked_node(&self) -> Option<Pubkey> {
let (_pubkey, (_stake, vote_account)) = self
.vote_accounts
.iter()
.max_by(|(_ak, av), (_bk, bv)| av.0.cmp(&bv.0))?;
let node_pubkey = vote_account.vote_state().as_ref().ok()?.node_pubkey;
Some(node_pubkey)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use solana_sdk::{pubkey::Pubkey, rent::Rent};
use solana_stake_program::stake_state;
use solana_vote_program::vote_state::{self, VoteState, VoteStateVersions};
// set up some dummies for a staked node (( vote ) ( stake ))
2021-03-09 13:06:07 -08:00
pub fn create_staked_node_accounts(
stake: u64,
) -> ((Pubkey, AccountSharedData), (Pubkey, AccountSharedData)) {
let vote_pubkey = solana_sdk::pubkey::new_rand();
2020-10-19 12:23:14 -07:00
let vote_account =
vote_state::create_account(&vote_pubkey, &solana_sdk::pubkey::new_rand(), 0, 1);
(
(vote_pubkey, vote_account),
create_stake_account(stake, &vote_pubkey),
)
}
// add stake to a vote_pubkey ( stake )
2021-03-09 13:06:07 -08:00
pub fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, AccountSharedData) {
let stake_pubkey = solana_sdk::pubkey::new_rand();
(
stake_pubkey,
stake_state::create_account(
&stake_pubkey,
&vote_pubkey,
&vote_state::create_account(&vote_pubkey, &solana_sdk::pubkey::new_rand(), 0, 1),
&Rent::free(),
stake,
),
)
}
pub fn create_warming_staked_node_accounts(
stake: u64,
epoch: Epoch,
2021-03-09 13:06:07 -08:00
) -> ((Pubkey, AccountSharedData), (Pubkey, AccountSharedData)) {
let vote_pubkey = solana_sdk::pubkey::new_rand();
2020-10-19 12:23:14 -07:00
let vote_account =
vote_state::create_account(&vote_pubkey, &solana_sdk::pubkey::new_rand(), 0, 1);
(
(vote_pubkey, vote_account),
create_warming_stake_account(stake, epoch, &vote_pubkey),
)
}
2020-12-13 17:26:34 -08:00
// add stake to a vote_pubkey ( stake )
pub fn create_warming_stake_account(
stake: u64,
epoch: Epoch,
vote_pubkey: &Pubkey,
2021-03-09 13:06:07 -08:00
) -> (Pubkey, AccountSharedData) {
let stake_pubkey = solana_sdk::pubkey::new_rand();
(
stake_pubkey,
stake_state::create_account_with_activation_epoch(
&stake_pubkey,
&vote_pubkey,
&vote_state::create_account(&vote_pubkey, &solana_sdk::pubkey::new_rand(), 0, 1),
&Rent::free(),
stake,
epoch,
),
)
}
#[test]
fn test_stakes_basic() {
for i in 0..4 {
2020-12-13 17:26:34 -08:00
let mut stakes = Stakes {
epoch: i,
..Stakes::default()
};
2019-06-19 11:54:52 -07:00
let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
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, None, true)
);
2019-06-19 11:54:52 -07:00
}
2019-06-19 11:54:52 -07:00
stake_account.lamports = 42;
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
{
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, None, true)
); // stays old stake, because only 10 is activated
2019-06-19 11:54:52 -07:00
}
2019-06-19 11:54:52 -07:00
// activate more
let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey);
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
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, None, true)
); // now stake of 42 is activated
2019-06-19 11:54:52 -07:00
}
2019-06-19 11:54:52 -07:00
stake_account.lamports = 0;
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
}
}
}
#[test]
fn test_stakes_highest() {
let mut stakes = Stakes::default();
assert_eq!(stakes.highest_staked_node(), None);
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) =
2019-06-19 11:54:52 -07:00
create_staked_node_accounts(20);
stakes.store(&vote11_pubkey, &vote11_account, true, true);
stakes.store(&stake11_pubkey, &stake11_account, true, true);
let vote11_node_pubkey = VoteState::from(&vote11_account).unwrap().node_pubkey;
assert_eq!(stakes.highest_staked_node(), Some(vote11_node_pubkey))
}
#[test]
fn test_stakes_vote_account_disappear_reappear() {
2020-12-13 17:26:34 -08:00
let mut stakes = Stakes {
epoch: 4,
..Stakes::default()
};
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10);
}
vote_account.lamports = 0;
stakes.store(&vote_pubkey, &vote_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_none());
}
vote_account.lamports = 1;
stakes.store(&vote_pubkey, &vote_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10);
}
// Vote account too big
2021-03-09 14:31:33 -08:00
let cache_data = vote_account.data().to_vec();
vote_account.data.push(0);
stakes.store(&vote_pubkey, &vote_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_none());
}
// Vote account uninitialized
let default_vote_state = VoteState::default();
let versioned = VoteStateVersions::new_current(default_vote_state);
VoteState::to(&versioned, &mut vote_account).unwrap();
stakes.store(&vote_pubkey, &vote_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_none());
}
vote_account.data = cache_data;
stakes.store(&vote_pubkey, &vote_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10);
}
}
#[test]
fn test_stakes_change_delegate() {
2020-12-13 17:26:34 -08:00
let mut stakes = Stakes {
epoch: 4,
..Stakes::default()
};
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&vote_pubkey2, &vote_account2, true, true);
// delegates to vote_pubkey
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
let stake = StakeState::stake_from(&stake_account).unwrap();
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
2019-06-19 11:54:52 -07:00
assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
2019-06-19 11:54:52 -07:00
);
assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0);
}
// delegates to vote_pubkey2
stakes.store(&stake_pubkey, &stake_account2, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
assert!(vote_accounts.get(&vote_pubkey2).is_some());
2019-06-19 11:54:52 -07:00
assert_eq!(
vote_accounts.get(&vote_pubkey2).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
2019-06-19 11:54:52 -07:00
);
}
}
#[test]
fn test_stakes_multiple_stakers() {
2020-12-13 17:26:34 -08:00
let mut stakes = Stakes {
epoch: 4,
..Stakes::default()
};
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
let (stake_pubkey2, stake_account2) = create_stake_account(10, &vote_pubkey);
stakes.store(&vote_pubkey, &vote_account, true, true);
// delegates to vote_pubkey
stakes.store(&stake_pubkey, &stake_account, true, true);
stakes.store(&stake_pubkey2, &stake_account2, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 20);
}
}
2019-06-19 11:54:52 -07:00
#[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, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
2019-06-19 11:54:52 -07:00
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, Some(&stakes.stake_history), true)
2019-06-19 11:54:52 -07:00
);
}
let stakes = stakes.clone_with_epoch(3, true);
2019-06-19 11:54:52 -07:00
{
let vote_accounts = stakes.vote_accounts();
assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
2019-06-19 11:54:52 -07:00
);
}
}
#[test]
fn test_stakes_not_delegate() {
2020-12-13 17:26:34 -08:00
let mut stakes = Stakes {
epoch: 4,
..Stakes::default()
};
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 10);
}
// not a stake account, and whacks above entry
stakes.store(
&stake_pubkey,
2021-03-09 13:06:07 -08:00
&AccountSharedData::new(1, 0, &solana_stake_program::id()),
true,
true,
);
{
let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey).unwrap().0, 0);
}
}
#[test]
fn test_vote_balance_and_staked_empty() {
let stakes = Stakes::default();
assert_eq!(stakes.vote_balance_and_staked(), 0);
}
#[test]
fn test_vote_balance_and_staked_normal() {
let mut stakes = Stakes::default();
impl Stakes {
pub fn vote_balance_and_warmed_staked(&self) -> u64 {
self.vote_accounts
.iter()
.map(|(_pubkey, (staked, account))| staked + account.lamports())
.sum()
}
}
let genesis_epoch = 0;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_warming_staked_node_accounts(10, genesis_epoch);
stakes.store(&vote_pubkey, &vote_account, true, true);
stakes.store(&stake_pubkey, &stake_account, true, true);
assert_eq!(stakes.vote_balance_and_staked(), 11);
assert_eq!(stakes.vote_balance_and_warmed_staked(), 1);
for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) {
stakes = stakes.clone_with_epoch(epoch, true);
// vote_balance_and_staked() always remain to return same lamports
// while vote_balance_and_warmed_staked() gradually increases
assert_eq!(stakes.vote_balance_and_staked(), 11);
assert_eq!(
stakes.vote_balance_and_warmed_staked(),
*expected_warmed_stake
);
}
}
}