Distinguish max replayed and max observed vote (#16936)

This commit is contained in:
carllin 2021-04-29 14:43:28 -07:00 committed by GitHub
parent 94edd6140c
commit 5981399612
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 243 additions and 146 deletions

View File

@ -295,6 +295,7 @@ impl Tower {
key,
last_landed_voted_slot,
get_frozen_hash(last_landed_voted_slot),
true,
);
}

View File

@ -5,7 +5,8 @@ use std::collections::{hash_map::Entry, HashMap};
#[derive(Default)]
pub(crate) struct LatestValidatorVotesForFrozenBanks {
// TODO: Clean outdated/unstaked pubkeys from this list.
max_frozen_votes: HashMap<Pubkey, (Slot, Vec<Hash>)>,
max_gossip_frozen_votes: HashMap<Pubkey, (Slot, Vec<Hash>)>,
max_replay_frozen_votes: HashMap<Pubkey, (Slot, Vec<Hash>)>,
// Pubkeys that had their `max_frozen_votes` updated since the last
// fork choice update
fork_choice_dirty_set: HashMap<Pubkey, (Slot, Vec<Hash>)>,
@ -19,26 +20,42 @@ impl LatestValidatorVotesForFrozenBanks {
vote_pubkey: Pubkey,
vote_slot: Slot,
frozen_hash: Option<Hash>,
is_replay_vote: bool,
) -> (bool, Option<Slot>) {
let max_frozen_votes_entry = self.max_frozen_votes.entry(vote_pubkey);
let vote_map = if is_replay_vote {
&mut self.max_replay_frozen_votes
} else {
&mut self.max_gossip_frozen_votes
};
let pubkey_max_frozen_votes = vote_map.entry(vote_pubkey);
if let Some(frozen_hash) = frozen_hash {
match max_frozen_votes_entry {
match pubkey_max_frozen_votes {
Entry::Occupied(mut occupied_entry) => {
let (latest_frozen_vote_slot, latest_frozen_vote_hashes) =
occupied_entry.get_mut();
if vote_slot > *latest_frozen_vote_slot {
self.fork_choice_dirty_set
.insert(vote_pubkey, (vote_slot, vec![frozen_hash]));
if is_replay_vote {
// Only record votes detected through replaying blocks,
// because votes in gossip are not consistently observable
// if the validator is replacing them.
self.fork_choice_dirty_set
.insert(vote_pubkey, (vote_slot, vec![frozen_hash]));
}
*latest_frozen_vote_slot = vote_slot;
*latest_frozen_vote_hashes = vec![frozen_hash];
return (true, Some(vote_slot));
} else if vote_slot == *latest_frozen_vote_slot
&& !latest_frozen_vote_hashes.contains(&frozen_hash)
{
let (_, dirty_frozen_hashes) =
self.fork_choice_dirty_set.entry(vote_pubkey).or_default();
assert!(!dirty_frozen_hashes.contains(&frozen_hash));
dirty_frozen_hashes.push(frozen_hash);
if is_replay_vote {
// Only record votes detected through replaying blocks,
// because votes in gossip are not consistently observable
// if the validator is replacing them.
let (_, dirty_frozen_hashes) =
self.fork_choice_dirty_set.entry(vote_pubkey).or_default();
assert!(!dirty_frozen_hashes.contains(&frozen_hash));
dirty_frozen_hashes.push(frozen_hash);
}
latest_frozen_vote_hashes.push(frozen_hash);
return (true, Some(vote_slot));
} else {
@ -49,8 +66,10 @@ impl LatestValidatorVotesForFrozenBanks {
Entry::Vacant(vacant_entry) => {
vacant_entry.insert((vote_slot, vec![frozen_hash]));
self.fork_choice_dirty_set
.insert(vote_pubkey, (vote_slot, vec![frozen_hash]));
if is_replay_vote {
self.fork_choice_dirty_set
.insert(vote_pubkey, (vote_slot, vec![frozen_hash]));
}
return (true, Some(vote_slot));
}
}
@ -60,7 +79,7 @@ impl LatestValidatorVotesForFrozenBanks {
// struct
(
false,
match max_frozen_votes_entry {
match pubkey_max_frozen_votes {
Entry::Occupied(occupied_entry) => Some(occupied_entry.get().0),
Entry::Vacant(_) => None,
},
@ -80,14 +99,23 @@ impl LatestValidatorVotesForFrozenBanks {
})
.collect()
}
#[cfg(test)]
fn latest_vote(&self, pubkey: &Pubkey, is_replay_vote: bool) -> Option<&(Slot, Vec<Hash>)> {
let vote_map = if is_replay_vote {
&self.max_replay_frozen_votes
} else {
&self.max_gossip_frozen_votes
};
vote_map.get(pubkey)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_latest_validator_votes_for_frozen_banks_check_add_vote() {
fn run_test_latest_validator_votes_for_frozen_banks_check_add_vote(is_replay_vote: bool) {
let mut latest_validator_votes_for_frozen_banks =
LatestValidatorVotesForFrozenBanks::default();
@ -100,13 +128,17 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
// Non-frozen bank isn't inserted, so should return None for
// the highest voted frozen slot
(false, None)
);
assert!(latest_validator_votes_for_frozen_banks
.max_frozen_votes
.max_replay_frozen_votes
.is_empty());
assert!(latest_validator_votes_for_frozen_banks
.max_gossip_frozen_votes
.is_empty());
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
@ -127,23 +159,30 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
expected_result
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
.is_none());
}
}
// Case 3: Adding duplicate vote for same slot should update the state
@ -154,23 +193,30 @@ mod tests {
vote_pubkey,
vote_slot,
duplicate_frozen_hash,
is_replay_vote,
),
(true, Some(vote_slot))
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
.is_none());
}
// Case 4: Adding duplicate vote that is not frozen should not update the state
let frozen_hash = None;
@ -179,23 +225,30 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
(false, Some(vote_slot))
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, all_frozen_hashes.clone())
);
.is_none());
}
// Case 5: Adding a vote for a new higher slot that is not yet frozen
// should not update the state
@ -207,23 +260,30 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
(false, Some(old_vote_slot))
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(old_vote_slot, all_frozen_hashes.clone())
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(old_vote_slot, all_frozen_hashes)
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(old_vote_slot, all_frozen_hashes)
);
.is_none());
}
// Case 6: Adding a vote for a new higher slot that *is* frozen
// should upate the state
@ -233,23 +293,30 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
(true, Some(vote_slot))
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
.is_none());
}
// Case 7: Adding a vote for a new pubkey should also update the state
vote_slot += 1;
@ -260,27 +327,43 @@ mod tests {
vote_pubkey,
vote_slot,
frozen_hash,
is_replay_vote,
),
(true, Some(vote_slot))
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.max_frozen_votes
.get(&vote_pubkey)
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
if is_replay_vote {
assert_eq!(
*latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
} else {
assert!(latest_validator_votes_for_frozen_banks
.fork_choice_dirty_set
.get(&vote_pubkey)
.unwrap(),
(vote_slot, vec![frozen_hash.unwrap()])
);
.is_none());
}
}
#[test]
fn test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set() {
fn test_latest_validator_votes_for_frozen_banks_check_add_vote_is_replay() {
run_test_latest_validator_votes_for_frozen_banks_check_add_vote(true)
}
#[test]
fn test_latest_validator_votes_for_frozen_banks_check_add_vote_is_not_replay() {
run_test_latest_validator_votes_for_frozen_banks_check_add_vote(false)
}
fn run_test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set(is_replay: bool) {
let mut latest_validator_votes_for_frozen_banks =
LatestValidatorVotesForFrozenBanks::default();
let num_validators = 10;
@ -296,6 +379,7 @@ mod tests {
vote_pubkey,
vote_slot,
Some(frozen_hash1),
is_replay
),
// This vote slot was frozen, and is the highest slot inserted thus far,
// so the highest vote should be Some(vote_slot)
@ -308,15 +392,21 @@ mod tests {
vote_pubkey,
vote_slot,
Some(frozen_hash2),
is_replay
),
// This vote slot was frozen, and is for a duplicate version of the highest slot
// inserted thus far, so the highest vote should be Some(vote_slot).
(true, Some(vote_slot))
);
vec![
(vote_pubkey, (vote_slot, frozen_hash1)),
(vote_pubkey, (vote_slot, frozen_hash2)),
]
if is_replay {
// Only replayed vote should modify the dirty set, which is used for fork fork choice.
vec![
(vote_pubkey, (vote_slot, frozen_hash1)),
(vote_pubkey, (vote_slot, frozen_hash2)),
]
} else {
vec![]
}
})
.collect()
};
@ -334,11 +424,12 @@ mod tests {
.take_votes_dirty_set(0)
.is_empty());
// Taking all the firty votes >= num_validators - 1 will only return the last vote
// Taking all the dirty votes >= num_validators - 1 will only return the last vote
let root = num_validators - 1;
let dirty_set = setup_dirty_set(&mut latest_validator_votes_for_frozen_banks);
let mut expected_dirty_set: Vec<(Pubkey, SlotHashKey)> =
dirty_set[dirty_set.len() - 2..dirty_set.len()].to_vec();
// dirty_set could be empty if `is_replay == false`, so use saturating_sub
dirty_set[dirty_set.len().saturating_sub(2)..dirty_set.len()].to_vec();
let mut votes_dirty_set_output =
latest_validator_votes_for_frozen_banks.take_votes_dirty_set(root);
votes_dirty_set_output.sort();
@ -348,4 +439,79 @@ mod tests {
.take_votes_dirty_set(0)
.is_empty());
}
#[test]
fn test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set_is_replay() {
run_test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set(true)
}
#[test]
fn test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set_is_not_replay() {
run_test_latest_validator_votes_for_frozen_banks_take_votes_dirty_set(false)
}
#[test]
fn test_latest_validator_votes_for_frozen_banks_add_replay_and_gossip_vote() {
let mut latest_validator_votes_for_frozen_banks =
LatestValidatorVotesForFrozenBanks::default();
// First simulate vote from gossip
let vote_pubkey = Pubkey::new_unique();
let vote_slot = 1;
let frozen_hash = Hash::new_unique();
let mut is_replay_vote = false;
assert_eq!(
latest_validator_votes_for_frozen_banks.check_add_vote(
vote_pubkey,
vote_slot,
Some(frozen_hash),
is_replay_vote,
),
(true, Some(vote_slot))
);
// Should find the vote in the gossip votes
assert_eq!(
*latest_validator_votes_for_frozen_banks
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash])
);
// Shouldn't find the vote in the replayed votes
assert!(latest_validator_votes_for_frozen_banks
.latest_vote(&vote_pubkey, !is_replay_vote)
.is_none());
assert!(latest_validator_votes_for_frozen_banks
.take_votes_dirty_set(0)
.is_empty());
// Next simulate vote from replay
is_replay_vote = true;
assert_eq!(
latest_validator_votes_for_frozen_banks.check_add_vote(
vote_pubkey,
vote_slot,
Some(frozen_hash),
is_replay_vote,
),
(true, Some(vote_slot))
);
// Should find the vote in the gossip and replay votes
assert_eq!(
*latest_validator_votes_for_frozen_banks
.latest_vote(&vote_pubkey, is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash])
);
assert_eq!(
*latest_validator_votes_for_frozen_banks
.latest_vote(&vote_pubkey, !is_replay_vote)
.unwrap(),
(vote_slot, vec![frozen_hash])
);
assert_eq!(
latest_validator_votes_for_frozen_banks.take_votes_dirty_set(0),
vec![(vote_pubkey, (vote_slot, frozen_hash))]
);
}
}

