patches crds vote-index assignment bug (#14438)

If tower is full, old votes are evicted from the front of the deque:
https://github.com/solana-labs/solana/blob/2074e407c/programs/vote/src/vote_state/mod.rs#L367-L373
whereas recent votes if expire are evicted from the back:
https://github.com/solana-labs/solana/blob/2074e407c/programs/vote/src/vote_state/mod.rs#L529-L537

As a result, from a single tower_index scalar, we cannot infer which crds-vote
should be overwritten:
https://github.com/solana-labs/solana/blob/2074e407c/core/src/crds_value.rs#L576

In addition there is an off by one bug in the existing code. tower_index is
bounded by MAX_LOCKOUT_HISTORY - 1:
https://github.com/solana-labs/solana/blob/2074e407c/core/src/consensus.rs#L382
So, it is at most 30, whereas MAX_VOTES is 32:
https://github.com/solana-labs/solana/blob/2074e407c/core/src/crds_value.rs#L29
Which means that this branch is never taken:
https://github.com/solana-labs/solana/blob/2074e407c/core/src/crds_value.rs#L590-L593
so crds table alwasys keeps 29 **oldest** votes by wallclock, and then
only overrides the 30st one each time. (i.e a tally of only two most
recent votes).
This commit is contained in:
behzad nouri
2021-01-21 13:08:07 +00:00
committed by GitHub
parent fcd72f309a
commit 8e581601d6
7 changed files with 289 additions and 132 deletions

View File

@@ -359,7 +359,7 @@ impl Tower {
slot: Slot,
hash: Hash,
last_voted_slot_in_bank: Option<Slot>,
) -> (Vote, usize) {
) -> (Vote, Vec<Slot> /*VoteState.tower*/) {
let mut local_vote_state = local_vote_state.clone();
let vote = Vote::new(vec![slot], hash);
local_vote_state.process_vote_unchecked(&vote);
@@ -379,7 +379,7 @@ impl Tower {
slots,
local_vote_state.votes
);
(Vote::new(slots, hash), local_vote_state.votes.len() - 1)
(Vote::new(slots, hash), local_vote_state.tower())
}
fn last_voted_slot_in_bank(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
@@ -388,7 +388,11 @@ impl Tower {
slot
}
pub fn new_vote_from_bank(&self, bank: &Bank, vote_account_pubkey: &Pubkey) -> (Vote, usize) {
pub fn new_vote_from_bank(
&self,
bank: &Bank,
vote_account_pubkey: &Pubkey,
) -> (Vote, Vec<Slot> /*VoteState.tower*/) {
let voted_slot = Self::last_voted_slot_in_bank(bank, vote_account_pubkey);
Self::new_vote(&self.lockouts, bank.slot(), bank.hash(), voted_slot)
}
@@ -2268,10 +2272,10 @@ pub mod test {
#[test]
fn test_new_vote() {
let local = VoteState::default();
let vote = Tower::new_vote(&local, 0, Hash::default(), None);
let (vote, tower_slots) = Tower::new_vote(&local, 0, Hash::default(), None);
assert_eq!(local.votes.len(), 0);
assert_eq!(vote.0.slots, vec![0]);
assert_eq!(vote.1, 0);
assert_eq!(vote.slots, vec![0]);
assert_eq!(tower_slots, vec![0]);
}
#[test]
@@ -2291,9 +2295,9 @@ pub mod test {
};
local.process_vote_unchecked(&vote);
assert_eq!(local.votes.len(), 1);
let vote = Tower::new_vote(&local, 1, Hash::default(), Some(0));
assert_eq!(vote.0.slots, vec![1]);
assert_eq!(vote.1, 1);
let (vote, tower_slots) = Tower::new_vote(&local, 1, Hash::default(), Some(0));
assert_eq!(vote.slots, vec![1]);
assert_eq!(tower_slots, vec![0, 1]);
}
#[test]
@@ -2306,10 +2310,10 @@ pub mod test {
};
local.process_vote_unchecked(&vote);
assert_eq!(local.votes.len(), 1);
let vote = Tower::new_vote(&local, 3, Hash::default(), Some(0));
//first vote expired, so index should be 0
assert_eq!(vote.0.slots, vec![3]);
assert_eq!(vote.1, 0);
let (vote, tower_slots) = Tower::new_vote(&local, 3, Hash::default(), Some(0));
assert_eq!(vote.slots, vec![3]);
// First vote expired, so should be evicted from tower.
assert_eq!(tower_slots, vec![3]);
}
#[test]