add additional vote lockout stake threshold (#34120)
* add additional vote lockout stake threshold
This commit is contained in:
parent
39a3566438
commit
07f38838ae
|
@ -52,7 +52,7 @@ use {
|
|||
pub enum ThresholdDecision {
|
||||
#[default]
|
||||
PassedThreshold,
|
||||
FailedThreshold(/* Observed stake */ u64),
|
||||
FailedThreshold(/* vote depth */ u64, /* Observed stake */ u64),
|
||||
}
|
||||
|
||||
impl ThresholdDecision {
|
||||
|
@ -141,6 +141,7 @@ impl SwitchForkDecision {
|
|||
}
|
||||
}
|
||||
|
||||
const VOTE_THRESHOLD_DEPTH_SHALLOW: usize = 4;
|
||||
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
||||
pub const SWITCH_FORK_THRESHOLD: f64 = 0.38;
|
||||
|
||||
|
@ -1042,46 +1043,88 @@ impl Tower {
|
|||
self.last_switch_threshold_check.is_none()
|
||||
}
|
||||
|
||||
/// Performs threshold check for `slot`
|
||||
///
|
||||
/// If it passes the check returns None, otherwise returns Some(fork_stake)
|
||||
pub fn check_vote_stake_threshold(
|
||||
/// Checks a single vote threshold for `slot`
|
||||
fn check_vote_stake_threshold(
|
||||
threshold_vote: Option<&Lockout>,
|
||||
vote_state_before_applying_vote: &VoteState,
|
||||
threshold_depth: usize,
|
||||
threshold_size: f64,
|
||||
slot: Slot,
|
||||
voted_stakes: &HashMap<Slot, u64>,
|
||||
total_stake: u64,
|
||||
) -> ThresholdDecision {
|
||||
let Some(threshold_vote) = threshold_vote else {
|
||||
// Tower isn't that deep.
|
||||
return ThresholdDecision::PassedThreshold;
|
||||
};
|
||||
let Some(fork_stake) = voted_stakes.get(&threshold_vote.slot()) else {
|
||||
// We haven't seen any votes on this fork yet, so no stake
|
||||
return ThresholdDecision::FailedThreshold(threshold_depth as u64, 0);
|
||||
};
|
||||
|
||||
let lockout = *fork_stake as f64 / total_stake as f64;
|
||||
trace!(
|
||||
"fork_stake slot: {}, threshold_vote slot: {}, lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot,
|
||||
threshold_vote.slot(),
|
||||
lockout,
|
||||
fork_stake,
|
||||
total_stake
|
||||
);
|
||||
if threshold_vote.confirmation_count() as usize > threshold_depth {
|
||||
for old_vote in &vote_state_before_applying_vote.votes {
|
||||
if old_vote.slot() == threshold_vote.slot()
|
||||
&& old_vote.confirmation_count() == threshold_vote.confirmation_count()
|
||||
{
|
||||
// If you bounce back to voting on the main fork after not
|
||||
// voting for a while, your latest vote N on the main fork
|
||||
// might pop off a lot of the stake of votes in the tower.
|
||||
// This stake would have rolled up to earlier votes in the
|
||||
// tower, so skip the stake check.
|
||||
return ThresholdDecision::PassedThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
if lockout > threshold_size {
|
||||
return ThresholdDecision::PassedThreshold;
|
||||
}
|
||||
ThresholdDecision::FailedThreshold(threshold_depth as u64, *fork_stake)
|
||||
}
|
||||
|
||||
/// Performs vote threshold checks for `slot`
|
||||
pub fn check_vote_stake_thresholds(
|
||||
&self,
|
||||
slot: Slot,
|
||||
voted_stakes: &VotedStakes,
|
||||
total_stake: Stake,
|
||||
) -> ThresholdDecision {
|
||||
// Generate the vote state assuming this vote is included.
|
||||
let mut vote_state = self.vote_state.clone();
|
||||
process_slot_vote_unchecked(&mut vote_state, slot);
|
||||
let lockout = vote_state.nth_recent_lockout(self.threshold_depth);
|
||||
if let Some(lockout) = lockout {
|
||||
if let Some(fork_stake) = voted_stakes.get(&lockout.slot()) {
|
||||
let lockout_stake = *fork_stake as f64 / total_stake as f64;
|
||||
trace!(
|
||||
"fork_stake slot: {}, vote slot: {}, lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot, lockout.slot(), lockout_stake, fork_stake, total_stake
|
||||
);
|
||||
if lockout.confirmation_count() as usize > self.threshold_depth {
|
||||
for old_vote in &self.vote_state.votes {
|
||||
if old_vote.slot() == lockout.slot()
|
||||
&& old_vote.confirmation_count() == lockout.confirmation_count()
|
||||
{
|
||||
return ThresholdDecision::PassedThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lockout_stake > self.threshold_size {
|
||||
return ThresholdDecision::PassedThreshold;
|
||||
}
|
||||
ThresholdDecision::FailedThreshold(*fork_stake)
|
||||
} else {
|
||||
// We haven't seen any votes on this fork yet, so no stake
|
||||
ThresholdDecision::FailedThreshold(0)
|
||||
// Assemble all the vote thresholds and depths to check.
|
||||
let vote_thresholds_and_depths = vec![
|
||||
(VOTE_THRESHOLD_DEPTH_SHALLOW, SWITCH_FORK_THRESHOLD),
|
||||
(self.threshold_depth, self.threshold_size),
|
||||
];
|
||||
|
||||
// Check one by one. If any threshold fails, return failure.
|
||||
for (threshold_depth, threshold_size) in vote_thresholds_and_depths {
|
||||
if let ThresholdDecision::FailedThreshold(vote_depth, stake) =
|
||||
Self::check_vote_stake_threshold(
|
||||
vote_state.nth_recent_lockout(threshold_depth),
|
||||
&self.vote_state,
|
||||
threshold_depth,
|
||||
threshold_size,
|
||||
slot,
|
||||
voted_stakes,
|
||||
total_stake,
|
||||
)
|
||||
{
|
||||
return ThresholdDecision::FailedThreshold(vote_depth, stake);
|
||||
}
|
||||
} else {
|
||||
ThresholdDecision::PassedThreshold
|
||||
}
|
||||
ThresholdDecision::PassedThreshold
|
||||
}
|
||||
|
||||
/// Update lockouts for all the ancestors
|
||||
|
@ -2297,7 +2340,7 @@ pub mod test {
|
|||
fn test_check_vote_threshold_without_votes() {
|
||||
let tower = Tower::new_for_tests(1, 0.67);
|
||||
let stakes = vec![(0, 1)].into_iter().collect();
|
||||
assert!(tower.check_vote_stake_threshold(0, &stakes, 2).passed());
|
||||
assert!(tower.check_vote_stake_thresholds(0, &stakes, 2).passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2310,7 +2353,7 @@ pub mod test {
|
|||
tower.record_vote(i, Hash::default());
|
||||
}
|
||||
assert!(!tower
|
||||
.check_vote_stake_threshold(MAX_LOCKOUT_HISTORY as u64 + 1, &stakes, 2,)
|
||||
.check_vote_stake_thresholds(MAX_LOCKOUT_HISTORY as u64 + 1, &stakes, 2)
|
||||
.passed());
|
||||
}
|
||||
|
||||
|
@ -2426,14 +2469,56 @@ pub mod test {
|
|||
let mut tower = Tower::new_for_tests(1, 0.67);
|
||||
let stakes = vec![(0, 1)].into_iter().collect();
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(!tower.check_vote_stake_threshold(1, &stakes, 2).passed());
|
||||
assert!(!tower.check_vote_stake_thresholds(1, &stakes, 2).passed());
|
||||
}
|
||||
#[test]
|
||||
fn test_check_vote_threshold_above_threshold() {
|
||||
let mut tower = Tower::new_for_tests(1, 0.67);
|
||||
let stakes = vec![(0, 2)].into_iter().collect();
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(tower.check_vote_stake_threshold(1, &stakes, 2).passed());
|
||||
assert!(tower.check_vote_stake_thresholds(1, &stakes, 2).passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_thresholds_above_thresholds() {
|
||||
let mut tower = Tower::new_for_tests(VOTE_THRESHOLD_DEPTH, 0.67);
|
||||
let stakes = vec![(0, 3), (VOTE_THRESHOLD_DEPTH_SHALLOW as u64, 2)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
for slot in 0..VOTE_THRESHOLD_DEPTH {
|
||||
tower.record_vote(slot as Slot, Hash::default());
|
||||
}
|
||||
assert!(tower
|
||||
.check_vote_stake_thresholds(VOTE_THRESHOLD_DEPTH.try_into().unwrap(), &stakes, 4)
|
||||
.passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_deep_below_threshold() {
|
||||
let mut tower = Tower::new_for_tests(VOTE_THRESHOLD_DEPTH, 0.67);
|
||||
let stakes = vec![(0, 6), (VOTE_THRESHOLD_DEPTH_SHALLOW as u64, 4)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
for slot in 0..VOTE_THRESHOLD_DEPTH {
|
||||
tower.record_vote(slot as Slot, Hash::default());
|
||||
}
|
||||
assert!(!tower
|
||||
.check_vote_stake_thresholds(VOTE_THRESHOLD_DEPTH.try_into().unwrap(), &stakes, 10)
|
||||
.passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_shallow_below_threshold() {
|
||||
let mut tower = Tower::new_for_tests(VOTE_THRESHOLD_DEPTH, 0.67);
|
||||
let stakes = vec![(0, 7), (VOTE_THRESHOLD_DEPTH_SHALLOW as u64, 1)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
for slot in 0..VOTE_THRESHOLD_DEPTH {
|
||||
tower.record_vote(slot as Slot, Hash::default());
|
||||
}
|
||||
assert!(!tower
|
||||
.check_vote_stake_thresholds(VOTE_THRESHOLD_DEPTH.try_into().unwrap(), &stakes, 10)
|
||||
.passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2443,7 +2528,7 @@ pub mod test {
|
|||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
tower.record_vote(2, Hash::default());
|
||||
assert!(tower.check_vote_stake_threshold(6, &stakes, 2).passed());
|
||||
assert!(tower.check_vote_stake_thresholds(6, &stakes, 2).passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2451,7 +2536,7 @@ pub mod test {
|
|||
let mut tower = Tower::new_for_tests(1, 0.67);
|
||||
let stakes = HashMap::new();
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(!tower.check_vote_stake_threshold(1, &stakes, 2).passed());
|
||||
assert!(!tower.check_vote_stake_thresholds(1, &stakes, 2).passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2462,7 +2547,7 @@ pub mod test {
|
|||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
tower.record_vote(2, Hash::default());
|
||||
assert!(tower.check_vote_stake_threshold(6, &stakes, 2,).passed());
|
||||
assert!(tower.check_vote_stake_thresholds(6, &stakes, 2).passed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2526,7 +2611,7 @@ pub mod test {
|
|||
&mut LatestValidatorVotesForFrozenBanks::default(),
|
||||
);
|
||||
assert!(tower
|
||||
.check_vote_stake_threshold(vote_to_evaluate, &voted_stakes, total_stake,)
|
||||
.check_vote_stake_thresholds(vote_to_evaluate, &voted_stakes, total_stake)
|
||||
.passed());
|
||||
|
||||
// CASE 2: Now we want to evaluate a vote for slot VOTE_THRESHOLD_DEPTH + 1. This slot
|
||||
|
@ -2546,7 +2631,7 @@ pub mod test {
|
|||
&mut LatestValidatorVotesForFrozenBanks::default(),
|
||||
);
|
||||
assert!(!tower
|
||||
.check_vote_stake_threshold(vote_to_evaluate, &voted_stakes, total_stake,)
|
||||
.check_vote_stake_thresholds(vote_to_evaluate, &voted_stakes, total_stake)
|
||||
.passed());
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ pub enum HeaviestForkFailures {
|
|||
LockedOut(u64),
|
||||
FailedThreshold(
|
||||
Slot,
|
||||
/* vote depth */ u64,
|
||||
/* Observed stake */ u64,
|
||||
/* Total stake */ u64,
|
||||
),
|
||||
|
@ -3305,7 +3306,7 @@ impl ReplayStage {
|
|||
.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);
|
||||
tower.check_vote_stake_thresholds(slot, &stats.voted_stakes, stats.total_stake);
|
||||
stats.is_locked_out = tower.is_locked_out(
|
||||
slot,
|
||||
ancestors
|
||||
|
@ -3646,9 +3647,10 @@ impl ReplayStage {
|
|||
if is_locked_out {
|
||||
failure_reasons.push(HeaviestForkFailures::LockedOut(candidate_vote_bank.slot()));
|
||||
}
|
||||
if let ThresholdDecision::FailedThreshold(fork_stake) = vote_threshold {
|
||||
if let ThresholdDecision::FailedThreshold(vote_depth, fork_stake) = vote_threshold {
|
||||
failure_reasons.push(HeaviestForkFailures::FailedThreshold(
|
||||
candidate_vote_bank.slot(),
|
||||
vote_depth,
|
||||
fork_stake,
|
||||
total_threshold_stake,
|
||||
));
|
||||
|
|
Loading…
Reference in New Issue