Refactor Vote Program Account setup (#2992)

This commit is contained in:
Sagar Dhawan
2019-02-28 17:08:45 -08:00
committed by GitHub
parent d5f0e49535
commit 20e4edec61
19 changed files with 278 additions and 151 deletions

View File

@@ -190,7 +190,7 @@ impl BroadcastService {
let mut broadcast_table = cluster_info
.read()
.unwrap()
.sorted_tvu_peers(&staking_utils::staked_nodes(&bank));
.sorted_tvu_peers(&staking_utils::node_stakes(&bank));
// Layer 1, leader nodes are limited to the fanout size.
broadcast_table.truncate(DATA_PLANE_FANOUT);
inc_new_counter_info!("broadcast_service-num_peers", broadcast_table.len() + 1);

View File

@@ -878,7 +878,7 @@ impl ClusterInfo {
let start = timestamp();
let stakes: HashMap<_, _> = match bank_forks {
Some(ref bank_forks) => {
staking_utils::staked_nodes(&bank_forks.read().unwrap().working_bank())
staking_utils::node_stakes(&bank_forks.read().unwrap().working_bank())
}
None => HashMap::new(),
};

View File

@@ -462,7 +462,7 @@ pub fn make_active_set_entries(
let vote_account_id = voting_keypair.pubkey();
let new_vote_account_tx =
VoteTransaction::new_account(active_keypair, vote_account_id, *last_id, 1, 1);
VoteTransaction::fund_staking_account(active_keypair, vote_account_id, *last_id, 1, 1);
let new_vote_account_entry = next_entry_mut(&mut last_entry_id, 1, vec![new_vote_account_tx]);
// 3) Create vote entry

View File

@@ -35,12 +35,12 @@ impl LeaderConfirmationService {
// Hold an accounts_db read lock as briefly as possible, just long enough to collect all
// the vote states
let vote_states = bank.vote_states(|vote_state| leader_id != vote_state.node_id);
let vote_states = bank.vote_states(|_, vote_state| leader_id != vote_state.delegate_id);
let mut ticks_and_stakes: Vec<(u64, u64)> = vote_states
.iter()
.filter_map(|vote_state| {
let validator_stake = bank.get_balance(&vote_state.node_id);
.filter_map(|(_, vote_state)| {
let validator_stake = bank.get_balance(&vote_state.delegate_id);
total_stake += validator_stake;
// Filter out any validators that don't have at least one vote
// by returning None

View File

@@ -5,13 +5,30 @@ use solana_sdk::pubkey::Pubkey;
/// Return the leader schedule for the given epoch.
fn leader_schedule(epoch_height: u64, bank: &Bank) -> LeaderSchedule {
let stakes = staking_utils::staked_nodes_at_epoch(bank, epoch_height);
let stakes = staking_utils::node_stakes_at_epoch(bank, epoch_height);
let mut seed = [0u8; 32];
seed[0..8].copy_from_slice(&epoch_height.to_le_bytes());
let stakes: Vec<_> = stakes.into_iter().collect();
let mut stakes: Vec<_> = stakes.into_iter().collect();
sort_stakes(&mut stakes);
LeaderSchedule::new(&stakes, seed, bank.slots_per_epoch())
}
fn sort_stakes(stakes: &mut Vec<(Pubkey, u64)>) {
// Sort first by stake. If stakes are the same, sort by pubkey to ensure a
// deterministic result.
// Note: Use unstable sort, because we dedup right after to remove the equal elements.
stakes.sort_unstable_by(|(l_id, l_stake), (r_id, r_stake)| {
if r_stake == l_stake {
r_id.cmp(&l_id)
} else {
r_stake.cmp(&l_stake)
}
});
// Now that it's sorted, we can do an O(n) dedup.
stakes.dedup();
}
/// Return the leader for the slot at the slot_index and epoch_height returned
/// by the given function.
fn slot_leader_by<F>(bank: &Bank, get_slot_index: F) -> Pubkey
@@ -92,7 +109,7 @@ mod tests {
let (genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(2, pubkey, 2);
let bank = Bank::new(&genesis_block);
let ids_and_stakes: Vec<_> = staking_utils::staked_nodes(&bank).into_iter().collect();
let ids_and_stakes: Vec<_> = staking_utils::node_stakes(&bank).into_iter().collect();
let seed = [0u8; 32];
let leader_schedule =
LeaderSchedule::new(&ids_and_stakes, seed, genesis_block.slots_per_epoch);
@@ -122,4 +139,31 @@ mod tests {
assert_eq!(next_slot_leader_index(0, 0, 2), (1, 0));
assert_eq!(next_slot_leader_index(1, 0, 2), (0, 1));
}
#[test]
fn test_sort_stakes_basic() {
let pubkey0 = Keypair::new().pubkey();
let pubkey1 = Keypair::new().pubkey();
let mut stakes = vec![(pubkey0, 1), (pubkey1, 2)];
sort_stakes(&mut stakes);
assert_eq!(stakes, vec![(pubkey1, 2), (pubkey0, 1)]);
}
#[test]
fn test_sort_stakes_with_dup() {
let pubkey0 = Keypair::new().pubkey();
let pubkey1 = Keypair::new().pubkey();
let mut stakes = vec![(pubkey0, 1), (pubkey1, 2), (pubkey0, 1)];
sort_stakes(&mut stakes);
assert_eq!(stakes, vec![(pubkey1, 2), (pubkey0, 1)]);
}
#[test]
fn test_sort_stakes_with_equal_stakes() {
let pubkey0 = Pubkey::default();
let pubkey1 = Keypair::new().pubkey();
let mut stakes = vec![(pubkey0, 1), (pubkey1, 1)];
sort_stakes(&mut stakes);
assert_eq!(stakes, vec![(pubkey1, 1), (pubkey0, 1)]);
}
}

View File

@@ -40,7 +40,7 @@ fn retransmit(
.to_owned(),
);
let (neighbors, children) = compute_retransmit_peers(
&staking_utils::staked_nodes(&bank_forks.read().unwrap().working_bank()),
&staking_utils::node_stakes(&bank_forks.read().unwrap().working_bank()),
cluster_info,
DATA_PLANE_FANOUT,
NEIGHBORHOOD_SIZE,

View File

@@ -15,18 +15,20 @@ pub fn get_supermajority_slot(bank: &Bank, epoch_height: u64) -> Option<u64> {
find_supermajority_slot(supermajority_stake, stakes_and_lockouts.values())
}
pub fn staked_nodes(bank: &Bank) -> HashMap<Pubkey, u64> {
staked_nodes_extractor(bank, |stake, _| stake)
/// Collect the node Pubkey and staker account balance for nodes
/// that have non-zero balance in their corresponding staking accounts
pub fn node_stakes(bank: &Bank) -> HashMap<Pubkey, u64> {
node_stakes_extractor(bank, |stake, _| stake)
}
/// Return the checkpointed stakes that should be used to generate a leader schedule.
pub fn staked_nodes_at_epoch(bank: &Bank, epoch_height: u64) -> HashMap<Pubkey, u64> {
staked_nodes_at_epoch_extractor(bank, epoch_height, |stake, _| stake)
pub fn node_stakes_at_epoch(bank: &Bank, epoch_height: u64) -> HashMap<Pubkey, u64> {
node_stakes_at_epoch_extractor(bank, epoch_height, |stake, _| stake)
}
/// Return the checkpointed stakes that should be used to generate a leader schedule.
/// state_extractor takes (stake, vote_state) and maps to an output.
fn staked_nodes_at_epoch_extractor<F, T>(
fn node_stakes_at_epoch_extractor<F, T>(
bank: &Bank,
epoch_height: u64,
state_extractor: F,
@@ -35,12 +37,12 @@ where
F: Fn(u64, &VoteState) -> T,
{
let epoch_slot_height = epoch_height * bank.slots_per_epoch();
staked_nodes_at_slot_extractor(bank, epoch_slot_height, state_extractor)
node_stakes_at_slot_extractor(bank, epoch_slot_height, state_extractor)
}
/// Return the checkpointed stakes that should be used to generate a leader schedule.
/// state_extractor takes (stake, vote_state) and maps to an output
fn staked_nodes_at_slot_extractor<F, T>(
fn node_stakes_at_slot_extractor<F, T>(
bank: &Bank,
current_slot_height: u64,
state_extractor: F,
@@ -59,25 +61,23 @@ where
.find(|bank| bank.id() <= slot_height)
.unwrap_or_else(|| banks.last().unwrap());
staked_nodes_extractor(bank, state_extractor)
node_stakes_extractor(bank, state_extractor)
}
/// Collect the node Pubkey and staker account balance for nodes
/// that have non-zero balance in their corresponding staker accounts.
/// state_extractor takes (stake, vote_state) and maps to an output
fn staked_nodes_extractor<F, T>(bank: &Bank, state_extractor: F) -> HashMap<Pubkey, T>
fn node_stakes_extractor<F, T>(bank: &Bank, state_extractor: F) -> HashMap<Pubkey, T>
where
F: Fn(u64, &VoteState) -> T,
{
bank.vote_states(|_| true)
bank.vote_states(|account_id, _| bank.get_balance(&account_id) > 0)
.iter()
.filter_map(|state| {
let balance = bank.get_balance(&state.staker_id);
if balance > 0 {
Some((state.node_id, state_extractor(balance, &state)))
} else {
None
}
.map(|(account_id, state)| {
(
state.delegate_id,
state_extractor(bank.get_balance(&account_id), &state),
)
})
.collect()
}
@@ -86,7 +86,7 @@ fn epoch_stakes_and_lockouts(
bank: &Bank,
epoch_height: u64,
) -> HashMap<Pubkey, (u64, Option<u64>)> {
staked_nodes_at_epoch_extractor(bank, epoch_height, |stake, state| (stake, state.root_slot))
node_stakes_at_epoch_extractor(bank, epoch_height, |stake, state| (stake, state.root_slot))
}
fn find_supermajority_slot<'a, I>(supermajority_stake: u64, stakes_and_lockouts: I) -> Option<u64>
@@ -146,7 +146,7 @@ mod tests {
expected.insert(pubkey, bootstrap_tokens - 1);
let bank = Bank::new_from_parent(&Arc::new(bank));
assert_eq!(
staked_nodes_at_slot_extractor(&bank, bank.slot_height(), |s, _| s),
node_stakes_at_slot_extractor(&bank, bank.slot_height(), |s, _| s),
expected
);
}
@@ -154,13 +154,12 @@ mod tests {
#[test]
fn test_epoch_stakes_and_lockouts() {
let validator = Keypair::new();
let voter = Keypair::new();
let (genesis_block, mint_keypair) = GenesisBlock::new(500);
let bank = Bank::new(&genesis_block);
let bank_voter = Keypair::new();
// Give the validator some stake
// Give the validator some stake but don't setup a staking account
bank.transfer(
1,
&mint_keypair,
@@ -169,9 +168,7 @@ mod tests {
)
.unwrap();
voting_keypair_tests::new_vote_account_with_vote(&validator, &voter, &bank, 1, 0);
assert_eq!(bank.get_balance(&validator.pubkey()), 0);
// Validator has zero balance, so they get filtered out. Only the bootstrap leader
// Validator has no token staked, so they get filtered out. Only the bootstrap leader
// created by the genesis block will get included
let expected: Vec<_> = epoch_stakes_and_lockouts(&bank, 0)
.values()
@@ -179,11 +176,11 @@ mod tests {
.collect();
assert_eq!(expected, vec![(1, None)]);
voting_keypair_tests::new_vote_account_with_vote(&mint_keypair, &bank_voter, &bank, 1, 0);
voting_keypair_tests::new_vote_account_with_vote(&mint_keypair, &bank_voter, &bank, 499, 0);
let result: HashSet<_> =
HashSet::from_iter(epoch_stakes_and_lockouts(&bank, 0).values().cloned());
let expected: HashSet<_> = HashSet::from_iter(vec![(1, None), (498, None)]);
let expected: HashSet<_> = HashSet::from_iter(vec![(1, None), (499, None)]);
assert_eq!(result, expected);
}

View File

@@ -590,8 +590,13 @@ mod tests {
let vote_account_id = validator_vote_account_keypair.pubkey();
let last_id = client.get_last_id();
let transaction =
VoteTransaction::new_account(&validator_keypair, vote_account_id, last_id, 1, 1);
let transaction = VoteTransaction::fund_staking_account(
&validator_keypair,
vote_account_id,
last_id,
1,
1,
);
let signature = client.transfer_signed(&transaction).unwrap();
client.poll_for_signature(&signature).unwrap();
@@ -608,7 +613,8 @@ mod tests {
let vote_state = VoteState::deserialize(&account_user_data);
if vote_state.map(|vote_state| vote_state.node_id) == Ok(validator_keypair.pubkey()) {
if vote_state.map(|vote_state| vote_state.delegate_id) == Ok(validator_keypair.pubkey())
{
break;
}

View File

@@ -110,7 +110,13 @@ pub mod tests {
num_tokens: u64,
) {
let last_id = bank.last_id();
let tx = VoteTransaction::new_account(from_keypair, *voting_pubkey, last_id, num_tokens, 0);
let tx = VoteTransaction::fund_staking_account(
from_keypair,
*voting_pubkey,
last_id,
num_tokens,
0,
);
bank.process_transaction(&tx).unwrap();
}