Improve readability of vote lockout processing (#16987)

* Improve readability of vote lockout processing

* clippy

* simplify comment

* feedback
This commit is contained in:
Justin Starry
2021-05-02 16:36:06 +08:00
committed by GitHub
parent 643133b2c1
commit 8e561354d5
2 changed files with 36 additions and 24 deletions

View File

@ -264,7 +264,7 @@ impl Tower {
}; };
for vote in &vote_state.votes { for vote in &vote_state.votes {
lockout_intervals lockout_intervals
.entry(vote.expiration_slot()) .entry(vote.last_locked_out_slot())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push((vote.slot, key)); .push((vote.slot, key));
} }
@ -534,20 +534,22 @@ impl Tower {
return true; return true;
} }
// Check if a slot is locked out by simulating adding a vote for that
// slot to the current lockouts to pop any expired votes. If any of the
// remaining voted slots are on a different fork from the checked slot,
// it's still locked out.
let mut lockouts = self.lockouts.clone(); let mut lockouts = self.lockouts.clone();
lockouts.process_slot_vote_unchecked(slot); lockouts.process_slot_vote_unchecked(slot);
for vote in &lockouts.votes { for vote in &lockouts.votes {
if vote.slot == slot { if slot != vote.slot && !ancestors[&slot].contains(&vote.slot) {
continue;
}
if !ancestors[&slot].contains(&vote.slot) {
return true; return true;
} }
} }
if let Some(root_slot) = lockouts.root_slot { if let Some(root_slot) = lockouts.root_slot {
if slot != root_slot {
// This case should never happen because bank forks purges all // This case should never happen because bank forks purges all
// non-descendants of the root every time root is set // non-descendants of the root every time root is set
if slot != root_slot {
assert!( assert!(
ancestors[&slot].contains(&root_slot), ancestors[&slot].contains(&root_slot),
"ancestors: {:?}, slot: {} root: {}", "ancestors: {:?}, slot: {} root: {}",
@ -728,8 +730,9 @@ impl Tower {
.unwrap() .unwrap()
.fork_stats .fork_stats
.lockout_intervals; .lockout_intervals;
// Find any locked out intervals in this bank with endpoint >= last_vote, // Find any locked out intervals for vote accounts in this bank with
// implies they are locked out at last_vote // `lockout_interval_end` >= `last_vote`, which implies they are locked out at
// `last_vote` on another fork.
for (_lockout_interval_end, intervals_keyed_by_end) in lockout_intervals.range((Included(last_voted_slot), Unbounded)) { for (_lockout_interval_end, intervals_keyed_by_end) in lockout_intervals.range((Included(last_voted_slot), Unbounded)) {
for (lockout_interval_start, vote_account_pubkey) in intervals_keyed_by_end { for (lockout_interval_start, vote_account_pubkey) in intervals_keyed_by_end {
if locked_out_vote_accounts.contains(vote_account_pubkey) { if locked_out_vote_accounts.contains(vote_account_pubkey) {

View File

@ -84,13 +84,15 @@ impl Lockout {
(INITIAL_LOCKOUT as u64).pow(self.confirmation_count) (INITIAL_LOCKOUT as u64).pow(self.confirmation_count)
} }
// The slot height at which this vote expires (cannot vote for any slot // The last slot at which a vote is still locked out. Validators should not
// less than this) // vote on a slot in another fork which is less than or equal to this slot
pub fn expiration_slot(&self) -> Slot { // to avoid having their stake slashed.
pub fn last_locked_out_slot(&self) -> Slot {
self.slot + self.lockout() self.slot + self.lockout()
} }
pub fn is_expired(&self, slot: Slot) -> bool {
self.expiration_slot() < slot pub fn is_locked_out_at_slot(&self, slot: Slot) -> bool {
self.last_locked_out_slot() >= slot
} }
} }
@ -354,22 +356,24 @@ impl VoteState {
} }
self.check_slots_are_valid(vote, slot_hashes)?; self.check_slots_are_valid(vote, slot_hashes)?;
vote.slots.iter().for_each(|s| self.process_slot(*s, epoch)); vote.slots
.iter()
.for_each(|s| self.process_next_vote_slot(*s, epoch));
Ok(()) Ok(())
} }
pub fn process_slot(&mut self, slot: Slot, epoch: Epoch) { pub fn process_next_vote_slot(&mut self, next_vote_slot: Slot, epoch: Epoch) {
// Ignore votes for slots earlier than we already have votes for // Ignore votes for slots earlier than we already have votes for
if self if self
.last_voted_slot() .last_voted_slot()
.map_or(false, |last_voted_slot| slot <= last_voted_slot) .map_or(false, |last_voted_slot| next_vote_slot <= last_voted_slot)
{ {
return; return;
} }
let vote = Lockout::new(slot); let vote = Lockout::new(next_vote_slot);
self.pop_expired_votes(slot); self.pop_expired_votes(next_vote_slot);
// Once the stack is full, pop the oldest lockout and distribute rewards // Once the stack is full, pop the oldest lockout and distribute rewards
if self.votes.len() == MAX_LOCKOUT_HISTORY { if self.votes.len() == MAX_LOCKOUT_HISTORY {
@ -540,9 +544,13 @@ impl VoteState {
Ok(pubkey) Ok(pubkey)
} }
fn pop_expired_votes(&mut self, slot: Slot) { // Pop all recent votes that are not locked out at the next vote slot. This
loop { // allows validators to switch forks once their votes for another fork have
if self.last_lockout().map_or(false, |v| v.is_expired(slot)) { // expired. This also allows validators continue voting on recent blocks in
// the same fork without increasing lockouts.
fn pop_expired_votes(&mut self, next_vote_slot: Slot) {
while let Some(vote) = self.last_lockout() {
if !vote.is_locked_out_at_slot(next_vote_slot) {
self.votes.pop_back(); self.votes.pop_back();
} else { } else {
break; break;
@ -1350,11 +1358,12 @@ mod tests {
// second vote // second vote
let top_vote = vote_state.votes.front().unwrap().slot; let top_vote = vote_state.votes.front().unwrap().slot;
vote_state vote_state
.process_slot_vote_unchecked(vote_state.last_lockout().unwrap().expiration_slot()); .process_slot_vote_unchecked(vote_state.last_lockout().unwrap().last_locked_out_slot());
assert_eq!(Some(top_vote), vote_state.root_slot); assert_eq!(Some(top_vote), vote_state.root_slot);
// Expire everything except the first vote // Expire everything except the first vote
vote_state.process_slot_vote_unchecked(vote_state.votes.front().unwrap().expiration_slot()); vote_state
.process_slot_vote_unchecked(vote_state.votes.front().unwrap().last_locked_out_slot());
// First vote and new vote are both stored for a total of 2 votes // First vote and new vote are both stored for a total of 2 votes
assert_eq!(vote_state.votes.len(), 2); assert_eq!(vote_state.votes.len(), 2);
} }