fix rewards points (#10914)

* fix rewards points

* fixups

* * verify that we don't spend more in rewards than we've allocated for rewards
* purge f64s from calculations that could be done with integers

* test typical values

* simplify iteration over delegations some

* fixups

* Use try_from

* Add a comment for commission_split()

* Add assertion to detect inconsistent reward dist.

* Fix vote_balance_and_staked

* Don't overwrite accounts with stale copies

* Fix CI...

* Add tests for vote_balance_and_staked

* Add test for the determinism of update_rewards

* Revert "Don't overwrite accounts with stale copies"

This reverts commit 9886d085a6.

* Make stake_delegation_accounts to return hashmap

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
This commit is contained in:
Rob Walker
2020-07-20 21:57:25 -07:00
committed by GitHub
parent b0d1c70718
commit 7cc2a6801b
5 changed files with 562 additions and 239 deletions

View File

@@ -15,9 +15,8 @@ pub struct Stakes {
/// stake_delegations
stake_delegations: HashMap<Pubkey, Delegation>,
/// unclaimed points.
// a point is a credit multiplied by the stake
points: u64,
/// unused
unused: u64,
/// current epoch, used to calculate current stake
epoch: Epoch,
@@ -49,7 +48,7 @@ impl Stakes {
Stakes {
stake_delegations: self.stake_delegations.clone(),
points: self.points,
unused: self.unused,
epoch,
vote_accounts: self
.vote_accounts
@@ -88,6 +87,18 @@ impl Stakes {
.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>()
}
pub fn is_stake(account: &Account) -> bool {
solana_vote_program::check_id(&account.owner)
|| solana_stake_program::check_id(&account.owner)
@@ -106,15 +117,6 @@ impl Stakes {
|v| v.0,
);
// count any increase in points, can only go forward
let old_credits = old
.and_then(|(_stake, old_account)| VoteState::credits_from(old_account))
.unwrap_or(0);
let credits = VoteState::credits_from(account).unwrap_or(old_credits);
self.points += credits.saturating_sub(old_credits) * stake;
self.vote_accounts.insert(*pubkey, (stake, account.clone()));
}
} else if solana_stake_program::check_id(&account.owner) {
@@ -176,18 +178,6 @@ impl Stakes {
.and_then(|(_k, (_stake, account))| VoteState::from(account))
.map(|vote_state| vote_state.node_pubkey)
}
/// currently unclaimed points
pub fn points(&self) -> u64 {
self.points
}
/// "claims" points, resets points to 0
pub fn claim_points(&mut self) -> u64 {
let points = self.points;
self.points = 0;
points
}
}
#[cfg(test)]
@@ -195,9 +185,7 @@ 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, MAX_LOCKOUT_HISTORY,
};
use solana_vote_program::vote_state::{self, VoteState};
// set up some dummies for a staked node (( vote ) ( stake ))
pub fn create_staked_node_accounts(stake: u64) -> ((Pubkey, Account), (Pubkey, Account)) {
@@ -224,6 +212,38 @@ pub mod tests {
)
}
pub fn create_warming_staked_node_accounts(
stake: u64,
epoch: Epoch,
) -> ((Pubkey, Account), (Pubkey, Account)) {
let vote_pubkey = Pubkey::new_rand();
let vote_account = vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1);
(
(vote_pubkey, vote_account),
create_warming_stake_account(stake, epoch, &vote_pubkey),
)
}
// add stake to a vote_pubkey ( stake )
pub fn create_warming_stake_account(
stake: u64,
epoch: Epoch,
vote_pubkey: &Pubkey,
) -> (Pubkey, Account) {
let stake_pubkey = Pubkey::new_rand();
(
stake_pubkey,
stake_state::create_account_with_activation_epoch(
&stake_pubkey,
&vote_pubkey,
&vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1),
&Rent::free(),
stake,
epoch,
),
)
}
#[test]
fn test_stakes_basic() {
for i in 0..4 {
@@ -302,76 +322,6 @@ pub mod tests {
assert_eq!(stakes.highest_staked_node(), Some(vote11_node_pubkey))
}
#[test]
fn test_stakes_points() {
let mut stakes = Stakes::default();
stakes.epoch = 4;
let stake = 42;
assert_eq!(stakes.points(), 0);
assert_eq!(stakes.claim_points(), 0);
assert_eq!(stakes.claim_points(), 0);
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(stake);
stakes.store(&vote_pubkey, &vote_account);
stakes.store(&stake_pubkey, &stake_account);
assert_eq!(stakes.points(), 0);
assert_eq!(stakes.claim_points(), 0);
let mut vote_state = Some(VoteState::from(&vote_account).unwrap());
for i in 0..MAX_LOCKOUT_HISTORY + 42 {
if let Some(v) = vote_state.as_mut() {
v.process_slot_vote_unchecked(i as u64)
}
let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
VoteState::to(&versioned, &mut vote_account).unwrap();
match versioned {
VoteStateVersions::Current(v) => {
vote_state = Some(*v);
}
_ => panic!("Has to be of type Current"),
};
stakes.store(&vote_pubkey, &vote_account);
assert_eq!(
stakes.points(),
vote_state.as_ref().unwrap().credits() * stake
);
}
vote_account.lamports = 0;
stakes.store(&vote_pubkey, &vote_account);
assert_eq!(
stakes.points(),
vote_state.as_ref().unwrap().credits() * stake
);
assert_eq!(
stakes.claim_points(),
vote_state.as_ref().unwrap().credits() * stake
);
assert_eq!(stakes.claim_points(), 0);
assert_eq!(stakes.claim_points(), 0);
// points come out of nowhere, but don't care here ;)
vote_account.lamports = 1;
stakes.store(&vote_pubkey, &vote_account);
assert_eq!(
stakes.points(),
vote_state.as_ref().unwrap().credits() * stake
);
// test going backwards, should never go backwards
let old_vote_state = vote_state;
let vote_account = vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1);
stakes.store(&vote_pubkey, &vote_account);
assert_eq!(
stakes.points(),
old_vote_state.as_ref().unwrap().credits() * stake
);
}
#[test]
fn test_stakes_vote_account_disappear_reappear() {
let mut stakes = Stakes::default();
@@ -528,4 +478,43 @@ pub mod tests {
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);
stakes.store(&stake_pubkey, &stake_account);
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);
// 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
);
}
}
}