View File

@ -420,12 +420,12 @@ impl ReplayStage {
// included in a block, so we may not have yet observed these votes just
// by replaying blocks.
let mut process_unfrozen_gossip_verified_vote_hashes_time = Measure::start("process_gossip_duplicate_confirmed_slots");
/*Self::process_gossip_verified_vote_hashes(
Self::process_gossip_verified_vote_hashes(
&gossip_verified_vote_hash_receiver,
&mut unfrozen_gossip_verified_vote_hashes,
&heaviest_subtree_fork_choice,
&mut latest_validator_votes_for_frozen_banks,
);*/
);
for _ in gossip_verified_vote_hash_receiver.try_iter() {}
process_unfrozen_gossip_verified_vote_hashes_time.stop();
@ -934,7 +934,6 @@ impl ReplayStage {
}
}
#[cfg(test)]
fn process_gossip_verified_vote_hashes(
gossip_verified_vote_hash_receiver: &GossipVerifiedVoteHashReceiver,
unfrozen_gossip_verified_vote_hashes: &mut UnfrozenGossipVerifiedVoteHashes,
@ -1765,6 +1764,7 @@ impl ReplayStage {
pubkey,
bank.slot(),
Some(bank_hash),
false,
);
}
}
@ -4700,12 +4700,11 @@ pub(crate) mod tests {
}
#[test]
fn test_gossip_vote_for_unrooted_slot() {
fn test_gossip_vote_doesnt_affect_fork_choice() {
let VoteSimulator {
bank_forks,
mut heaviest_subtree_fork_choice,
mut latest_validator_votes_for_frozen_banks,
mut progress,
vote_pubkeys,
..
} = setup_forks();
@ -4714,6 +4713,9 @@ pub(crate) mod tests {
let mut unfrozen_gossip_verified_vote_hashes = UnfrozenGossipVerifiedVoteHashes::default();
let (gossip_verified_vote_hash_sender, gossip_verified_vote_hash_receiver) = unbounded();
// Best slot is 4
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot().0, 4);
// Cast a vote for slot 3 on one fork
let vote_slot = 3;
let vote_bank = bank_forks.read().unwrap().get(vote_slot).unwrap().clone();
@ -4727,87 +4729,15 @@ pub(crate) mod tests {
&mut latest_validator_votes_for_frozen_banks,
);
// Pick the best fork
// Pick the best fork. Gossip votes shouldn't affect fork choice
heaviest_subtree_fork_choice.compute_bank_stats(
&vote_bank,
&Tower::default(),
&mut latest_validator_votes_for_frozen_banks,
);
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot().0, 6);
// Now send another vote for a frozen bank on the other fork, where the new vote
// is bigger than the last vote
let bigger_vote_slot = 4;
let bigger_vote_bank = bank_forks
.read()
.unwrap()
.get(bigger_vote_slot)
.unwrap()
.clone();
assert!(heaviest_subtree_fork_choice
.contains_block(&(bigger_vote_slot, bigger_vote_bank.hash())));
gossip_verified_vote_hash_sender
.send((vote_pubkey, bigger_vote_slot, bigger_vote_bank.hash()))
.expect("Send should succeed");
ReplayStage::process_gossip_verified_vote_hashes(
&gossip_verified_vote_hash_receiver,
&mut unfrozen_gossip_verified_vote_hashes,
&heaviest_subtree_fork_choice,
&mut latest_validator_votes_for_frozen_banks,
);
// Now set a root for a slot on the previously voted fork thats smaller than the new vote
let new_root = 3;
ReplayStage::handle_new_root(
new_root,
&bank_forks,
&mut progress,
&AbsRequestSender::default(),
None,
&mut heaviest_subtree_fork_choice,
&mut GossipDuplicateConfirmedSlots::default(),
&mut unfrozen_gossip_verified_vote_hashes,
&mut true,
&mut vec![],
);
// Add a new bank, freeze it
let parent_bank = bank_forks.read().unwrap().get(6).unwrap().clone();
let new_bank = Bank::new_from_parent(&parent_bank, &Pubkey::default(), 7);
bank_forks.write().unwrap().insert(new_bank);
let new_bank = bank_forks.read().unwrap().get(7).unwrap().clone();
new_bank.freeze();
heaviest_subtree_fork_choice.add_new_leaf_slot(
(new_bank.slot(), new_bank.hash()),
Some((parent_bank.slot(), parent_bank.hash())),
);
// Compute bank stats on new slot
heaviest_subtree_fork_choice.compute_bank_stats(
&new_bank,
&Tower::default(),
&mut latest_validator_votes_for_frozen_banks,
);
// Even though the `bigger_vote_slot` no longer exists in the fork choice tree,
// this vote should remove the previous vote's weight because we know there
// was a later vote
let old_vote_node = (vote_slot, vote_bank.hash());
assert_eq!(
heaviest_subtree_fork_choice
.stake_voted_at(&old_vote_node)
.unwrap(),
0
);
assert_eq!(
heaviest_subtree_fork_choice
.stake_voted_subtree(&old_vote_node)
.unwrap(),
0
);
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(new_bank.slot(), new_bank.hash())
);
// Best slot is still 4
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot().0, 4);
}
#[test]

View File

@ -21,8 +21,8 @@ impl UnfrozenGossipVerifiedVoteHashes {
) {
// If this is a frozen bank, then we need to update the `latest_validator_votes_for_frozen_banks`
let frozen_hash = if is_frozen { Some(hash) } else { None };
let (was_added, latest_frozen_vote_slot) =
latest_validator_votes_for_frozen_banks.check_add_vote(pubkey, vote_slot, frozen_hash);
let (was_added, latest_frozen_vote_slot) = latest_validator_votes_for_frozen_banks
.check_add_vote(pubkey, vote_slot, frozen_hash, false);
if !was_added
&& latest_frozen_vote_slot