2020-12-17 13:22:50 -08:00
|
|
|
use solana_runtime::bank::Bank;
|
2019-11-02 00:38:30 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
clock::{Epoch, Slot},
|
|
|
|
pubkey::Pubkey,
|
|
|
|
};
|
2020-12-17 13:22:50 -08: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
|
2019-11-02 00:38:30 -07:00
|
|
|
pub fn get_supermajority_slot(bank: &Bank, epoch: Epoch) -> Option<u64> {
|
2019-02-28 13:15:25 -08:00
|
|
|
// Find the amount of stake needed for supermajority
|
2019-11-02 00:38:30 -07:00
|
|
|
let stakes_and_lockouts = epoch_stakes_and_lockouts(bank, epoch);
|
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-05-14 13:35:14 -07:00
|
|
|
pub fn vote_account_stakes(bank: &Bank) -> HashMap<Pubkey, u64> {
|
2019-05-14 16:15:51 -07:00
|
|
|
bank.vote_accounts()
|
|
|
|
.into_iter()
|
2019-05-14 13:35:14 -07:00
|
|
|
.map(|(id, (stake, _))| (id, stake))
|
2019-03-03 18:04:13 -08:00
|
|
|
.collect()
|
2019-03-01 17:31:59 -08:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:38:30 -07:00
|
|
|
fn epoch_stakes_and_lockouts(bank: &Bank, epoch: Epoch) -> Vec<(u64, Option<u64>)> {
|
2020-11-30 09:18:33 -08:00
|
|
|
bank.epoch_vote_accounts(epoch)
|
2019-05-14 16:15:51 -07:00
|
|
|
.expect("Bank state for epoch is missing")
|
2020-11-30 09:18:33 -08:00
|
|
|
.iter()
|
|
|
|
.filter_map(|(_ /*vote pubkey*/, (stake, vote_account))| {
|
|
|
|
let root_slot = vote_account.vote_state().as_ref().ok()?.root_slot;
|
|
|
|
Some((*stake, root_slot))
|
|
|
|
})
|
2019-03-03 18:04:13 -08:00
|
|
|
.collect()
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:38:30 -07:00
|
|
|
fn find_supermajority_slot<'a, I>(supermajority_stake: u64, stakes_and_lockouts: I) -> Option<Slot>
|
2019-02-28 13:15:25 -08:00
|
|
|
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 {
|
2019-11-08 02:27:35 -08:00
|
|
|
total += *stake;
|
2019-02-28 13:15:25 -08:00
|
|
|
if total > supermajority_stake {
|
|
|
|
return Some(slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2019-05-16 08:23:31 -07:00
|
|
|
pub(crate) mod tests {
|
2019-02-28 13:15:25 -08:00
|
|
|
use super::*;
|
2019-11-08 20:56:57 -08:00
|
|
|
use crate::genesis_utils::{
|
2020-11-20 00:21:03 -08:00
|
|
|
bootstrap_validator_stake_lamports, create_genesis_config, GenesisConfigInfo,
|
2019-11-08 20:56:57 -08:00
|
|
|
};
|
2020-11-30 09:18:33 -08:00
|
|
|
use rand::Rng;
|
2020-12-17 13:22:50 -08:00
|
|
|
use solana_runtime::vote_account::{ArcVoteAccount, VoteAccounts};
|
2019-09-25 13:53:49 -07:00
|
|
|
use solana_sdk::{
|
2020-11-30 09:18:33 -08:00
|
|
|
account::{from_account, Account},
|
2019-12-30 19:57:53 -08:00
|
|
|
clock::Clock,
|
2019-09-25 13:53:49 -07:00
|
|
|
instruction::Instruction,
|
|
|
|
pubkey::Pubkey,
|
2020-02-20 13:28:55 -08:00
|
|
|
signature::{Keypair, Signer},
|
2020-02-20 12:13:23 -08:00
|
|
|
signers::Signers,
|
2020-10-28 22:01:07 -07:00
|
|
|
sysvar::stake_history::{self, StakeHistory},
|
2019-09-25 13:53:49 -07:00
|
|
|
transaction::Transaction,
|
|
|
|
};
|
2019-11-20 10:12:43 -08:00
|
|
|
use solana_stake_program::{
|
2019-09-26 13:29:29 -07:00
|
|
|
stake_instruction,
|
2019-12-14 04:38:24 -08:00
|
|
|
stake_state::{Authorized, Delegation, Lockup, Stake},
|
2019-09-26 13:29:29 -07:00
|
|
|
};
|
2020-11-30 09:18:33 -08:00
|
|
|
use solana_vote_program::{
|
|
|
|
vote_instruction,
|
|
|
|
vote_state::{VoteInit, VoteState, VoteStateVersions},
|
|
|
|
};
|
2019-02-28 13:15:25 -08:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2019-11-02 00:38:30 -07:00
|
|
|
fn new_from_parent(parent: &Arc<Bank>, slot: Slot) -> 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-05-16 08:23:31 -07:00
|
|
|
pub(crate) fn setup_vote_and_stake_accounts(
|
|
|
|
bank: &Bank,
|
|
|
|
from_account: &Keypair,
|
2019-11-08 02:27:35 -08:00
|
|
|
vote_account: &Keypair,
|
2020-03-19 01:58:52 -07:00
|
|
|
validator_identity_account: &Keypair,
|
2019-05-16 08:23:31 -07:00
|
|
|
amount: u64,
|
|
|
|
) {
|
2019-11-08 02:27:35 -08:00
|
|
|
let vote_pubkey = vote_account.pubkey();
|
2020-04-24 12:03:46 -07:00
|
|
|
fn process_instructions<T: Signers>(bank: &Bank, keypairs: &T, ixs: &[Instruction]) {
|
2020-02-20 12:13:23 -08:00
|
|
|
let tx = Transaction::new_signed_with_payer(
|
2019-05-16 08:23:31 -07:00
|
|
|
ixs,
|
2020-02-20 12:13:23 -08:00
|
|
|
Some(&keypairs.pubkeys()[0]),
|
2019-06-03 09:04:51 -07:00
|
|
|
keypairs,
|
2019-05-16 08:23:31 -07:00
|
|
|
bank.last_blockhash(),
|
2020-02-20 12:13:23 -08:00
|
|
|
);
|
|
|
|
bank.process_transaction(&tx).unwrap();
|
2019-05-16 08:23:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
process_instructions(
|
|
|
|
bank,
|
2020-03-19 01:58:52 -07:00
|
|
|
&[from_account, vote_account, validator_identity_account],
|
2020-04-24 12:03:46 -07:00
|
|
|
&vote_instruction::create_account(
|
2019-05-23 23:20:04 -07:00
|
|
|
&from_account.pubkey(),
|
2019-11-08 02:27:35 -08:00
|
|
|
&vote_pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
&VoteInit {
|
2020-03-19 01:58:52 -07:00
|
|
|
node_pubkey: validator_identity_account.pubkey(),
|
2019-11-08 02:27:35 -08:00
|
|
|
authorized_voter: vote_pubkey,
|
|
|
|
authorized_withdrawer: vote_pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
commission: 0,
|
|
|
|
},
|
2019-05-23 23:20:04 -07:00
|
|
|
amount,
|
|
|
|
),
|
2019-05-16 08:23:31 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
let stake_account_keypair = Keypair::new();
|
|
|
|
let stake_account_pubkey = stake_account_keypair.pubkey();
|
|
|
|
|
|
|
|
process_instructions(
|
|
|
|
bank,
|
2019-06-10 12:17:29 -07:00
|
|
|
&[from_account, &stake_account_keypair],
|
2020-04-24 12:03:46 -07:00
|
|
|
&stake_instruction::create_account_and_delegate_stake(
|
2019-05-16 08:23:31 -07:00
|
|
|
&from_account.pubkey(),
|
|
|
|
&stake_account_pubkey,
|
2019-11-08 02:27:35 -08:00
|
|
|
&vote_pubkey,
|
2019-09-26 13:29:29 -07:00
|
|
|
&Authorized::auto(&stake_account_pubkey),
|
2019-12-14 04:38:24 -08:00
|
|
|
&Lockup::default(),
|
2019-09-29 21:18:15 -07:00
|
|
|
amount,
|
2019-05-21 07:32:38 -07:00
|
|
|
),
|
2019-05-16 08:23:31 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-28 13:15:25 -08:00
|
|
|
#[test]
|
|
|
|
fn test_epoch_stakes_and_lockouts() {
|
2019-11-20 10:12:43 -08:00
|
|
|
solana_logger::setup();
|
2020-11-20 00:21:03 -08:00
|
|
|
let stake = bootstrap_validator_stake_lamports();
|
2019-06-19 11:54:52 -07:00
|
|
|
let leader_stake = Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
2020-11-20 00:21:03 -08:00
|
|
|
stake: bootstrap_validator_stake_lamports(),
|
2019-11-25 13:14:32 -08:00
|
|
|
activation_epoch: std::u64::MAX, // mark as bootstrap
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
2019-06-19 11:54:52 -07:00
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
|
2019-02-28 13:15:25 -08:00
|
|
|
let validator = Keypair::new();
|
|
|
|
|
2019-11-08 20:56:57 -08:00
|
|
|
let GenesisConfigInfo {
|
|
|
|
genesis_config,
|
2019-05-22 20:39:00 -07:00
|
|
|
mint_keypair,
|
|
|
|
..
|
2020-11-20 00:21:03 -08:00
|
|
|
} = create_genesis_config(10_000 * bootstrap_validator_stake_lamports());
|
2019-03-06 14:44:21 -08:00
|
|
|
|
2019-11-08 20:56:57 -08:00
|
|
|
let bank = Bank::new(&genesis_config);
|
2019-11-08 02:27:35 -08:00
|
|
|
let vote_account = Keypair::new();
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-02-28 17:08:45 -08:00
|
|
|
// Give the validator some stake but don't setup a staking account
|
2020-01-22 08:22:09 -08:00
|
|
|
// Validator has no lamports staked, so they get filtered out. Only the bootstrap validator
|
2019-11-08 20:56:57 -08:00
|
|
|
// created by the genesis config will get included
|
2019-03-27 04:59:30 -07: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
|
2020-03-19 01:58:52 -07:00
|
|
|
setup_vote_and_stake_accounts(&bank, &mint_keypair, &vote_account, &mint_keypair, stake);
|
2019-02-28 13:15:25 -08:00
|
|
|
|
2019-08-01 14:27:47 -07:00
|
|
|
// simulated stake
|
2019-06-19 11:54:52 -07:00
|
|
|
let other_stake = Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
|
|
|
stake,
|
|
|
|
activation_epoch: bank.epoch(),
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
2019-06-19 11:54:52 -07:00
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
|
2019-10-08 22:34:26 -07:00
|
|
|
let first_leader_schedule_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
|
|
|
// find the first slot in the next leader schedule epoch
|
2019-08-12 20:59:57 -07:00
|
|
|
let mut slot = bank.slot();
|
|
|
|
loop {
|
2019-08-01 14:27:47 -07:00
|
|
|
slot += 1;
|
2019-10-08 22:34:26 -07:00
|
|
|
if bank.get_leader_schedule_epoch(slot) != first_leader_schedule_epoch {
|
2019-08-12 20:59:57 -07:00
|
|
|
break;
|
|
|
|
}
|
2019-03-06 16:32:23 -08:00
|
|
|
}
|
2019-08-01 14:27:47 -07:00
|
|
|
let bank = new_from_parent(&Arc::new(bank), slot);
|
2019-10-08 22:34:26 -07:00
|
|
|
let next_leader_schedule_epoch = bank.get_leader_schedule_epoch(slot);
|
2019-03-03 18:04:13 -08:00
|
|
|
|
2019-10-08 22:34:26 -07:00
|
|
|
let result: Vec<_> = epoch_stakes_and_lockouts(&bank, first_leader_schedule_epoch);
|
2019-08-12 20:59:57 -07:00
|
|
|
assert_eq!(
|
|
|
|
result,
|
2020-11-11 13:11:57 -08:00
|
|
|
vec![(
|
|
|
|
leader_stake.stake(first_leader_schedule_epoch, None, true),
|
|
|
|
None
|
|
|
|
)]
|
2019-08-12 20:59:57 -07:00
|
|
|
);
|
2019-06-19 11:54:52 -07:00
|
|
|
|
2019-08-01 14:27:47 -07:00
|
|
|
// epoch stakes and lockouts are saved off for the future epoch, should
|
|
|
|
// match current bank state
|
2019-10-08 22:34:26 -07:00
|
|
|
let mut result: Vec<_> = epoch_stakes_and_lockouts(&bank, next_leader_schedule_epoch);
|
2019-06-19 11:54:52 -07:00
|
|
|
result.sort();
|
2019-08-12 20:59:57 -07:00
|
|
|
let stake_history =
|
2020-10-28 22:01:07 -07:00
|
|
|
from_account::<StakeHistory>(&bank.get_account(&stake_history::id()).unwrap()).unwrap();
|
2019-06-19 11:54:52 -07:00
|
|
|
let mut expected = vec![
|
2020-11-11 13:11:57 -08:00
|
|
|
(
|
|
|
|
leader_stake.stake(bank.epoch(), Some(&stake_history), true),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
other_stake.stake(bank.epoch(), Some(&stake_history), true),
|
|
|
|
None,
|
|
|
|
),
|
2019-06-19 11:54:52 -07:00
|
|
|
];
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-06-19 11:54:52 -07:00
|
|
|
expected.sort();
|
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-05-14 16:15:51 -07:00
|
|
|
fn test_to_staked_nodes() {
|
2019-03-03 18:04:13 -08:00
|
|
|
let mut stakes = Vec::new();
|
2020-10-19 12:12:08 -07:00
|
|
|
let node1 = solana_sdk::pubkey::new_rand();
|
2019-03-03 18:04:13 -08:00
|
|
|
|
2019-05-14 16:15:51 -07:00
|
|
|
// Node 1 has stake of 3
|
2019-03-03 18:04:13 -08:00
|
|
|
for i in 0..3 {
|
2019-09-25 13:53:49 -07:00
|
|
|
stakes.push((
|
|
|
|
i,
|
2019-12-30 19:57:53 -08:00
|
|
|
VoteState::new(
|
|
|
|
&VoteInit {
|
|
|
|
node_pubkey: node1,
|
|
|
|
..VoteInit::default()
|
|
|
|
},
|
|
|
|
&Clock::default(),
|
|
|
|
),
|
2019-09-25 13:53:49 -07:00
|
|
|
));
|
2019-03-03 18:04:13 -08:00
|
|
|
}
|
|
|
|
|
2019-05-14 16:15:51 -07:00
|
|
|
// Node 1 has stake of 5
|
2020-10-19 12:12:08 -07:00
|
|
|
let node2 = solana_sdk::pubkey::new_rand();
|
2019-09-25 13:53:49 -07:00
|
|
|
|
|
|
|
stakes.push((
|
|
|
|
5,
|
2019-12-30 19:57:53 -08:00
|
|
|
VoteState::new(
|
|
|
|
&VoteInit {
|
|
|
|
node_pubkey: node2,
|
|
|
|
..VoteInit::default()
|
|
|
|
},
|
|
|
|
&Clock::default(),
|
|
|
|
),
|
2019-09-25 13:53:49 -07:00
|
|
|
));
|
2020-11-30 09:18:33 -08:00
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
let vote_accounts = stakes.into_iter().map(|(stake, vote_state)| {
|
|
|
|
let account = Account::new_data(
|
|
|
|
rng.gen(), // lamports
|
|
|
|
&VoteStateVersions::Current(Box::new(vote_state)),
|
|
|
|
&Pubkey::new_unique(), // owner
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let vote_pubkey = Pubkey::new_unique();
|
|
|
|
(vote_pubkey, (stake, ArcVoteAccount::from(account)))
|
|
|
|
});
|
2020-12-17 13:22:50 -08:00
|
|
|
let result = vote_accounts.collect::<VoteAccounts>().staked_nodes();
|
2019-03-03 18:04:13 -08:00
|
|
|
assert_eq!(result.len(), 2);
|
2019-05-14 16:15:51 -07:00
|
|
|
assert_eq!(result[&node1], 3);
|
|
|
|
assert_eq!(result[&node2], 5);
|
2019-03-01 17:31:59 -08:00
|
|
|
}
|
2019-02-28 13:15:25 -08:00
|
|
|
}
|