Skip to content

Commit

Permalink
v1.14: Update is_locked_out cache when adopting on chain vote state (…
Browse files Browse the repository at this point in the history
…backport of #33341) (#33402)

* Update is_locked_out cache when adopting on chain vote state (#33341)

* Update is_locked_out cache when adopting on chain vote state

* extend to all cached tower checks

* upgrade error to panic

(cherry picked from commit 85cc6ac)

# Conflicts:
#	core/src/consensus.rs

* Fix conflicts

---------

Co-authored-by: Ashwin Sekar <[email protected]>
  • Loading branch information
mergify[bot] and AshwinSekar authored Sep 26, 2023
1 parent 695192c commit fcd8a70
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 32 deletions.
2 changes: 1 addition & 1 deletion core/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ impl Tower {
let vote = Vote::new(vec![vote_slot], vote_hash);
let result = self.vote_state.process_vote_unchecked(vote);
if result.is_err() {
error!(
panic!(
"Error while recording vote {} {} in local tower {:?}",
vote_slot, vote_hash, result
);
Expand Down
157 changes: 126 additions & 31 deletions core/src/replay_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2823,7 +2823,7 @@ impl ReplayStage {
pub fn compute_bank_stats(
my_vote_pubkey: &Pubkey,
ancestors: &HashMap<u64, HashSet<u64>>,
frozen_banks: &mut Vec<Arc<Bank>>,
frozen_banks: &mut [Arc<Bank>],
tower: &mut Tower,
progress: &mut ProgressMap,
vote_tracker: &VoteTracker,
Expand All @@ -2834,7 +2834,7 @@ impl ReplayStage {
) -> Vec<Slot> {
frozen_banks.sort_by_key(|bank| bank.slot());
let mut new_stats = vec![];
for bank in frozen_banks {
for bank in frozen_banks.iter() {
let bank_slot = bank.slot();
// Only time progress map should be missing a bank slot
// is if this node was the leader for this slot as those banks
Expand Down Expand Up @@ -2937,6 +2937,11 @@ impl ReplayStage {
.get_hash(last_voted_slot)
.expect("Must exist for us to have frozen descendant"),
);
// Since we are updating our tower we need to update associated caches for previously computed
// slots as well.
for slot in frozen_banks.iter().map(|b| b.slot()) {
Self::cache_tower_stats(progress, tower, slot, ancestors);
}
}
}
}
Expand Down Expand Up @@ -2997,24 +3002,33 @@ impl ReplayStage {
cluster_slots,
);

let stats = progress
.get_fork_stats_mut(bank_slot)
.expect("All frozen banks must exist in the Progress map");

stats.vote_threshold =
tower.check_vote_stake_threshold(bank_slot, &stats.voted_stakes, stats.total_stake);
stats.is_locked_out = tower.is_locked_out(
bank_slot,
ancestors
.get(&bank_slot)
.expect("Ancestors map should contain slot for is_locked_out() check"),
);
stats.has_voted = tower.has_voted(bank_slot);
stats.is_recent = tower.is_recent(bank_slot);
Self::cache_tower_stats(progress, tower, bank_slot, ancestors);
}
new_stats
}

fn cache_tower_stats(
progress: &mut ProgressMap,
tower: &Tower,
slot: Slot,
ancestors: &HashMap<u64, HashSet<u64>>,
) {
let stats = progress
.get_fork_stats_mut(slot)
.expect("All frozen banks must exist in the Progress map");

stats.vote_threshold =
tower.check_vote_stake_threshold(slot, &stats.voted_stakes, stats.total_stake);
stats.is_locked_out = tower.is_locked_out(
slot,
ancestors
.get(&slot)
.expect("Ancestors map should contain slot for is_locked_out() check"),
);
stats.has_voted = tower.has_voted(slot);
stats.is_recent = tower.is_recent(slot);
}

fn update_propagation_status(
progress: &mut ProgressMap,
slot: Slot,
Expand Down Expand Up @@ -5825,7 +5839,7 @@ pub(crate) mod tests {

// All forks have same weight so heaviest bank to vote/reset on should be the tip of
// the fork with the lower slot
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand All @@ -5844,7 +5858,7 @@ pub(crate) mod tests {

// 4 should be the heaviest slot, but should not be votable
// because of lockout. 5 is the heaviest slot on the same fork as the last vote.
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -5887,7 +5901,7 @@ pub(crate) mod tests {

// 4 should be the heaviest slot, but should not be votable
// because of lockout. 5 is no longer valid due to it being a duplicate.
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -5923,7 +5937,7 @@ pub(crate) mod tests {
// the right version of the block, so `duplicate_slots_to_repair`
// should be empty
assert!(duplicate_slots_to_repair.is_empty());
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -5975,7 +5989,7 @@ pub(crate) mod tests {

// All forks have same weight so heaviest bank to vote/reset on should be the tip of
// the fork with the lower slot
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -6022,7 +6036,7 @@ pub(crate) mod tests {
SlotStateUpdate::Duplicate(duplicate_state),
);

let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -6057,7 +6071,7 @@ pub(crate) mod tests {
SlotStateUpdate::Duplicate(duplicate_state),
);

let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -6097,7 +6111,7 @@ pub(crate) mod tests {
// the right version of the block, so `duplicate_slots_to_repair`
// should be empty
assert!(duplicate_slots_to_repair.is_empty());
let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, _) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand Down Expand Up @@ -7123,7 +7137,7 @@ pub(crate) mod tests {
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
latest_validator_votes_for_frozen_banks: &mut LatestValidatorVotesForFrozenBanks,
my_vote_pubkey: Option<Pubkey>,
) -> (Option<Slot>, Option<Slot>) {
) -> (Option<Slot>, Option<Slot>, Vec<HeaviestForkFailures>) {
let mut frozen_banks: Vec<_> = bank_forks
.read()
.unwrap()
Expand All @@ -7150,7 +7164,7 @@ pub(crate) mod tests {
let SelectVoteAndResetForkResult {
vote_bank,
reset_bank,
..
heaviest_fork_failures,
} = ReplayStage::select_vote_and_reset_forks(
&heaviest_bank,
heaviest_bank_on_same_fork.as_ref(),
Expand All @@ -7164,6 +7178,7 @@ pub(crate) mod tests {
(
vote_bank.map(|(b, _)| b.slot()),
reset_bank.map(|b| b.slot()),
heaviest_fork_failures,
)
}

Expand Down Expand Up @@ -7233,7 +7248,7 @@ pub(crate) mod tests {
}

#[test]
fn test_tower_sync_from_bank() {
fn test_tower_sync_from_bank_failed_switch() {
solana_logger::setup_with_default(
"error,solana_core::replay_stage=info,solana_core::consensus=info",
);
Expand All @@ -7252,9 +7267,10 @@ pub(crate) mod tests {
slot 6
We had some point voted 0 - 6, while the rest of the network voted 0 - 4.
We are sitting with an oudated tower that has voted until 1. We see that 2 is the heaviest slot,
We are sitting with an oudated tower that has voted until 1. We see that 4 is the heaviest slot,
however in the past we have voted up to 6. We must acknowledge the vote state present at 6,
adopt it as our own and *not* vote on 2 or 4, to respect slashing rules.
adopt it as our own and *not* vote on 2 or 4, to respect slashing rules as there is
not enough stake to switch
*/

let generate_votes = |pubkeys: Vec<Pubkey>| {
Expand All @@ -7273,7 +7289,7 @@ pub(crate) mod tests {
tower.record_vote(0, bank_hash(0));
tower.record_vote(1, bank_hash(1));

let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, failures) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand All @@ -7284,8 +7300,12 @@ pub(crate) mod tests {

assert_eq!(vote_fork, None);
assert_eq!(reset_fork, Some(6));
assert_eq!(
failures,
vec![HeaviestForkFailures::FailedSwitchThreshold(4)],
);

let (vote_fork, reset_fork) = run_compute_and_select_forks(
let (vote_fork, reset_fork, failures) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
Expand All @@ -7296,5 +7316,80 @@ pub(crate) mod tests {

assert_eq!(vote_fork, None);
assert_eq!(reset_fork, Some(6));
assert_eq!(
failures,
vec![HeaviestForkFailures::FailedSwitchThreshold(4)],
);
}

#[test]
fn test_tower_sync_from_bank_failed_lockout() {
solana_logger::setup_with_default(
"error,solana_core::replay_stage=info,solana_core::consensus=info",
);
/*
Fork structure:
slot 0
|
slot 1
/ \
slot 3 |
| slot 2
slot 4 |
slot 5
|
slot 6
We had some point voted 0 - 6, while the rest of the network voted 0 - 4.
We are sitting with an oudated tower that has voted until 1. We see that 4 is the heaviest slot,
however in the past we have voted up to 6. We must acknowledge the vote state present at 6,
adopt it as our own and *not* vote on 3 or 4, to respect slashing rules as we are locked
out on 4, even though there is enough stake to switch. However we should still reset onto
4.
*/

let generate_votes = |pubkeys: Vec<Pubkey>| {
pubkeys
.into_iter()
.zip(iter::once(vec![0, 1, 2, 5, 6]).chain(iter::repeat(vec![0, 1, 3, 4]).take(2)))
.collect()
};
let tree = tr(0) / (tr(1) / (tr(3) / (tr(4))) / (tr(2) / (tr(5) / (tr(6)))));
let (mut vote_simulator, _blockstore) =
setup_forks_from_tree(tree, 3, Some(Box::new(generate_votes)));
let (bank_forks, mut progress) = (vote_simulator.bank_forks, vote_simulator.progress);
let bank_hash = |slot| bank_forks.read().unwrap().bank_hash(slot).unwrap();
let my_vote_pubkey = vote_simulator.vote_pubkeys[0];
let mut tower = Tower::default();
tower.node_pubkey = vote_simulator.node_pubkeys[0];
tower.record_vote(0, bank_hash(0));
tower.record_vote(1, bank_hash(1));

let (vote_fork, reset_fork, failures) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
&mut vote_simulator.heaviest_subtree_fork_choice,
&mut vote_simulator.latest_validator_votes_for_frozen_banks,
Some(my_vote_pubkey),
);

assert_eq!(vote_fork, None);
assert_eq!(reset_fork, Some(4));
assert_eq!(failures, vec![HeaviestForkFailures::LockedOut(4),]);

let (vote_fork, reset_fork, failures) = run_compute_and_select_forks(
&bank_forks,
&mut progress,
&mut tower,
&mut vote_simulator.heaviest_subtree_fork_choice,
&mut vote_simulator.latest_validator_votes_for_frozen_banks,
Some(my_vote_pubkey),
);

assert_eq!(vote_fork, None);
assert_eq!(reset_fork, Some(4));
assert_eq!(failures, vec![HeaviestForkFailures::LockedOut(4),]);
}
}

0 comments on commit fcd8a70

Please sign in to comment.