Add vote instructions that directly update on chain vote state (#21531)
* Add vote state instructions UpdateVoteState and UpdateVoteStateSwitch * cargo tree * extract vote state version conversion to common fn
This commit is contained in:
@ -33,7 +33,7 @@ use {
|
||||
bank_forks::BankForks,
|
||||
commitment::VOTE_THRESHOLD_SIZE,
|
||||
epoch_stakes::{EpochAuthorizedVoters, EpochStakes},
|
||||
vote_sender_types::{ReplayVoteReceiver, ReplayedVote},
|
||||
vote_sender_types::ReplayVoteReceiver,
|
||||
},
|
||||
solana_sdk::{
|
||||
clock::{Epoch, Slot, DEFAULT_MS_PER_SLOT, DEFAULT_TICKS_PER_SLOT},
|
||||
@ -44,7 +44,10 @@ use {
|
||||
slot_hashes,
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_vote_program::{self, vote_state::Vote, vote_transaction},
|
||||
solana_vote_program::{
|
||||
vote_state::VoteTransaction,
|
||||
vote_transaction::{self, ParsedVote},
|
||||
},
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{
|
||||
@ -403,7 +406,7 @@ impl ClusterInfoVoteListener {
|
||||
.filter_map(|(vote_tx, packet)| {
|
||||
let (vote, vote_account_key) = vote_transaction::parse_vote_transaction(&vote_tx)
|
||||
.and_then(|(vote_account_key, vote, _)| {
|
||||
if vote.slots.is_empty() {
|
||||
if vote.slots().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((vote, vote_account_key))
|
||||
@ -674,7 +677,7 @@ impl ClusterInfoVoteListener {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn track_new_votes_and_notify_confirmations(
|
||||
vote: Vote,
|
||||
vote: Box<dyn VoteTransaction>,
|
||||
vote_pubkey: &Pubkey,
|
||||
vote_tracker: &VoteTracker,
|
||||
root_bank: &Bank,
|
||||
@ -687,17 +690,17 @@ impl ClusterInfoVoteListener {
|
||||
bank_notification_sender: &Option<BankNotificationSender>,
|
||||
cluster_confirmed_slot_sender: &Option<GossipDuplicateConfirmedSlotsSender>,
|
||||
) {
|
||||
if vote.slots.is_empty() {
|
||||
if vote.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let last_vote_slot = *vote.slots.last().unwrap();
|
||||
let last_vote_hash = vote.hash;
|
||||
let (last_vote_slot, last_vote_hash) = vote.last_voted_slot_hash().unwrap();
|
||||
|
||||
let root = root_bank.slot();
|
||||
let mut is_new_vote = false;
|
||||
let vote_slots = vote.slots();
|
||||
// If slot is before the root, ignore it
|
||||
for slot in vote.slots.iter().filter(|slot| **slot > root).rev() {
|
||||
for slot in vote_slots.iter().filter(|slot| **slot > root).rev() {
|
||||
let slot = *slot;
|
||||
|
||||
// if we don't have stake information, ignore it
|
||||
@ -781,28 +784,28 @@ impl ClusterInfoVoteListener {
|
||||
}
|
||||
|
||||
if is_new_vote {
|
||||
subscriptions.notify_vote(&vote);
|
||||
let _ = verified_vote_sender.send((*vote_pubkey, vote.slots));
|
||||
subscriptions.notify_vote(vote);
|
||||
let _ = verified_vote_sender.send((*vote_pubkey, vote_slots));
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_gossip_votes(
|
||||
vote_tracker: &VoteTracker,
|
||||
vote_pubkey: &Pubkey,
|
||||
vote: &Vote,
|
||||
vote: &dyn VoteTransaction,
|
||||
gossip_tx: &Transaction,
|
||||
) -> bool {
|
||||
if vote.slots.is_empty() {
|
||||
if vote.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let last_vote_slot = vote.slots.last().unwrap();
|
||||
let last_vote_slot = vote.last_voted_slot().unwrap();
|
||||
// Votes from gossip need to be verified as they have not been
|
||||
// verified by the replay pipeline. Determine the authorized voter
|
||||
// based on the last vote slot. This will drop votes from authorized
|
||||
// voters trying to make votes for slots earlier than the epoch for
|
||||
// which they are authorized
|
||||
let actual_authorized_voter =
|
||||
vote_tracker.get_authorized_voter(vote_pubkey, *last_vote_slot);
|
||||
vote_tracker.get_authorized_voter(vote_pubkey, last_vote_slot);
|
||||
|
||||
if actual_authorized_voter.is_none() {
|
||||
return false;
|
||||
@ -822,7 +825,7 @@ impl ClusterInfoVoteListener {
|
||||
fn filter_and_confirm_with_new_votes(
|
||||
vote_tracker: &VoteTracker,
|
||||
gossip_vote_txs: Vec<Transaction>,
|
||||
replayed_votes: Vec<ReplayedVote>,
|
||||
replayed_votes: Vec<ParsedVote>,
|
||||
root_bank: &Bank,
|
||||
subscriptions: &RpcSubscriptions,
|
||||
gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender,
|
||||
@ -839,7 +842,7 @@ impl ClusterInfoVoteListener {
|
||||
.filter_map(|gossip_tx| {
|
||||
vote_transaction::parse_vote_transaction(gossip_tx)
|
||||
.filter(|(vote_pubkey, vote, _)| {
|
||||
Self::filter_gossip_votes(vote_tracker, vote_pubkey, vote, gossip_tx)
|
||||
Self::filter_gossip_votes(vote_tracker, vote_pubkey, &**vote, gossip_tx)
|
||||
})
|
||||
.map(|v| (true, v))
|
||||
})
|
||||
@ -1243,7 +1246,7 @@ mod tests {
|
||||
replay_votes_sender
|
||||
.send((
|
||||
vote_keypair.pubkey(),
|
||||
replay_vote.clone(),
|
||||
Box::new(replay_vote.clone()),
|
||||
switch_proof_hash,
|
||||
))
|
||||
.unwrap();
|
||||
@ -1490,7 +1493,8 @@ mod tests {
|
||||
let (votes_sender, votes_receiver) = unbounded();
|
||||
let (verified_vote_sender, _verified_vote_receiver) = unbounded();
|
||||
let (gossip_verified_vote_hash_sender, _gossip_verified_vote_hash_receiver) = unbounded();
|
||||
let (replay_votes_sender, replay_votes_receiver) = unbounded();
|
||||
let (replay_votes_sender, replay_votes_receiver): (ReplayVoteSender, ReplayVoteReceiver) =
|
||||
unbounded();
|
||||
|
||||
let vote_slot = 1;
|
||||
let vote_bank_hash = Hash::default();
|
||||
@ -1530,7 +1534,7 @@ mod tests {
|
||||
replay_votes_sender
|
||||
.send((
|
||||
vote_keypair.pubkey(),
|
||||
Vote::new(vec![vote_slot], Hash::default()),
|
||||
Box::new(Vote::new(vec![vote_slot], Hash::default())),
|
||||
switch_proof_hash,
|
||||
))
|
||||
.unwrap();
|
||||
@ -1676,7 +1680,7 @@ mod tests {
|
||||
// Add gossip vote for same slot, should not affect outcome
|
||||
vec![(
|
||||
validator0_keypairs.vote_keypair.pubkey(),
|
||||
Vote::new(vec![voted_slot], Hash::default()),
|
||||
Box::new(Vote::new(vec![voted_slot], Hash::default())),
|
||||
None,
|
||||
)],
|
||||
&bank,
|
||||
@ -1732,7 +1736,7 @@ mod tests {
|
||||
vote_txs,
|
||||
vec![(
|
||||
validator_keypairs[1].vote_keypair.pubkey(),
|
||||
Vote::new(vec![first_slot_in_new_epoch], Hash::default()),
|
||||
Box::new(Vote::new(vec![first_slot_in_new_epoch], Hash::default())),
|
||||
None,
|
||||
)],
|
||||
&new_root_bank,
|
||||
|
@ -21,7 +21,9 @@ use {
|
||||
},
|
||||
solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY},
|
||||
vote_state::{
|
||||
BlockTimestamp, Lockout, Vote, VoteState, VoteTransaction, MAX_LOCKOUT_HISTORY,
|
||||
},
|
||||
},
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
@ -367,22 +369,16 @@ impl Tower {
|
||||
) -> Vote {
|
||||
let vote = Vote::new(vec![slot], hash);
|
||||
local_vote_state.process_vote_unchecked(&vote);
|
||||
let slots = if let Some(last_voted_slot_in_bank) = last_voted_slot_in_bank {
|
||||
let slots = if let Some(last_voted_slot) = last_voted_slot_in_bank {
|
||||
local_vote_state
|
||||
.votes
|
||||
.iter()
|
||||
.map(|v| v.slot)
|
||||
.skip_while(|s| *s <= last_voted_slot_in_bank)
|
||||
.skip_while(|s| *s <= last_voted_slot)
|
||||
.collect()
|
||||
} else {
|
||||
local_vote_state.votes.iter().map(|v| v.slot).collect()
|
||||
};
|
||||
trace!(
|
||||
"new vote with {:?} {:?} {:?}",
|
||||
last_voted_slot_in_bank,
|
||||
slots,
|
||||
local_vote_state.votes
|
||||
);
|
||||
Vote::new(slots, hash)
|
||||
}
|
||||
|
||||
@ -415,7 +411,7 @@ impl Tower {
|
||||
last_voted_slot_in_bank,
|
||||
);
|
||||
|
||||
new_vote.timestamp = self.maybe_timestamp(self.last_vote.last_voted_slot().unwrap_or(0));
|
||||
new_vote.set_timestamp(self.maybe_timestamp(self.last_vote.last_voted_slot().unwrap_or(0)));
|
||||
self.last_vote = new_vote;
|
||||
|
||||
let new_root = self.root();
|
||||
@ -2252,7 +2248,7 @@ pub mod test {
|
||||
let mut local = VoteState::default();
|
||||
let vote = Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), None);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
assert_eq!(vote.slots, vec![0]);
|
||||
assert_eq!(vote.slots(), vec![0]);
|
||||
assert_eq!(local.tower(), vec![0]);
|
||||
}
|
||||
|
||||
@ -2263,7 +2259,7 @@ pub mod test {
|
||||
// another vote for slot 0 should return an empty vote as the diff.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), Some(0));
|
||||
assert!(vote.slots.is_empty());
|
||||
assert!(vote.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2278,7 +2274,7 @@ pub mod test {
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 1, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![1]);
|
||||
assert_eq!(vote.slots(), vec![1]);
|
||||
assert_eq!(local.tower(), vec![0, 1]);
|
||||
}
|
||||
|
||||
@ -2298,7 +2294,7 @@ pub mod test {
|
||||
// observable in any of the results.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 3, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![3]);
|
||||
assert_eq!(vote.slots(), vec![3]);
|
||||
assert_eq!(local.tower(), vec![3]);
|
||||
}
|
||||
|
||||
@ -2380,7 +2376,7 @@ pub mod test {
|
||||
tower.record_vote(i as u64, Hash::default());
|
||||
}
|
||||
|
||||
expected.timestamp = tower.last_vote.timestamp;
|
||||
expected.timestamp = tower.last_vote.timestamp();
|
||||
assert_eq!(expected, tower.last_vote)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use {
|
||||
account::from_account, clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signature,
|
||||
slot_hashes::SlotHashes, sysvar,
|
||||
},
|
||||
solana_vote_program::vote_state::Vote,
|
||||
solana_vote_program::vote_state::VoteTransaction,
|
||||
std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
sync::Arc,
|
||||
@ -19,7 +19,7 @@ const MAX_VOTES_PER_VALIDATOR: usize = 1000;
|
||||
|
||||
pub struct VerifiedVoteMetadata {
|
||||
pub vote_account_key: Pubkey,
|
||||
pub vote: Vote,
|
||||
pub vote: Box<dyn VoteTransaction>,
|
||||
pub packet: Packets,
|
||||
pub signature: Signature,
|
||||
}
|
||||
@ -153,15 +153,15 @@ impl VerifiedVotePackets {
|
||||
packet,
|
||||
signature,
|
||||
} = verfied_vote_metadata;
|
||||
if vote.slots.is_empty() {
|
||||
if vote.is_empty() {
|
||||
error!("Empty votes should have been filtered out earlier in the pipeline");
|
||||
continue;
|
||||
}
|
||||
let slot = vote.slots.last().unwrap();
|
||||
let hash = vote.hash;
|
||||
let slot = vote.last_voted_slot().unwrap();
|
||||
let hash = vote.hash();
|
||||
|
||||
let validator_votes = self.0.entry(vote_account_key).or_default();
|
||||
validator_votes.insert((*slot, hash), (packet, signature));
|
||||
validator_votes.insert((slot, hash), (packet, signature));
|
||||
|
||||
if validator_votes.len() > MAX_VOTES_PER_VALIDATOR {
|
||||
let smallest_key = validator_votes.keys().next().cloned().unwrap();
|
||||
@ -182,6 +182,7 @@ mod tests {
|
||||
crossbeam_channel::unbounded,
|
||||
solana_perf::packet::Packet,
|
||||
solana_sdk::slot_hashes::MAX_ENTRIES,
|
||||
solana_vote_program::vote_state::Vote,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -198,7 +199,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote: vote.clone(),
|
||||
vote: Box::new(vote.clone()),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new(&[1u8; 64]),
|
||||
}])
|
||||
@ -218,7 +219,7 @@ mod tests {
|
||||
// Same slot, same hash, should not be inserted
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new(&[1u8; 64]),
|
||||
}])
|
||||
@ -240,7 +241,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![vote_slot], new_vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new(&[1u8; 64]),
|
||||
}])
|
||||
@ -263,7 +264,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new(&[2u8; 64]),
|
||||
}])
|
||||
@ -302,7 +303,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new(&[1u8; 64]),
|
||||
}])
|
||||
@ -339,7 +340,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::default(),
|
||||
signature: Signature::new_unique(),
|
||||
}])
|
||||
@ -393,7 +394,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![*vote_slot], *vote_hash);
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
vote: Box::new(vote),
|
||||
packet: Packets::new(vec![Packet::default(); num_packets]),
|
||||
signature: Signature::new_unique(),
|
||||
}])
|
||||
@ -457,7 +458,7 @@ mod tests {
|
||||
my_leader_bank.slot() + 1,
|
||||
));
|
||||
let vote_account_key = vote_simulator.vote_pubkeys[1];
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
let vote = Box::new(Vote::new(vec![vote_slot], vote_hash));
|
||||
s.send(vec![VerifiedVoteMetadata {
|
||||
vote_account_key,
|
||||
vote,
|
||||
|
Reference in New Issue
Block a user