Skip to content

Commit

Permalink
Use timestamp to tiebreak votes in banking_stage
Browse files Browse the repository at this point in the history
  • Loading branch information
AshwinSekar committed Jun 1, 2023
1 parent 8203c6e commit 814cfbe
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 14 deletions.
176 changes: 163 additions & 13 deletions core/src/latest_unprocessed_votes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,6 +34,7 @@ pub struct LatestValidatorVotePacket {
vote: Option<Arc<ImmutableDeserializedPacket>>,
slot: Slot,
forwarded: bool,
timestamp: Option<UnixTimestamp>,
}

impl LatestValidatorVotePacket {
Expand Down Expand Up @@ -62,13 +67,15 @@ 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),
slot,
pubkey,
vote_source,
forwarded: false,
timestamp,
})
}
_ => Err(DeserializedPacketError::VoteTransactionError),
Expand All @@ -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)
Expand Down Expand Up @@ -193,12 +204,18 @@ 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_timestamp = latest_vote.read().unwrap().timestamp;
// 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;
Expand All @@ -217,6 +234,7 @@ impl LatestUnprocessedVotes {
None
}

#[cfg(test)]
pub fn get_latest_vote_slot(&self, pubkey: Pubkey) -> Option<Slot> {
self.latest_votes_per_pubkey
.read()
Expand All @@ -225,6 +243,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
Expand Down Expand Up @@ -336,8 +363,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(),
Expand Down Expand Up @@ -481,8 +510,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)
Expand All @@ -505,8 +539,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!(
Expand All @@ -526,6 +566,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]
Expand All @@ -548,6 +696,7 @@ mod tests {
vec![(i, 1)],
VoteSource::Gossip,
&keypairs[rng.gen_range(0, 10)],
None,
);
latest_unprocessed_votes.update_latest_vote(vote);
}
Expand All @@ -562,6 +711,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 {
Expand Down Expand Up @@ -594,8 +744,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);

Expand Down Expand Up @@ -685,11 +835,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);
Expand Down
16 changes: 15 additions & 1 deletion sdk/program/src/vote/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use {
crate::{
clock::Slot,
clock::{Slot, UnixTimestamp},
hash::Hash,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
Expand Down Expand Up @@ -185,6 +185,20 @@ 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> {
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 {
Expand Down

0 comments on commit 814cfbe

Please sign in to comment.