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:
Ashwin Sekar
2021-12-07 16:47:26 -08:00
committed by GitHub
parent 1df88837c8
commit f0acf7681e
18 changed files with 591 additions and 103 deletions

View File

@ -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,

View File

@ -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)
}

View File

@ -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,