Use timestamp to tiebreak votes in banking_stage (#31925)

This commit is contained in:
Ashwin Sekar 2023-06-05 09:28:00 -07:00 committed by GitHub
parent 6b33ff8ae9
commit 9f62cc1e19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 182 additions and 15 deletions

View File

@ -7,7 +7,11 @@ use {
rand::{thread_rng, Rng},
solana_perf::packet::Packet,
solana_runtime::bank::Bank,
solana_sdk::{clock::Slot, program_utils::limited_deserialize, pubkey::Pubkey},
solana_sdk::{
clock::{Slot, UnixTimestamp},
program_utils::limited_deserialize,
pubkey::Pubkey,
},
solana_vote_program::vote_instruction::VoteInstruction,
std::{
collections::HashMap,
@ -30,6 +34,7 @@ pub struct LatestValidatorVotePacket {
vote: Option<Arc<ImmutableDeserializedPacket>>,
slot: Slot,
forwarded: bool,
timestamp: Option<UnixTimestamp>,
}
impl LatestValidatorVotePacket {
@ -62,6 +67,7 @@ impl LatestValidatorVotePacket {
.get(0)
.ok_or(DeserializedPacketError::VoteTransactionError)?;
let slot = vote_state_update_instruction.last_voted_slot().unwrap_or(0);
let timestamp = vote_state_update_instruction.timestamp();
Ok(Self {
vote: Some(vote),
@ -69,6 +75,7 @@ impl LatestValidatorVotePacket {
pubkey,
vote_source,
forwarded: false,
timestamp,
})
}
_ => Err(DeserializedPacketError::VoteTransactionError),
@ -87,6 +94,10 @@ impl LatestValidatorVotePacket {
self.slot
}
pub fn timestamp(&self) -> Option<UnixTimestamp> {
self.timestamp
}
pub fn is_forwarded(&self) -> bool {
// By definition all gossip votes have been forwarded
self.forwarded || matches!(self.vote_source, VoteSource::Gossip)
@ -193,12 +204,20 @@ impl LatestUnprocessedVotes {
) -> Option<LatestValidatorVotePacket> {
let pubkey = vote.pubkey();
let slot = vote.slot();
let timestamp = vote.timestamp();
if let Some(latest_vote) = self.get_entry(pubkey) {
let latest_slot = latest_vote.read().unwrap().slot();
if slot > latest_slot {
let (latest_slot, latest_timestamp) = latest_vote
.read()
.map(|vote| (vote.slot(), vote.timestamp()))
.unwrap();
// Allow votes for later slots or the same slot with later timestamp (refreshed votes)
// We directly compare as options to prioritize votes for same slot with timestamp as
// Some > None
if slot > latest_slot || ((slot == latest_slot) && (timestamp > latest_timestamp)) {
let mut latest_vote = latest_vote.write().unwrap();
let latest_slot = latest_vote.slot();
if slot > latest_slot {
let latest_timestamp = latest_vote.timestamp();
if slot > latest_slot || ((slot == latest_slot) && (timestamp > latest_timestamp)) {
let old_vote = std::mem::replace(latest_vote.deref_mut(), vote);
if old_vote.is_vote_taken() {
return None;
@ -217,6 +236,7 @@ impl LatestUnprocessedVotes {
None
}
#[cfg(test)]
pub fn get_latest_vote_slot(&self, pubkey: Pubkey) -> Option<Slot> {
self.latest_votes_per_pubkey
.read()
@ -225,6 +245,15 @@ impl LatestUnprocessedVotes {
.map(|l| l.read().unwrap().slot())
}
#[cfg(test)]
fn get_latest_timestamp(&self, pubkey: Pubkey) -> Option<UnixTimestamp> {
self.latest_votes_per_pubkey
.read()
.unwrap()
.get(&pubkey)
.and_then(|l| l.read().unwrap().timestamp())
}
/// Returns how many packets were forwardable
/// Performs a weighted random order based on stake and stops forwarding at the first error
/// Votes from validators with 0 stakes are ignored
@ -336,8 +365,10 @@ mod tests {
slots: Vec<(u64, u32)>,
vote_source: VoteSource,
keypairs: &ValidatorVoteKeypairs,
timestamp: Option<UnixTimestamp>,
) -> LatestValidatorVotePacket {
let vote = VoteStateUpdate::from(slots);
let mut vote = VoteStateUpdate::from(slots);
vote.timestamp = timestamp;
let vote_tx = new_vote_state_update_transaction(
vote,
Hash::new_unique(),
@ -481,8 +512,13 @@ mod tests {
let keypair_a = ValidatorVoteKeypairs::new_rand();
let keypair_b = ValidatorVoteKeypairs::new_rand();
let vote_a = from_slots(vec![(0, 2), (1, 1)], VoteSource::Gossip, &keypair_a);
let vote_b = from_slots(vec![(0, 5), (4, 2), (9, 1)], VoteSource::Gossip, &keypair_b);
let vote_a = from_slots(vec![(0, 2), (1, 1)], VoteSource::Gossip, &keypair_a, None);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (9, 1)],
VoteSource::Gossip,
&keypair_b,
None,
);
assert!(latest_unprocessed_votes
.update_latest_vote(vote_a)
@ -505,8 +541,14 @@ mod tests {
vec![(0, 5), (1, 4), (3, 3), (10, 1)],
VoteSource::Gossip,
&keypair_a,
None,
);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (6, 1)],
VoteSource::Gossip,
&keypair_b,
None,
);
let vote_b = from_slots(vec![(0, 5), (4, 2), (6, 1)], VoteSource::Gossip, &keypair_a);
// Evict previous vote
assert_eq!(
@ -526,6 +568,114 @@ mod tests {
);
assert_eq!(2, latest_unprocessed_votes.len());
// Same votes should be no-ops
let vote_a = from_slots(
vec![(0, 5), (1, 4), (3, 3), (10, 1)],
VoteSource::Gossip,
&keypair_a,
None,
);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (9, 1)],
VoteSource::Gossip,
&keypair_b,
None,
);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);
assert_eq!(2, latest_unprocessed_votes.len());
assert_eq!(
10,
latest_unprocessed_votes
.get_latest_vote_slot(keypair_a.node_keypair.pubkey())
.unwrap()
);
assert_eq!(
9,
latest_unprocessed_votes
.get_latest_vote_slot(keypair_b.node_keypair.pubkey())
.unwrap()
);
// Same votes with timestamps should override
let vote_a = from_slots(
vec![(0, 5), (1, 4), (3, 3), (10, 1)],
VoteSource::Gossip,
&keypair_a,
Some(1),
);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (9, 1)],
VoteSource::Gossip,
&keypair_b,
Some(2),
);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);
assert_eq!(2, latest_unprocessed_votes.len());
assert_eq!(
Some(1),
latest_unprocessed_votes.get_latest_timestamp(keypair_a.node_keypair.pubkey())
);
assert_eq!(
Some(2),
latest_unprocessed_votes.get_latest_timestamp(keypair_b.node_keypair.pubkey())
);
// Same votes with bigger timestamps should override
let vote_a = from_slots(
vec![(0, 5), (1, 4), (3, 3), (10, 1)],
VoteSource::Gossip,
&keypair_a,
Some(5),
);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (9, 1)],
VoteSource::Gossip,
&keypair_b,
Some(6),
);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);
assert_eq!(2, latest_unprocessed_votes.len());
assert_eq!(
Some(5),
latest_unprocessed_votes.get_latest_timestamp(keypair_a.node_keypair.pubkey())
);
assert_eq!(
Some(6),
latest_unprocessed_votes.get_latest_timestamp(keypair_b.node_keypair.pubkey())
);
// Same votes with smaller timestamps should not override
let vote_a = from_slots(
vec![(0, 5), (1, 4), (3, 3), (10, 1)],
VoteSource::Gossip,
&keypair_a,
Some(2),
);
let vote_b = from_slots(
vec![(0, 5), (4, 2), (9, 1)],
VoteSource::Gossip,
&keypair_b,
Some(3),
);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);
assert_eq!(2, latest_unprocessed_votes.len());
assert_eq!(
Some(5),
latest_unprocessed_votes.get_latest_timestamp(keypair_a.node_keypair.pubkey())
);
assert_eq!(
Some(6),
latest_unprocessed_votes.get_latest_timestamp(keypair_b.node_keypair.pubkey())
);
}
#[test]
@ -548,6 +698,7 @@ mod tests {
vec![(i, 1)],
VoteSource::Gossip,
&keypairs[rng.gen_range(0, 10)],
None,
);
latest_unprocessed_votes.update_latest_vote(vote);
}
@ -562,6 +713,7 @@ mod tests {
vec![(i, 1)],
VoteSource::Tpu,
&keypairs_tpu[rng.gen_range(0, 10)],
None,
);
latest_unprocessed_votes_tpu.update_latest_vote(vote);
if i % 214 == 0 {
@ -594,8 +746,8 @@ mod tests {
let keypair_a = ValidatorVoteKeypairs::new_rand();
let keypair_b = ValidatorVoteKeypairs::new_rand();
let vote_a = from_slots(vec![(1, 1)], VoteSource::Gossip, &keypair_a);
let vote_b = from_slots(vec![(2, 1)], VoteSource::Tpu, &keypair_b);
let vote_a = from_slots(vec![(1, 1)], VoteSource::Gossip, &keypair_a, None);
let vote_b = from_slots(vec![(2, 1)], VoteSource::Tpu, &keypair_b, None);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);
@ -685,11 +837,11 @@ mod tests {
let keypair_c = ValidatorVoteKeypairs::new_rand();
let keypair_d = ValidatorVoteKeypairs::new_rand();
let vote_a = from_slots(vec![(1, 1)], VoteSource::Gossip, &keypair_a);
let mut vote_b = from_slots(vec![(2, 1)], VoteSource::Tpu, &keypair_b);
let vote_a = from_slots(vec![(1, 1)], VoteSource::Gossip, &keypair_a, None);
let mut vote_b = from_slots(vec![(2, 1)], VoteSource::Tpu, &keypair_b, None);
vote_b.forwarded = true;
let vote_c = from_slots(vec![(3, 1)], VoteSource::Tpu, &keypair_c);
let vote_d = from_slots(vec![(4, 1)], VoteSource::Gossip, &keypair_d);
let vote_c = from_slots(vec![(3, 1)], VoteSource::Tpu, &keypair_c, None);
let vote_d = from_slots(vec![(4, 1)], VoteSource::Gossip, &keypair_d, None);
latest_unprocessed_votes.update_latest_vote(vote_a);
latest_unprocessed_votes.update_latest_vote(vote_b);

View File

@ -2,7 +2,7 @@
use {
crate::{
clock::Slot,
clock::{Slot, UnixTimestamp},
hash::Hash,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
@ -185,6 +185,21 @@ impl VoteInstruction {
_ => panic!("Tried to get slot on non simple vote instruction"),
}
}
/// Only to be used on vote instructions (guard with is_simple_vote), panics otherwise
pub fn timestamp(&self) -> Option<UnixTimestamp> {
assert!(self.is_simple_vote());
match self {
Self::Vote(v) | Self::VoteSwitch(v, _) => v.timestamp,
Self::UpdateVoteState(vote_state_update)
| Self::UpdateVoteStateSwitch(vote_state_update, _)
| Self::CompactUpdateVoteState(vote_state_update)
| Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
vote_state_update.timestamp
}
_ => panic!("Tried to get timestamp on non simple vote instruction"),
}
}
}
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {