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:
@@ -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]
|
||||
|
Reference in New Issue
Block a user