Skip to content

Commit 4bc8d5d

Browse files
authored
banking_stage: do not drain votes that cannot land on our leader fork (#2465)
1 parent 9d4867e commit 4bc8d5d

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

core/src/banking_stage/latest_unprocessed_votes.rs

+45-9
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ use {
88
solana_perf::packet::Packet,
99
solana_runtime::bank::Bank,
1010
solana_sdk::{
11+
account::from_account,
1112
clock::{Slot, UnixTimestamp},
13+
hash::Hash,
1214
program_utils::limited_deserialize,
1315
pubkey::Pubkey,
16+
slot_hashes::SlotHashes,
17+
sysvar,
1418
},
1519
solana_vote_program::vote_instruction::VoteInstruction,
1620
std::{
@@ -36,6 +40,7 @@ pub struct LatestValidatorVotePacket {
3640
pubkey: Pubkey,
3741
vote: Option<Arc<ImmutableDeserializedPacket>>,
3842
slot: Slot,
43+
hash: Hash,
3944
forwarded: bool,
4045
timestamp: Option<UnixTimestamp>,
4146
}
@@ -70,11 +75,13 @@ impl LatestValidatorVotePacket {
7075
.first()
7176
.ok_or(DeserializedPacketError::VoteTransactionError)?;
7277
let slot = vote_state_update_instruction.last_voted_slot().unwrap_or(0);
78+
let hash = vote_state_update_instruction.hash();
7379
let timestamp = vote_state_update_instruction.timestamp();
7480

7581
Ok(Self {
7682
vote: Some(vote),
7783
slot,
84+
hash,
7885
pubkey,
7986
vote_source,
8087
forwarded: false,
@@ -97,6 +104,10 @@ impl LatestValidatorVotePacket {
97104
self.slot
98105
}
99106

107+
pub(crate) fn hash(&self) -> Hash {
108+
self.hash
109+
}
110+
100111
pub fn timestamp(&self) -> Option<UnixTimestamp> {
101112
self.timestamp
102113
}
@@ -115,9 +126,6 @@ impl LatestValidatorVotePacket {
115126
}
116127
}
117128

118-
// TODO: replace this with rand::seq::index::sample_weighted once we can update rand to 0.8+
119-
// This requires updating dependencies of ed25519-dalek as rand_core is not compatible cross
120-
// version https://github.com/dalek-cryptography/ed25519-dalek/pull/214
121129
pub(crate) fn weighted_random_order_by_stake<'a>(
122130
bank: &Bank,
123131
pubkeys: impl Iterator<Item = &'a Pubkey>,
@@ -322,17 +330,30 @@ impl LatestUnprocessedVotes {
322330
}
323331

324332
/// Drains all votes yet to be processed sorted by a weighted random ordering by stake
333+
/// Do not touch votes that are for a different fork from `bank` as we know they will fail,
334+
/// however the next bank could be built on a different fork and consume these votes.
325335
pub fn drain_unprocessed(&self, bank: Arc<Bank>) -> Vec<Arc<ImmutableDeserializedPacket>> {
326-
let pubkeys_by_stake = weighted_random_order_by_stake(
327-
&bank,
328-
self.latest_votes_per_pubkey.read().unwrap().keys(),
329-
)
330-
.collect_vec();
336+
let slot_hashes = bank
337+
.get_account(&sysvar::slot_hashes::id())
338+
.and_then(|account| from_account::<SlotHashes, _>(&account));
339+
if slot_hashes.is_none() {
340+
error!(
341+
"Slot hashes sysvar doesn't exist on bank {}. Including all votes without filtering",
342+
bank.slot()
343+
);
344+
}
345+
346+
let pubkeys_by_stake = {
347+
let binding = self.latest_votes_per_pubkey.read().unwrap();
348+
weighted_random_order_by_stake(&bank, binding.keys())
349+
};
331350
pubkeys_by_stake
332-
.into_iter()
333351
.filter_map(|pubkey| {
334352
self.get_entry(pubkey).and_then(|lock| {
335353
let mut latest_vote = lock.write().unwrap();
354+
if !Self::is_valid_for_our_fork(&latest_vote, &slot_hashes) {
355+
return None;
356+
}
336357
latest_vote.take_vote().map(|vote| {
337358
self.num_unprocessed_votes.fetch_sub(1, Ordering::Relaxed);
338359
vote
@@ -342,6 +363,21 @@ impl LatestUnprocessedVotes {
342363
.collect_vec()
343364
}
344365

366+
/// Check if `vote` can land in our fork based on `slot_hashes`
367+
fn is_valid_for_our_fork(
368+
vote: &LatestValidatorVotePacket,
369+
slot_hashes: &Option<SlotHashes>,
370+
) -> bool {
371+
let Some(slot_hashes) = slot_hashes else {
372+
// When slot hashes is not present we do not filter
373+
return true;
374+
};
375+
slot_hashes
376+
.get(&vote.slot())
377+
.map(|found_hash| *found_hash == vote.hash())
378+
.unwrap_or(false)
379+
}
380+
345381
/// Sometimes we forward and hold the packets, sometimes we forward and clear.
346382
/// This also clears all gossip votes since by definition they have been forwarded
347383
pub fn clear_forwarded_packets(&self) {

sdk/program/src/vote/instruction.rs

+13
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,19 @@ impl VoteInstruction {
209209
}
210210
}
211211

212+
/// Only to be used on vote instructions (guard with is_simple_vote), panics otherwise
213+
pub fn hash(&self) -> Hash {
214+
assert!(self.is_simple_vote());
215+
match self {
216+
Self::Vote(v) | Self::VoteSwitch(v, _) => v.hash,
217+
Self::UpdateVoteState(vote_state_update)
218+
| Self::UpdateVoteStateSwitch(vote_state_update, _)
219+
| Self::CompactUpdateVoteState(vote_state_update)
220+
| Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => vote_state_update.hash,
221+
Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => tower_sync.hash,
222+
_ => panic!("Tried to get hash on non simple vote instruction"),
223+
}
224+
}
212225
/// Only to be used on vote instructions (guard with is_simple_vote), panics otherwise
213226
pub fn timestamp(&self) -> Option<UnixTimestamp> {
214227
assert!(self.is_simple_vote());

0 commit comments

Comments
 (0)