caches staked nodes computed from vote-accounts (#13929)

This commit is contained in:
behzad nouri
2020-12-17 21:22:50 +00:00
committed by GitHub
parent fd7d2f82ae
commit d6d76219b6
11 changed files with 367 additions and 78 deletions

View File

@ -4293,6 +4293,10 @@ impl Bank {
self.stakes.read().unwrap().stake_delegations().clone()
}
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> {
self.stakes.read().unwrap().staked_nodes()
}
/// current vote accounts for this bank along with the stake
/// attributed to each account
/// Note: This clones the entire vote-accounts hashmap. For a single
@ -4323,6 +4327,10 @@ impl Bank {
&self.epoch_stakes
}
pub fn epoch_staked_nodes(&self, epoch: Epoch) -> Option<HashMap<Pubkey, u64>> {
Some(self.epoch_stakes.get(&epoch)?.stakes().staked_nodes())
}
/// vote accounts for the specific epoch along with the stake
/// attributed to each account
pub fn epoch_vote_accounts(

View File

@ -261,7 +261,7 @@ mod test_bank_serialize {
// These some what long test harness is required to freeze the ABI of
// Bank's serialization due to versioned nature
#[frozen_abi(digest = "5NHt6PLRJPWJH9FUcweSsUWgN5hXMfXj1BduDrDHH73w")]
#[frozen_abi(digest = "Gv3em1cZt9cjQWepg8C5aaK95deyA1fifowRfmmTuoES")]
#[derive(Serialize, AbiExample)]
pub struct BankAbiTestWrapperFuture {
#[serde(serialize_with = "wrapper_future")]

View File

@ -1,16 +1,16 @@
//! Stakes serve as a cache of stake and vote accounts to derive
//! node stakes
use crate::vote_account::ArcVoteAccount;
use crate::vote_account::{ArcVoteAccount, VoteAccounts};
use solana_sdk::{
account::Account, clock::Epoch, pubkey::Pubkey, sysvar::stake_history::StakeHistory,
};
use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState};
use std::collections::HashMap;
use std::{borrow::Borrow, collections::HashMap};
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)]
pub struct Stakes {
/// vote accounts
vote_accounts: HashMap<Pubkey, (u64, ArcVoteAccount)>,
vote_accounts: VoteAccounts,
/// stake_delegations
stake_delegations: HashMap<Pubkey, Delegation>,
@ -52,19 +52,14 @@ impl Stakes {
let vote_accounts_for_next_epoch = self
.vote_accounts
.iter()
.map(|(pubkey, (_stake, account))| {
(
*pubkey,
(
self.calculate_stake(
pubkey,
next_epoch,
Some(&stake_history_upto_prev_epoch),
fix_stake_deactivate,
),
account.clone(),
),
)
.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();
@ -179,14 +174,10 @@ impl Stakes {
// if adjustments need to be made...
if stake != old_stake {
if let Some((voter_pubkey, stake)) = old_stake {
self.vote_accounts
.entry(voter_pubkey)
.and_modify(|e| e.0 -= stake);
self.vote_accounts.sub_stake(&voter_pubkey, stake);
}
if let Some((voter_pubkey, stake)) = stake {
self.vote_accounts
.entry(voter_pubkey)
.and_modify(|e| e.0 += stake);
self.vote_accounts.add_stake(&voter_pubkey, stake);
}
}
@ -209,13 +200,17 @@ impl Stakes {
}
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, ArcVoteAccount)> {
&self.vote_accounts
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

View File

@ -1,9 +1,15 @@
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use solana_sdk::{account::Account, instruction::InstructionError};
use solana_sdk::{account::Account, instruction::InstructionError, pubkey::Pubkey};
use solana_vote_program::vote_state::VoteState;
use std::ops::Deref;
use std::sync::{Arc, Once, RwLock, RwLockReadGuard};
use std::{
borrow::Borrow,
cmp::Ordering,
collections::{hash_map::Entry, HashMap},
iter::FromIterator,
ops::Deref,
sync::{Arc, Once, RwLock, RwLockReadGuard},
};
// The value here does not matter. It will be overwritten
// at the first call to VoteAccount::vote_state().
@ -20,6 +26,18 @@ pub struct VoteAccount {
vote_state_once: Once,
}
#[derive(Debug, AbiExample)]
pub struct VoteAccounts {
vote_accounts: HashMap<Pubkey, (u64 /*stake*/, ArcVoteAccount)>,
staked_nodes: RwLock<
HashMap<
Pubkey, // VoteAccount.vote_state.node_pubkey.
u64, // Total stake across all vote-accounts.
>,
>,
staked_nodes_once: Once,
}
impl VoteAccount {
pub fn lamports(&self) -> u64 {
self.account.lamports
@ -31,6 +49,100 @@ impl VoteAccount {
});
self.vote_state.read().unwrap()
}
/// VoteState.node_pubkey of this vote-account.
fn node_pubkey(&self) -> Option<Pubkey> {
Some(self.vote_state().as_ref().ok()?.node_pubkey)
}
}
impl VoteAccounts {
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> {
self.staked_nodes_once.call_once(|| {
let mut staked_nodes = HashMap::new();
for (stake, vote_account) in
self.vote_accounts.values().filter(|(stake, _)| *stake != 0)
{
if let Some(node_pubkey) = vote_account.node_pubkey() {
staked_nodes
.entry(node_pubkey)
.and_modify(|s| *s += *stake)
.or_insert(*stake);
}
}
*self.staked_nodes.write().unwrap() = staked_nodes
});
self.staked_nodes.read().unwrap().clone()
}
pub fn iter(&self) -> impl Iterator<Item = (&Pubkey, &(u64, ArcVoteAccount))> {
self.vote_accounts.iter()
}
pub fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, ArcVoteAccount)) {
self.add_node_stake(stake, &vote_account);
if let Some((stake, vote_account)) =
self.vote_accounts.insert(pubkey, (stake, vote_account))
{
self.sub_node_stake(stake, &vote_account);
}
}
pub fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, ArcVoteAccount)> {
let value = self.vote_accounts.remove(pubkey);
if let Some((stake, ref vote_account)) = value {
self.sub_node_stake(stake, vote_account);
}
value
}
pub fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) {
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
*stake += delta;
let vote_account = vote_account.clone();
self.add_node_stake(delta, &vote_account);
}
}
pub fn sub_stake(&mut self, pubkey: &Pubkey, delta: u64) {
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
*stake = stake
.checked_sub(delta)
.expect("subtraction value exceeds account's stake");
let vote_account = vote_account.clone();
self.sub_node_stake(delta, &vote_account);
}
}
fn add_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) {
if stake != 0 && self.staked_nodes_once.is_completed() {
if let Some(node_pubkey) = vote_account.node_pubkey() {
self.staked_nodes
.write()
.unwrap()
.entry(node_pubkey)
.and_modify(|s| *s += stake)
.or_insert(stake);
}
}
}
fn sub_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) {
if stake != 0 && self.staked_nodes_once.is_completed() {
if let Some(node_pubkey) = vote_account.node_pubkey() {
match self.staked_nodes.write().unwrap().entry(node_pubkey) {
Entry::Vacant(_) => panic!("this should not happen!"),
Entry::Occupied(mut entry) => match entry.get().cmp(&stake) {
Ordering::Less => panic!("subtraction value exceeds node's stake"),
Ordering::Equal => {
entry.remove_entry();
}
Ordering::Greater => *entry.get_mut() -= stake,
},
}
}
}
}
}
impl Deref for ArcVoteAccount {
@ -92,16 +204,104 @@ impl PartialEq<VoteAccount> for VoteAccount {
}
}
impl Default for VoteAccounts {
fn default() -> Self {
Self {
vote_accounts: HashMap::default(),
staked_nodes: RwLock::default(),
staked_nodes_once: Once::new(),
}
}
}
impl Clone for VoteAccounts {
fn clone(&self) -> Self {
if self.staked_nodes_once.is_completed() {
let staked_nodes = self.staked_nodes.read().unwrap().clone();
let other = Self {
vote_accounts: self.vote_accounts.clone(),
staked_nodes: RwLock::new(staked_nodes),
staked_nodes_once: Once::new(),
};
other.staked_nodes_once.call_once(|| {});
other
} else {
Self {
vote_accounts: self.vote_accounts.clone(),
staked_nodes: RwLock::default(),
staked_nodes_once: Once::new(),
}
}
}
}
impl PartialEq<VoteAccounts> for VoteAccounts {
fn eq(&self, other: &Self) -> bool {
self.vote_accounts == other.vote_accounts
}
}
type VoteAccountsHashMap = HashMap<Pubkey, (u64 /*stake*/, ArcVoteAccount)>;
impl From<VoteAccountsHashMap> for VoteAccounts {
fn from(vote_accounts: VoteAccountsHashMap) -> Self {
Self {
vote_accounts,
staked_nodes: RwLock::default(),
staked_nodes_once: Once::new(),
}
}
}
impl Borrow<VoteAccountsHashMap> for VoteAccounts {
fn borrow(&self) -> &VoteAccountsHashMap {
&self.vote_accounts
}
}
impl FromIterator<(Pubkey, (u64 /*stake*/, ArcVoteAccount))> for VoteAccounts {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = (Pubkey, (u64, ArcVoteAccount))>,
{
Self::from(HashMap::from_iter(iter))
}
}
impl Serialize for VoteAccounts {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.vote_accounts.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for VoteAccounts {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let vote_accounts = VoteAccountsHashMap::deserialize(deserializer)?;
Ok(Self::from(vote_accounts))
}
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::Options;
use rand::Rng;
use solana_sdk::{pubkey::Pubkey, sysvar::clock::Clock};
use solana_vote_program::vote_state::{VoteInit, VoteStateVersions};
use std::iter::repeat_with;
fn new_rand_vote_account<R: Rng>(rng: &mut R) -> (Account, VoteState) {
fn new_rand_vote_account<R: Rng>(
rng: &mut R,
node_pubkey: Option<Pubkey>,
) -> (Account, VoteState) {
let vote_init = VoteInit {
node_pubkey: Pubkey::new_unique(),
node_pubkey: node_pubkey.unwrap_or_else(Pubkey::new_unique),
authorized_voter: Pubkey::new_unique(),
authorized_withdrawer: Pubkey::new_unique(),
commission: rng.gen(),
@ -123,10 +323,42 @@ mod tests {
(account, vote_state)
}
fn new_rand_vote_accounts<R: Rng>(
rng: &mut R,
num_nodes: usize,
) -> impl Iterator<Item = (Pubkey, (u64 /*stake*/, ArcVoteAccount))> + '_ {
let nodes: Vec<_> = repeat_with(Pubkey::new_unique).take(num_nodes).collect();
repeat_with(move || {
let node = nodes[rng.gen_range(0, nodes.len())];
let (account, _) = new_rand_vote_account(rng, Some(node));
let stake = rng.gen_range(0, 997);
(Pubkey::new_unique(), (stake, ArcVoteAccount::from(account)))
})
}
fn staked_nodes<'a, I>(vote_accounts: I) -> HashMap<Pubkey, u64>
where
I: IntoIterator<Item = &'a (Pubkey, (u64, ArcVoteAccount))>,
{
let mut staked_nodes = HashMap::new();
for (_, (stake, vote_account)) in vote_accounts
.into_iter()
.filter(|(_, (stake, _))| *stake != 0)
{
if let Some(node_pubkey) = vote_account.node_pubkey() {
staked_nodes
.entry(node_pubkey)
.and_modify(|s| *s += *stake)
.or_insert(*stake);
}
}
staked_nodes
}
#[test]
fn test_vote_account() {
let mut rng = rand::thread_rng();
let (account, vote_state) = new_rand_vote_account(&mut rng);
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
let lamports = account.lamports;
let vote_account = ArcVoteAccount::from(account);
assert_eq!(lamports, vote_account.lamports());
@ -138,7 +370,7 @@ mod tests {
#[test]
fn test_vote_account_serialize() {
let mut rng = rand::thread_rng();
let (account, vote_state) = new_rand_vote_account(&mut rng);
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
let vote_account = ArcVoteAccount::from(account.clone());
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
// Assert than ArcVoteAccount has the same wire format as Account.
@ -151,7 +383,7 @@ mod tests {
#[test]
fn test_vote_account_deserialize() {
let mut rng = rand::thread_rng();
let (account, vote_state) = new_rand_vote_account(&mut rng);
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
let data = bincode::serialize(&account).unwrap();
let vote_account = ArcVoteAccount::from(account);
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
@ -166,7 +398,7 @@ mod tests {
#[test]
fn test_vote_account_round_trip() {
let mut rng = rand::thread_rng();
let (account, vote_state) = new_rand_vote_account(&mut rng);
let (account, vote_state) = new_rand_vote_account(&mut rng, None);
let vote_account = ArcVoteAccount::from(account);
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
let data = bincode::serialize(&vote_account).unwrap();
@ -178,4 +410,90 @@ mod tests {
*other_vote_account.vote_state().as_ref().unwrap()
);
}
#[test]
fn test_vote_accounts_serialize() {
let mut rng = rand::thread_rng();
let vote_accounts_hash_map: HashMap<Pubkey, (u64, ArcVoteAccount)> =
new_rand_vote_accounts(&mut rng, 64).take(1024).collect();
let vote_accounts = VoteAccounts::from(vote_accounts_hash_map.clone());
assert!(vote_accounts.staked_nodes().len() > 32);
assert_eq!(
bincode::serialize(&vote_accounts).unwrap(),
bincode::serialize(&vote_accounts_hash_map).unwrap(),
);
assert_eq!(
bincode::options().serialize(&vote_accounts).unwrap(),
bincode::options()
.serialize(&vote_accounts_hash_map)
.unwrap(),
)
}
#[test]
fn test_vote_accounts_deserialize() {
let mut rng = rand::thread_rng();
let vote_accounts_hash_map: HashMap<Pubkey, (u64, ArcVoteAccount)> =
new_rand_vote_accounts(&mut rng, 64).take(1024).collect();
let data = bincode::serialize(&vote_accounts_hash_map).unwrap();
let vote_accounts: VoteAccounts = bincode::deserialize(&data).unwrap();
assert!(vote_accounts.staked_nodes().len() > 32);
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
let data = bincode::options()
.serialize(&vote_accounts_hash_map)
.unwrap();
let vote_accounts: VoteAccounts = bincode::options().deserialize(&data).unwrap();
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
}
#[test]
fn test_staked_nodes() {
let mut rng = rand::thread_rng();
let mut accounts: Vec<_> = new_rand_vote_accounts(&mut rng, 64).take(1024).collect();
let mut vote_accounts = VoteAccounts::default();
// Add vote accounts.
for (k, (pubkey, (stake, vote_account))) in accounts.iter().enumerate() {
vote_accounts.insert(*pubkey, (*stake, vote_account.clone()));
if (k + 1) % 128 == 0 {
assert_eq!(
staked_nodes(&accounts[..k + 1]),
vote_accounts.staked_nodes()
);
}
}
// Remove some of the vote accounts.
for k in 0..256 {
let index = rng.gen_range(0, accounts.len());
let (pubkey, (_, _)) = accounts.swap_remove(index);
vote_accounts.remove(&pubkey);
if (k + 1) % 32 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes());
}
}
// Modify the stakes for some of the accounts.
for k in 0..2048 {
let index = rng.gen_range(0, accounts.len());
let (pubkey, (stake, _)) = &mut accounts[index];
let new_stake = rng.gen_range(0, 997);
if new_stake < *stake {
vote_accounts.sub_stake(pubkey, *stake - new_stake);
} else {
vote_accounts.add_stake(pubkey, new_stake - *stake);
}
*stake = new_stake;
if (k + 1) % 128 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes());
}
}
// Remove everything.
while !accounts.is_empty() {
let index = rng.gen_range(0, accounts.len());
let (pubkey, (_, _)) = accounts.swap_remove(index);
vote_accounts.remove(&pubkey);
if accounts.len() % 32 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes());
}
}
assert!(vote_accounts.staked_nodes.read().unwrap().is_empty());
}
}