2019-02-28 13:15:25 -08:00
|
|
|
use solana_runtime::bank::Bank;
|
2019-03-03 18:04:13 -08:00
|
|
|
use solana_sdk::account::Account;
|
2019-02-28 13:15:25 -08:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2019-03-02 14:51:26 -07:00
|
|
|
use solana_vote_api::vote_state::VoteState;
|
2019-03-03 18:04:13 -08:00
|
|
|
use std::borrow::Borrow;
|
2019-05-09 19:27:06 -07:00
|
|
|
use std::collections::HashMap;
|
2019-02-28 13:15:25 -08:00
|
|
|
|
|
|
|
/// Looks through vote accounts, and finds the latest slot that has achieved
|
|
|
|
/// supermajority lockout
|
|
|
|
pub fn get_supermajority_slot(bank: &Bank, epoch_height: u64) -> Option<u64> {
|
|
|
|
// Find the amount of stake needed for supermajority
|
|
|
|
let stakes_and_lockouts = epoch_stakes_and_lockouts(bank, epoch_height);
|
2019-03-01 17:31:59 -08:00
|
|
|
let total_stake: u64 = stakes_and_lockouts.iter().map(|s| s.0).sum();
|
2019-02-28 13:15:25 -08:00
|
|
|
let supermajority_stake = total_stake * 2 / 3;
|
|
|
|
|
|
|
|
// Filter out the states that don't have a max lockout
|
2019-03-01 17:31:59 -08:00
|
|
|
find_supermajority_slot(supermajority_stake, stakes_and_lockouts.iter())
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
pub fn vote_account_balances(bank: &Bank) -> HashMap<Pubkey, u64> {
|
|
|
|
let node_staked_accounts = node_staked_accounts(bank);
|
|
|
|
node_staked_accounts
|
|
|
|
.map(|(id, stake, _)| (id, stake))
|
|
|
|
.collect()
|
2019-03-01 17:31:59 -08:00
|
|
|
}
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
/// Collect the delegate account balance and vote states for delegates have non-zero balance in
|
|
|
|
/// any of their managed staking accounts
|
|
|
|
pub fn delegated_stakes(bank: &Bank) -> HashMap<Pubkey, u64> {
|
|
|
|
let node_staked_accounts = node_staked_accounts(bank);
|
|
|
|
let node_staked_vote_states = to_vote_state(node_staked_accounts);
|
|
|
|
to_delegated_stakes(node_staked_vote_states)
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
/// At the specified epoch, collect the node account balance and vote states for nodes that
|
|
|
|
/// have non-zero balance in their corresponding staking accounts
|
|
|
|
pub fn vote_account_balances_at_epoch(
|
2019-02-28 13:15:25 -08:00
|
|
|
bank: &Bank,
|
|
|
|
epoch_height: u64,
|
2019-03-03 18:04:13 -08:00
|
|
|
) -> Option<HashMap<Pubkey, u64>> {
|
|
|
|
let node_staked_accounts = node_staked_accounts_at_epoch(bank, epoch_height);
|
|
|
|
node_staked_accounts.map(|epoch_state| epoch_state.map(|(id, stake, _)| (*id, stake)).collect())
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-04-16 13:03:01 -07:00
|
|
|
/// At the specified epoch, collect the delegate account balance and vote states for delegates
|
2019-03-03 18:04:13 -08:00
|
|
|
/// that have non-zero balance in any of their managed staking accounts
|
|
|
|
pub fn delegated_stakes_at_epoch(bank: &Bank, epoch_height: u64) -> Option<HashMap<Pubkey, u64>> {
|
|
|
|
let node_staked_accounts = node_staked_accounts_at_epoch(bank, epoch_height);
|
|
|
|
let node_staked_vote_states = node_staked_accounts.map(to_vote_state);
|
|
|
|
node_staked_vote_states.map(to_delegated_stakes)
|
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
/// Collect the node account balance and vote states for nodes have non-zero balance in
|
|
|
|
/// their corresponding staking accounts
|
|
|
|
fn node_staked_accounts(bank: &Bank) -> impl Iterator<Item = (Pubkey, u64, Account)> {
|
2019-04-05 14:23:00 -07:00
|
|
|
bank.vote_accounts()
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|(account_id, account)| {
|
|
|
|
filter_zero_balances(&account).map(|stake| (account_id, stake, account))
|
|
|
|
})
|
2019-03-03 18:04:13 -08:00
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-03-18 12:12:33 -07:00
|
|
|
pub fn node_staked_accounts_at_epoch(
|
2019-03-03 18:04:13 -08:00
|
|
|
bank: &Bank,
|
|
|
|
epoch_height: u64,
|
|
|
|
) -> Option<impl Iterator<Item = (&Pubkey, u64, &Account)>> {
|
|
|
|
bank.epoch_vote_accounts(epoch_height).map(|epoch_state| {
|
2019-03-11 17:58:21 -07:00
|
|
|
epoch_state
|
2019-05-09 19:27:06 -07:00
|
|
|
.iter()
|
2019-03-11 17:58:21 -07:00
|
|
|
.filter_map(|(account_id, account)| {
|
|
|
|
filter_zero_balances(account).map(|stake| (account_id, stake, account))
|
|
|
|
})
|
|
|
|
.filter(|(account_id, _, account)| filter_no_delegate(account_id, account))
|
2019-03-03 18:04:13 -08:00
|
|
|
})
|
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-03-11 17:58:21 -07:00
|
|
|
fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool {
|
2019-03-14 10:48:27 -06:00
|
|
|
VoteState::deserialize(&account.data)
|
2019-04-10 17:52:47 -07:00
|
|
|
.map(|vote_state| vote_state.node_id != *account_id)
|
2019-03-11 17:58:21 -07:00
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
fn filter_zero_balances(account: &Account) -> Option<u64> {
|
|
|
|
let balance = Bank::read_balance(&account);
|
|
|
|
if balance > 0 {
|
|
|
|
Some(balance)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
fn to_vote_state(
|
|
|
|
node_staked_accounts: impl Iterator<Item = (impl Borrow<Pubkey>, u64, impl Borrow<Account>)>,
|
|
|
|
) -> impl Iterator<Item = (u64, VoteState)> {
|
|
|
|
node_staked_accounts.filter_map(|(_, stake, account)| {
|
2019-03-14 10:48:27 -06:00
|
|
|
VoteState::deserialize(&account.borrow().data)
|
2019-03-03 18:04:13 -08:00
|
|
|
.ok()
|
|
|
|
.map(|vote_state| (stake, vote_state))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_delegated_stakes(
|
|
|
|
node_staked_accounts: impl Iterator<Item = (u64, VoteState)>,
|
|
|
|
) -> HashMap<Pubkey, u64> {
|
|
|
|
let mut map: HashMap<Pubkey, u64> = HashMap::new();
|
|
|
|
node_staked_accounts.for_each(|(stake, state)| {
|
2019-04-10 17:52:47 -07:00
|
|
|
let delegate = &state.node_id;
|
2019-03-03 18:04:13 -08:00
|
|
|
map.entry(*delegate)
|
|
|
|
.and_modify(|s| *s += stake)
|
|
|
|
.or_insert(stake);
|
2019-03-01 17:31:59 -08:00
|
|
|
});
|
|
|
|
map
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-01 17:31:59 -08:00
|
|
|
fn epoch_stakes_and_lockouts(bank: &Bank, epoch_height: u64) -> Vec<(u64, Option<u64>)> {
|
2019-03-03 18:04:13 -08:00
|
|
|
let node_staked_accounts =
|
|
|
|
node_staked_accounts_at_epoch(bank, epoch_height).expect("Bank state for epoch is missing");
|
|
|
|
let node_staked_vote_states = to_vote_state(node_staked_accounts);
|
|
|
|
node_staked_vote_states
|
|
|
|
.map(|(stake, states)| (stake, states.root_slot))
|
|
|
|
.collect()
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn find_supermajority_slot<'a, I>(supermajority_stake: u64, stakes_and_lockouts: I) -> Option<u64>
|
|
|
|
where
|
|
|
|
I: Iterator<Item = &'a (u64, Option<u64>)>,
|
|
|
|
{
|
|
|
|
// Filter out the states that don't have a max lockout
|
|
|
|
let mut stakes_and_lockouts: Vec<_> = stakes_and_lockouts
|
|
|
|
.filter_map(|(stake, slot)| slot.map(|s| (stake, s)))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
// Sort by the root slot, in descending order
|
|
|
|
stakes_and_lockouts.sort_unstable_by(|s1, s2| s1.1.cmp(&s2.1).reverse());
|
|
|
|
|
|
|
|
// Find if any slot has achieved sufficient votes for supermajority lockout
|
|
|
|
let mut total = 0;
|
|
|
|
for (stake, slot) in stakes_and_lockouts {
|
|
|
|
total += stake;
|
|
|
|
if total > supermajority_stake {
|
|
|
|
return Some(slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2019-05-07 11:16:22 -07:00
|
|
|
pub mod tests {
|
2019-02-28 13:15:25 -08:00
|
|
|
use super::*;
|
2019-05-07 11:16:22 -07:00
|
|
|
use crate::genesis_utils::{
|
|
|
|
create_genesis_block, create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS,
|
|
|
|
};
|
2019-02-28 13:15:25 -08:00
|
|
|
use crate::voting_keypair::tests as voting_keypair_tests;
|
2019-03-01 16:39:23 -08:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2019-02-28 13:15:25 -08:00
|
|
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
2019-05-09 19:27:06 -07:00
|
|
|
use std::collections::HashSet;
|
2019-02-28 13:15:25 -08:00
|
|
|
use std::iter::FromIterator;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank {
|
2019-03-09 19:28:43 -08:00
|
|
|
Bank::new_from_parent(parent, &Pubkey::default(), slot)
|
2019-03-01 16:39:23 -08:00
|
|
|
}
|
|
|
|
|
2019-02-28 13:15:25 -08:00
|
|
|
#[test]
|
|
|
|
fn test_bank_staked_nodes_at_epoch() {
|
2019-05-07 11:16:22 -07:00
|
|
|
let (genesis_block, _mint_keypair, voting_keypair) =
|
|
|
|
create_genesis_block_with_leader(1, &Pubkey::new_rand(), BOOTSTRAP_LEADER_LAMPORTS);
|
|
|
|
|
2019-02-28 13:15:25 -08:00
|
|
|
let bank = Bank::new(&genesis_block);
|
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
// Epoch doesn't exist
|
2019-02-28 13:15:25 -08:00
|
|
|
let mut expected = HashMap::new();
|
2019-03-03 18:04:13 -08:00
|
|
|
assert_eq!(vote_account_balances_at_epoch(&bank, 10), None);
|
|
|
|
|
|
|
|
// First epoch has the bootstrap leader
|
2019-05-07 11:16:22 -07:00
|
|
|
expected.insert(voting_keypair.pubkey(), BOOTSTRAP_LEADER_LAMPORTS);
|
2019-03-03 18:04:13 -08:00
|
|
|
let expected = Some(expected);
|
|
|
|
assert_eq!(vote_account_balances_at_epoch(&bank, 0), expected);
|
|
|
|
|
|
|
|
// Second epoch carries same information
|
|
|
|
let bank = new_from_parent(&Arc::new(bank), 1);
|
|
|
|
assert_eq!(vote_account_balances_at_epoch(&bank, 0), expected);
|
|
|
|
assert_eq!(vote_account_balances_at_epoch(&bank, 1), expected);
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_epoch_stakes_and_lockouts() {
|
|
|
|
let validator = Keypair::new();
|
|
|
|
|
2019-05-07 11:16:22 -07:00
|
|
|
let (genesis_block, mint_keypair) = create_genesis_block(500);
|
2019-03-06 14:44:21 -08:00
|
|
|
|
2019-02-28 13:15:25 -08:00
|
|
|
let bank = Bank::new(&genesis_block);
|
|
|
|
let bank_voter = Keypair::new();
|
|
|
|
|
2019-02-28 17:08:45 -08:00
|
|
|
// Give the validator some stake but don't setup a staking account
|
2019-03-05 16:58:52 -08:00
|
|
|
// Validator has no lamports staked, so they get filtered out. Only the bootstrap leader
|
2019-03-03 18:04:13 -08:00
|
|
|
// created by the genesis block will get included
|
2019-03-27 05:59:30 -06:00
|
|
|
bank.transfer(1, &mint_keypair, &validator.pubkey())
|
2019-03-01 09:55:13 -08:00
|
|
|
.unwrap();
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-03-03 18:04:13 -08:00
|
|
|
// Make a mint vote account. Because the mint has nonzero stake, this
|
|
|
|
// should show up in the active set
|
2019-04-10 17:52:47 -07:00
|
|
|
voting_keypair_tests::new_vote_account(
|
2019-03-11 17:58:21 -07:00
|
|
|
&mint_keypair,
|
|
|
|
&bank_voter,
|
|
|
|
&mint_keypair.pubkey(),
|
|
|
|
&bank,
|
|
|
|
499,
|
|
|
|
);
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-03-06 16:32:23 -08:00
|
|
|
// soonest slot that could be a new epoch is 1
|
|
|
|
let mut slot = 1;
|
|
|
|
let mut epoch = bank.get_stakers_epoch(0);
|
|
|
|
// find the first slot in the next stakers_epoch
|
|
|
|
while bank.get_stakers_epoch(slot) == epoch {
|
|
|
|
slot += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
epoch = bank.get_stakers_epoch(slot);
|
2019-03-03 18:04:13 -08:00
|
|
|
|
2019-03-06 16:32:23 -08:00
|
|
|
let bank = new_from_parent(&Arc::new(bank), slot);
|
2019-03-03 18:04:13 -08:00
|
|
|
|
|
|
|
let result: Vec<_> = epoch_stakes_and_lockouts(&bank, 0);
|
2019-05-07 11:16:22 -07:00
|
|
|
assert_eq!(result, vec![(BOOTSTRAP_LEADER_LAMPORTS, None)]);
|
2019-03-03 18:04:13 -08:00
|
|
|
|
|
|
|
let result: HashSet<_> = HashSet::from_iter(epoch_stakes_and_lockouts(&bank, epoch));
|
2019-05-07 11:16:22 -07:00
|
|
|
let expected: HashSet<_> =
|
|
|
|
HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (499, None)]);
|
2019-02-28 13:15:25 -08:00
|
|
|
assert_eq!(result, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_supermajority_slot() {
|
|
|
|
let supermajority = 10;
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(5, None), (5, None)];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(5, None), (5, None), (9, Some(2))];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(5, None), (5, None), (9, Some(2)), (1, Some(3))];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(5, None), (5, None), (9, Some(2)), (2, Some(3))];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
Some(2)
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(9, Some(2)), (2, Some(3)), (9, None)];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
Some(2)
|
|
|
|
);
|
|
|
|
|
|
|
|
let stakes_and_slots = vec![(9, Some(2)), (2, Some(3)), (9, Some(3))];
|
|
|
|
assert_eq!(
|
|
|
|
find_supermajority_slot(supermajority, stakes_and_slots.iter()),
|
|
|
|
Some(3)
|
|
|
|
);
|
|
|
|
}
|
2019-03-01 17:31:59 -08:00
|
|
|
|
|
|
|
#[test]
|
2019-03-03 18:04:13 -08:00
|
|
|
fn test_to_delegated_stakes() {
|
|
|
|
let mut stakes = Vec::new();
|
2019-03-30 21:37:33 -06:00
|
|
|
let delegate1 = Pubkey::new_rand();
|
|
|
|
let delegate2 = Pubkey::new_rand();
|
2019-03-03 18:04:13 -08:00
|
|
|
|
|
|
|
// Delegate 1 has stake of 3
|
|
|
|
for i in 0..3 {
|
2019-04-10 17:52:47 -07:00
|
|
|
stakes.push((i, VoteState::new(&Pubkey::new_rand(), &delegate1, 0)));
|
2019-03-03 18:04:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delegate 1 has stake of 5
|
2019-04-10 17:52:47 -07:00
|
|
|
stakes.push((5, VoteState::new(&Pubkey::new_rand(), &delegate2, 0)));
|
2019-03-03 18:04:13 -08:00
|
|
|
|
|
|
|
let result = to_delegated_stakes(stakes.into_iter());
|
|
|
|
assert_eq!(result.len(), 2);
|
|
|
|
assert_eq!(result[&delegate1], 3);
|
|
|
|
assert_eq!(result[&delegate2], 5);
|
2019-03-01 17:31:59 -08:00
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|