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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 591 additions and 103 deletions

65
Cargo.lock generated
View File

@ -962,6 +962,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote 1.0.10",
"syn 1.0.81",
]
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "3.2.1" version = "3.2.1"
@ -1277,6 +1287,15 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "erased-serde"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.2.8"
@ -1640,6 +1659,17 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1", "wasi 0.10.2+wasi-snapshot-preview1",
] ]
[[package]]
name = "ghost"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479"
dependencies = [
"proc-macro2 1.0.32",
"quote 1.0.10",
"syn 1.0.81",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.25.0" version = "0.25.0"
@ -2012,6 +2042,16 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "inventory"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1367fed6750ff2a5bcb967a631528303bb85631f167a75eb1bf7762d57eb7678"
dependencies = [
"ctor",
"ghost",
]
[[package]] [[package]]
name = "iovec" name = "iovec"
version = "0.1.4" version = "0.1.4"
@ -5998,6 +6038,7 @@ dependencies = [
"solana-program-runtime", "solana-program-runtime",
"solana-sdk", "solana-sdk",
"thiserror", "thiserror",
"typetag",
] ]
[[package]] [[package]]
@ -6935,6 +6976,30 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "typetag"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4080564c5b2241b5bff53ab610082234e0c57b0417f4bd10596f183001505b8a"
dependencies = [
"erased-serde",
"inventory",
"once_cell",
"serde",
"typetag-impl",
]
[[package]]
name = "typetag-impl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e60147782cc30833c05fba3bab1d9b5771b2685a2557672ac96fa5d154099c0e"
dependencies = [
"proc-macro2 1.0.32",
"quote 1.0.10",
"syn 1.0.81",
]
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
version = "0.1.3" version = "0.1.3"

View File

@ -33,7 +33,7 @@ use {
bank_forks::BankForks, bank_forks::BankForks,
commitment::VOTE_THRESHOLD_SIZE, commitment::VOTE_THRESHOLD_SIZE,
epoch_stakes::{EpochAuthorizedVoters, EpochStakes}, epoch_stakes::{EpochAuthorizedVoters, EpochStakes},
vote_sender_types::{ReplayVoteReceiver, ReplayedVote}, vote_sender_types::ReplayVoteReceiver,
}, },
solana_sdk::{ solana_sdk::{
clock::{Epoch, Slot, DEFAULT_MS_PER_SLOT, DEFAULT_TICKS_PER_SLOT}, clock::{Epoch, Slot, DEFAULT_MS_PER_SLOT, DEFAULT_TICKS_PER_SLOT},
@ -44,7 +44,10 @@ use {
slot_hashes, slot_hashes,
transaction::Transaction, transaction::Transaction,
}, },
solana_vote_program::{self, vote_state::Vote, vote_transaction}, solana_vote_program::{
vote_state::VoteTransaction,
vote_transaction::{self, ParsedVote},
},
std::{ std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::{ sync::{
@ -403,7 +406,7 @@ impl ClusterInfoVoteListener {
.filter_map(|(vote_tx, packet)| { .filter_map(|(vote_tx, packet)| {
let (vote, vote_account_key) = vote_transaction::parse_vote_transaction(&vote_tx) let (vote, vote_account_key) = vote_transaction::parse_vote_transaction(&vote_tx)
.and_then(|(vote_account_key, vote, _)| { .and_then(|(vote_account_key, vote, _)| {
if vote.slots.is_empty() { if vote.slots().is_empty() {
None None
} else { } else {
Some((vote, vote_account_key)) Some((vote, vote_account_key))
@ -674,7 +677,7 @@ impl ClusterInfoVoteListener {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn track_new_votes_and_notify_confirmations( fn track_new_votes_and_notify_confirmations(
vote: Vote, vote: Box<dyn VoteTransaction>,
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
root_bank: &Bank, root_bank: &Bank,
@ -687,17 +690,17 @@ impl ClusterInfoVoteListener {
bank_notification_sender: &Option<BankNotificationSender>, bank_notification_sender: &Option<BankNotificationSender>,
cluster_confirmed_slot_sender: &Option<GossipDuplicateConfirmedSlotsSender>, cluster_confirmed_slot_sender: &Option<GossipDuplicateConfirmedSlotsSender>,
) { ) {
if vote.slots.is_empty() { if vote.is_empty() {
return; return;
} }
let last_vote_slot = *vote.slots.last().unwrap(); let (last_vote_slot, last_vote_hash) = vote.last_voted_slot_hash().unwrap();
let last_vote_hash = vote.hash;
let root = root_bank.slot(); let root = root_bank.slot();
let mut is_new_vote = false; let mut is_new_vote = false;
let vote_slots = vote.slots();
// If slot is before the root, ignore it // 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; let slot = *slot;
// if we don't have stake information, ignore it // if we don't have stake information, ignore it
@ -781,28 +784,28 @@ impl ClusterInfoVoteListener {
} }
if is_new_vote { if is_new_vote {
subscriptions.notify_vote(&vote); subscriptions.notify_vote(vote);
let _ = verified_vote_sender.send((*vote_pubkey, vote.slots)); let _ = verified_vote_sender.send((*vote_pubkey, vote_slots));
} }
} }
fn filter_gossip_votes( fn filter_gossip_votes(
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
vote: &Vote, vote: &dyn VoteTransaction,
gossip_tx: &Transaction, gossip_tx: &Transaction,
) -> bool { ) -> bool {
if vote.slots.is_empty() { if vote.is_empty() {
return false; 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 // Votes from gossip need to be verified as they have not been
// verified by the replay pipeline. Determine the authorized voter // verified by the replay pipeline. Determine the authorized voter
// based on the last vote slot. This will drop votes from authorized // based on the last vote slot. This will drop votes from authorized
// voters trying to make votes for slots earlier than the epoch for // voters trying to make votes for slots earlier than the epoch for
// which they are authorized // which they are authorized
let actual_authorized_voter = 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() { if actual_authorized_voter.is_none() {
return false; return false;
@ -822,7 +825,7 @@ impl ClusterInfoVoteListener {
fn filter_and_confirm_with_new_votes( fn filter_and_confirm_with_new_votes(
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
gossip_vote_txs: Vec<Transaction>, gossip_vote_txs: Vec<Transaction>,
replayed_votes: Vec<ReplayedVote>, replayed_votes: Vec<ParsedVote>,
root_bank: &Bank, root_bank: &Bank,
subscriptions: &RpcSubscriptions, subscriptions: &RpcSubscriptions,
gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender, gossip_verified_vote_hash_sender: &GossipVerifiedVoteHashSender,
@ -839,7 +842,7 @@ impl ClusterInfoVoteListener {
.filter_map(|gossip_tx| { .filter_map(|gossip_tx| {
vote_transaction::parse_vote_transaction(gossip_tx) vote_transaction::parse_vote_transaction(gossip_tx)
.filter(|(vote_pubkey, vote, _)| { .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)) .map(|v| (true, v))
}) })
@ -1243,7 +1246,7 @@ mod tests {
replay_votes_sender replay_votes_sender
.send(( .send((
vote_keypair.pubkey(), vote_keypair.pubkey(),
replay_vote.clone(), Box::new(replay_vote.clone()),
switch_proof_hash, switch_proof_hash,
)) ))
.unwrap(); .unwrap();
@ -1490,7 +1493,8 @@ mod tests {
let (votes_sender, votes_receiver) = unbounded(); let (votes_sender, votes_receiver) = unbounded();
let (verified_vote_sender, _verified_vote_receiver) = unbounded(); let (verified_vote_sender, _verified_vote_receiver) = unbounded();
let (gossip_verified_vote_hash_sender, _gossip_verified_vote_hash_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_slot = 1;
let vote_bank_hash = Hash::default(); let vote_bank_hash = Hash::default();
@ -1530,7 +1534,7 @@ mod tests {
replay_votes_sender replay_votes_sender
.send(( .send((
vote_keypair.pubkey(), vote_keypair.pubkey(),
Vote::new(vec![vote_slot], Hash::default()), Box::new(Vote::new(vec![vote_slot], Hash::default())),
switch_proof_hash, switch_proof_hash,
)) ))
.unwrap(); .unwrap();
@ -1676,7 +1680,7 @@ mod tests {
// Add gossip vote for same slot, should not affect outcome // Add gossip vote for same slot, should not affect outcome
vec![( vec![(
validator0_keypairs.vote_keypair.pubkey(), validator0_keypairs.vote_keypair.pubkey(),
Vote::new(vec![voted_slot], Hash::default()), Box::new(Vote::new(vec![voted_slot], Hash::default())),
None, None,
)], )],
&bank, &bank,
@ -1732,7 +1736,7 @@ mod tests {
vote_txs, vote_txs,
vec![( vec![(
validator_keypairs[1].vote_keypair.pubkey(), 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, None,
)], )],
&new_root_bank, &new_root_bank,

View File

@ -21,7 +21,9 @@ use {
}, },
solana_vote_program::{ solana_vote_program::{
vote_instruction, vote_instruction,
vote_state::{BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY}, vote_state::{
BlockTimestamp, Lockout, Vote, VoteState, VoteTransaction, MAX_LOCKOUT_HISTORY,
},
}, },
std::{ std::{
cmp::Ordering, cmp::Ordering,
@ -367,22 +369,16 @@ impl Tower {
) -> Vote { ) -> Vote {
let vote = Vote::new(vec![slot], hash); let vote = Vote::new(vec![slot], hash);
local_vote_state.process_vote_unchecked(&vote); 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 local_vote_state
.votes .votes
.iter() .iter()
.map(|v| v.slot) .map(|v| v.slot)
.skip_while(|s| *s <= last_voted_slot_in_bank) .skip_while(|s| *s <= last_voted_slot)
.collect() .collect()
} else { } else {
local_vote_state.votes.iter().map(|v| v.slot).collect() 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) Vote::new(slots, hash)
} }
@ -415,7 +411,7 @@ impl Tower {
last_voted_slot_in_bank, 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; self.last_vote = new_vote;
let new_root = self.root(); let new_root = self.root();
@ -2252,7 +2248,7 @@ pub mod test {
let mut local = VoteState::default(); let mut local = VoteState::default();
let vote = Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), None); let vote = Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), None);
assert_eq!(local.votes.len(), 1); assert_eq!(local.votes.len(), 1);
assert_eq!(vote.slots, vec![0]); assert_eq!(vote.slots(), vec![0]);
assert_eq!(local.tower(), 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. // another vote for slot 0 should return an empty vote as the diff.
let vote = let vote =
Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), Some(0)); Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), Some(0));
assert!(vote.slots.is_empty()); assert!(vote.is_empty());
} }
#[test] #[test]
@ -2278,7 +2274,7 @@ pub mod test {
assert_eq!(local.votes.len(), 1); assert_eq!(local.votes.len(), 1);
let vote = let vote =
Tower::apply_vote_and_generate_vote_diff(&mut local, 1, Hash::default(), Some(0)); 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]); assert_eq!(local.tower(), vec![0, 1]);
} }
@ -2298,7 +2294,7 @@ pub mod test {
// observable in any of the results. // observable in any of the results.
let vote = let vote =
Tower::apply_vote_and_generate_vote_diff(&mut local, 3, Hash::default(), Some(0)); 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]); assert_eq!(local.tower(), vec![3]);
} }
@ -2380,7 +2376,7 @@ pub mod test {
tower.record_vote(i as u64, Hash::default()); 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) assert_eq!(expected, tower.last_vote)
} }

View File

@ -7,7 +7,7 @@ use {
account::from_account, clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signature, account::from_account, clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signature,
slot_hashes::SlotHashes, sysvar, slot_hashes::SlotHashes, sysvar,
}, },
solana_vote_program::vote_state::Vote, solana_vote_program::vote_state::VoteTransaction,
std::{ std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
sync::Arc, sync::Arc,
@ -19,7 +19,7 @@ const MAX_VOTES_PER_VALIDATOR: usize = 1000;
pub struct VerifiedVoteMetadata { pub struct VerifiedVoteMetadata {
pub vote_account_key: Pubkey, pub vote_account_key: Pubkey,
pub vote: Vote, pub vote: Box<dyn VoteTransaction>,
pub packet: Packets, pub packet: Packets,
pub signature: Signature, pub signature: Signature,
} }
@ -153,15 +153,15 @@ impl VerifiedVotePackets {
packet, packet,
signature, signature,
} = verfied_vote_metadata; } = verfied_vote_metadata;
if vote.slots.is_empty() { if vote.is_empty() {
error!("Empty votes should have been filtered out earlier in the pipeline"); error!("Empty votes should have been filtered out earlier in the pipeline");
continue; continue;
} }
let slot = vote.slots.last().unwrap(); let slot = vote.last_voted_slot().unwrap();
let hash = vote.hash; let hash = vote.hash();
let validator_votes = self.0.entry(vote_account_key).or_default(); 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 { if validator_votes.len() > MAX_VOTES_PER_VALIDATOR {
let smallest_key = validator_votes.keys().next().cloned().unwrap(); let smallest_key = validator_votes.keys().next().cloned().unwrap();
@ -182,6 +182,7 @@ mod tests {
crossbeam_channel::unbounded, crossbeam_channel::unbounded,
solana_perf::packet::Packet, solana_perf::packet::Packet,
solana_sdk::slot_hashes::MAX_ENTRIES, solana_sdk::slot_hashes::MAX_ENTRIES,
solana_vote_program::vote_state::Vote,
}; };
#[test] #[test]
@ -198,7 +199,7 @@ mod tests {
let vote = Vote::new(vec![vote_slot], vote_hash); let vote = Vote::new(vec![vote_slot], vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote: vote.clone(), vote: Box::new(vote.clone()),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new(&[1u8; 64]), signature: Signature::new(&[1u8; 64]),
}]) }])
@ -218,7 +219,7 @@ mod tests {
// Same slot, same hash, should not be inserted // Same slot, same hash, should not be inserted
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new(&[1u8; 64]), signature: Signature::new(&[1u8; 64]),
}]) }])
@ -240,7 +241,7 @@ mod tests {
let vote = Vote::new(vec![vote_slot], new_vote_hash); let vote = Vote::new(vec![vote_slot], new_vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new(&[1u8; 64]), signature: Signature::new(&[1u8; 64]),
}]) }])
@ -263,7 +264,7 @@ mod tests {
let vote = Vote::new(vec![vote_slot], vote_hash); let vote = Vote::new(vec![vote_slot], vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new(&[2u8; 64]), signature: Signature::new(&[2u8; 64]),
}]) }])
@ -302,7 +303,7 @@ mod tests {
let vote = Vote::new(vec![vote_slot], vote_hash); let vote = Vote::new(vec![vote_slot], vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new(&[1u8; 64]), signature: Signature::new(&[1u8; 64]),
}]) }])
@ -339,7 +340,7 @@ mod tests {
let vote = Vote::new(vec![vote_slot], vote_hash); let vote = Vote::new(vec![vote_slot], vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::default(), packet: Packets::default(),
signature: Signature::new_unique(), signature: Signature::new_unique(),
}]) }])
@ -393,7 +394,7 @@ mod tests {
let vote = Vote::new(vec![*vote_slot], *vote_hash); let vote = Vote::new(vec![*vote_slot], *vote_hash);
s.send(vec![VerifiedVoteMetadata { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote: Box::new(vote),
packet: Packets::new(vec![Packet::default(); num_packets]), packet: Packets::new(vec![Packet::default(); num_packets]),
signature: Signature::new_unique(), signature: Signature::new_unique(),
}]) }])
@ -457,7 +458,7 @@ mod tests {
my_leader_bank.slot() + 1, my_leader_bank.slot() + 1,
)); ));
let vote_account_key = vote_simulator.vote_pubkeys[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 { s.send(vec![VerifiedVoteMetadata {
vote_account_key, vote_account_key,
vote, vote,

View File

@ -1042,7 +1042,7 @@ impl ClusterInfo {
"invalid vote index: {}, switch: {}, vote slots: {:?}, tower: {:?}", "invalid vote index: {}, switch: {}, vote slots: {:?}, tower: {:?}",
vote_index, vote_index,
hash.is_some(), hash.is_some(),
vote.slots, vote.slots(),
tower tower
); );
} }

View File

@ -306,8 +306,8 @@ impl Sanitize for Vote {
impl Vote { impl Vote {
pub fn new(from: Pubkey, transaction: Transaction, wallclock: u64) -> Self { pub fn new(from: Pubkey, transaction: Transaction, wallclock: u64) -> Self {
let slot = parse_vote_transaction(&transaction) let slot =
.and_then(|(_, vote, _)| vote.slots.last().copied()); parse_vote_transaction(&transaction).and_then(|(_, vote, _)| vote.last_voted_slot());
Self { Self {
from, from,
transaction, transaction,

View File

@ -2572,7 +2572,7 @@ fn test_duplicate_shreds_broadcast_leader() {
.map(|(_, vote, _)| vote) .map(|(_, vote, _)| vote)
.unwrap(); .unwrap();
// Filter out empty votes // Filter out empty votes
if !vote.slots.is_empty() { if !vote.is_empty() {
Some((vote, leader_vote_tx)) Some((vote, leader_vote_tx))
} else { } else {
None None
@ -2584,14 +2584,16 @@ fn test_duplicate_shreds_broadcast_leader() {
.collect(); .collect();
parsed_vote_iter.sort_by(|(vote, _), (vote2, _)| { parsed_vote_iter.sort_by(|(vote, _), (vote2, _)| {
vote.slots.last().unwrap().cmp(vote2.slots.last().unwrap()) vote.last_voted_slot()
.unwrap()
.cmp(&vote2.last_voted_slot().unwrap())
}); });
for (parsed_vote, leader_vote_tx) in &parsed_vote_iter { for (parsed_vote, leader_vote_tx) in &parsed_vote_iter {
if let Some(latest_vote_slot) = parsed_vote.slots.last() { if let Some(latest_vote_slot) = parsed_vote.last_voted_slot() {
info!("received vote for {}", latest_vote_slot); info!("received vote for {}", latest_vote_slot);
// Add to EpochSlots. Mark all slots frozen between slot..=max_vote_slot. // Add to EpochSlots. Mark all slots frozen between slot..=max_vote_slot.
if *latest_vote_slot > max_vote_slot { if latest_vote_slot > max_vote_slot {
let new_epoch_slots: Vec<Slot> = let new_epoch_slots: Vec<Slot> =
(max_vote_slot + 1..latest_vote_slot + 1).collect(); (max_vote_slot + 1..latest_vote_slot + 1).collect();
info!( info!(
@ -2599,13 +2601,13 @@ fn test_duplicate_shreds_broadcast_leader() {
new_epoch_slots new_epoch_slots
); );
cluster_info.push_epoch_slots(&new_epoch_slots); cluster_info.push_epoch_slots(&new_epoch_slots);
max_vote_slot = *latest_vote_slot; max_vote_slot = latest_vote_slot;
} }
// Only vote on even slots. Note this may violate lockouts if the // Only vote on even slots. Note this may violate lockouts if the
// validator started voting on a different fork before we could exit // validator started voting on a different fork before we could exit
// it above. // it above.
let vote_hash = parsed_vote.hash; let vote_hash = parsed_vote.hash();
if latest_vote_slot % 2 == 0 { if latest_vote_slot % 2 == 0 {
info!( info!(
"Simulating vote from our node on slot {}, hash {}", "Simulating vote from our node on slot {}, hash {}",
@ -2618,7 +2620,7 @@ fn test_duplicate_shreds_broadcast_leader() {
// by this validator so it's fine. // by this validator so it's fine.
let leader_blockstore = open_blockstore(&bad_leader_ledger_path); let leader_blockstore = open_blockstore(&bad_leader_ledger_path);
let mut vote_slots: Vec<Slot> = AncestorIterator::new_inclusive( let mut vote_slots: Vec<Slot> = AncestorIterator::new_inclusive(
*latest_vote_slot, latest_vote_slot,
&leader_blockstore, &leader_blockstore,
) )
.take(MAX_LOCKOUT_HISTORY) .take(MAX_LOCKOUT_HISTORY)

View File

@ -562,6 +562,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "ctor"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
dependencies = [
"quote 1.0.6",
"syn 1.0.67",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "2.1.0" version = "2.1.0"
@ -825,6 +835,15 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "erased-serde"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.2.8"
@ -1062,6 +1081,17 @@ dependencies = [
"wasi 0.10.1+wasi-snapshot-preview1", "wasi 0.10.1+wasi-snapshot-preview1",
] ]
[[package]]
name = "ghost"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.6",
"syn 1.0.67",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.21.0" version = "0.21.0"
@ -1309,6 +1339,16 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "inventory"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1367fed6750ff2a5bcb967a631528303bb85631f167a75eb1bf7762d57eb7678"
dependencies = [
"ctor",
"ghost",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.3.0" version = "2.3.0"
@ -3476,6 +3516,7 @@ dependencies = [
"solana-program-runtime", "solana-program-runtime",
"solana-sdk", "solana-sdk",
"thiserror", "thiserror",
"typetag",
] ]
[[package]] [[package]]
@ -3949,6 +3990,30 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "typetag"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4080564c5b2241b5bff53ab610082234e0c57b0417f4bd10596f183001505b8a"
dependencies = [
"erased-serde",
"inventory",
"once_cell",
"serde",
"typetag-impl",
]
[[package]]
name = "typetag-impl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e60147782cc30833c05fba3bab1d9b5771b2685a2557672ac96fa5d154099c0e"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.6",
"syn 1.0.67",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.4"

View File

@ -22,6 +22,7 @@ solana-logger = { path = "../../logger", version = "=1.10.0" }
solana-metrics = { path = "../../metrics", version = "=1.10.0" } solana-metrics = { path = "../../metrics", version = "=1.10.0" }
solana-program-runtime = { path = "../../program-runtime", version = "=1.10.0" } solana-program-runtime = { path = "../../program-runtime", version = "=1.10.0" }
solana-sdk = { path = "../../sdk", version = "=1.10.0" } solana-sdk = { path = "../../sdk", version = "=1.10.0" }
typetag = "0.1"
thiserror = "1.0" thiserror = "1.0"
[build-dependencies] [build-dependencies]

View File

@ -4,7 +4,7 @@
use { use {
crate::{ crate::{
id, id,
vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState}, vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate},
}, },
log::*, log::*,
num_derive::{FromPrimitive, ToPrimitive}, num_derive::{FromPrimitive, ToPrimitive},
@ -156,6 +156,24 @@ pub enum VoteInstruction {
/// 2. `[SIGNER]` Vote or withdraw authority /// 2. `[SIGNER]` Vote or withdraw authority
/// 3. `[SIGNER]` New vote or withdraw authority /// 3. `[SIGNER]` New vote or withdraw authority
AuthorizeChecked(VoteAuthorize), AuthorizeChecked(VoteAuthorize),
/// Update the onchain vote state for the signer.
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[]` Slot hashes sysvar
/// 2. `[]` Clock sysvar
/// 3. `[SIGNER]` Vote authority
UpdateVoteState(VoteStateUpdate),
/// Update the onchain vote state for the signer along with a switching proof.
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[]` Slot hashes sysvar
/// 2. `[]` Clock sysvar
/// 3. `[SIGNER]` Vote authority
UpdateVoteStateSwitch(VoteStateUpdate, Hash),
} }
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction { fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
@ -313,6 +331,45 @@ pub fn vote_switch(
) )
} }
pub fn update_vote_state(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
vote_state_update: VoteStateUpdate,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
];
Instruction::new_with_bincode(
id(),
&VoteInstruction::UpdateVoteState(vote_state_update),
account_metas,
)
}
pub fn update_vote_state_switch(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
vote_state_update: VoteStateUpdate,
proof_hash: Hash,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
];
Instruction::new_with_bincode(
id(),
&VoteInstruction::UpdateVoteStateSwitch(vote_state_update, proof_hash),
account_metas,
)
}
pub fn withdraw( pub fn withdraw(
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
authorized_withdrawer_pubkey: &Pubkey, authorized_withdrawer_pubkey: &Pubkey,
@ -406,6 +463,23 @@ pub fn process_instruction(
&signers, &signers,
) )
} }
VoteInstruction::UpdateVoteState(vote_state_update)
| VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => {
inc_new_counter_info!("vote-state-native", 1);
vote_state::process_vote_state_update(
me,
&from_keyed_account::<SlotHashes>(keyed_account_at_index(
keyed_accounts,
first_instruction_account + 1,
)?)?,
&from_keyed_account::<Clock>(keyed_account_at_index(
keyed_accounts,
first_instruction_account + 2,
)?)?,
&vote_state_update,
&signers,
)
}
VoteInstruction::Withdraw(lamports) => { VoteInstruction::Withdraw(lamports) => {
let to = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let to = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
let rent_sysvar = if invoke_context let rent_sysvar = if invoke_context
@ -544,6 +618,14 @@ mod tests {
)), )),
Err(InstructionError::InvalidAccountOwner), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!(
process_instruction_as_one_arg(&update_vote_state(
&invalid_vote_state_pubkey(),
&Pubkey::default(),
VoteStateUpdate::default(),
)),
Err(InstructionError::InvalidAccountOwner),
);
} }
#[test] #[test]
@ -553,7 +635,7 @@ mod tests {
&Pubkey::new_unique(), &Pubkey::new_unique(),
&Pubkey::new_unique(), &Pubkey::new_unique(),
&VoteInit::default(), &VoteInit::default(),
100, 101,
); );
assert_eq!( assert_eq!(
process_instruction_as_one_arg(&instructions[1]), process_instruction_as_one_arg(&instructions[1]),
@ -585,6 +667,25 @@ mod tests {
)), )),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
assert_eq!(
process_instruction_as_one_arg(&update_vote_state(
&Pubkey::default(),
&Pubkey::default(),
VoteStateUpdate::default(),
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction_as_one_arg(&update_vote_state_switch(
&Pubkey::default(),
&Pubkey::default(),
VoteStateUpdate::default(),
Hash::default(),
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!( assert_eq!(
process_instruction_as_one_arg(&update_validator_identity( process_instruction_as_one_arg(&update_validator_identity(
&Pubkey::new_unique(), &Pubkey::new_unique(),

View File

@ -19,9 +19,11 @@ use {
sysvar::clock::Clock, sysvar::clock::Clock,
}, },
std::{ std::{
any::Any,
boxed::Box, boxed::Box,
cmp::Ordering, cmp::Ordering,
collections::{HashSet, VecDeque}, collections::{HashSet, VecDeque},
fmt::Debug,
}, },
}; };
@ -36,9 +38,69 @@ pub const INITIAL_LOCKOUT: usize = 2;
// Maximum number of credits history to keep around // Maximum number of credits history to keep around
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64; pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
// Offset of VoteState::prior_voters, for determining initialization status without deserialization // Offset of VoteState::pri : Clone + Debug {or_voters, for determining initialization status without deserialization
const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82; const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82;
// VoteTransactionClone hack is done so that we can derive clone on the tower that uses the
// VoteTransaction trait object. Clone doesn't work here because it returns Self which is not
// allowed for trait objects
#[typetag::serde{tag = "type"}]
pub trait VoteTransaction: VoteTransactionClone + Debug + Send {
fn slot(&self, i: usize) -> Slot;
fn len(&self) -> usize;
fn hash(&self) -> Hash;
fn timestamp(&self) -> Option<UnixTimestamp>;
fn last_voted_slot(&self) -> Option<Slot>;
fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)>;
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>);
fn slots(&self) -> Vec<Slot> {
(0..self.len()).map(|i| self.slot(i)).collect()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
// Have to manually implement because deriving PartialEq returns Self
fn eq(&self, other: &dyn VoteTransaction) -> bool;
fn as_any(&self) -> &dyn Any;
}
pub trait VoteTransactionClone {
fn clone_box(&self) -> Box<dyn VoteTransaction>;
}
impl<T> VoteTransactionClone for T
where
T: VoteTransaction + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn VoteTransaction> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn VoteTransaction> {
fn clone(&self) -> Box<dyn VoteTransaction> {
self.clone_box()
}
}
// Have to manually implement because derive returns Self
impl<'a, 'b> PartialEq<dyn VoteTransaction + 'b> for dyn VoteTransaction + 'a {
fn eq(&self, other: &(dyn VoteTransaction + 'b)) -> bool {
VoteTransaction::eq(self, other)
}
}
// This is needed because of weirdness in the derive PartialEq macro
// See rust issue #31740 for more info
impl PartialEq<&Self> for Box<dyn VoteTransaction> {
fn eq(&self, other: &&Self) -> bool {
VoteTransaction::eq(self.as_ref(), other.as_ref())
}
}
#[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")] #[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
pub struct Vote { pub struct Vote {
@ -58,17 +120,51 @@ impl Vote {
timestamp: None, timestamp: None,
} }
} }
}
pub fn last_voted_slot(&self) -> Option<Slot> { #[typetag::serde]
impl VoteTransaction for Vote {
fn slot(&self, i: usize) -> Slot {
self.slots[i]
}
fn len(&self) -> usize {
self.slots.len()
}
fn hash(&self) -> Hash {
self.hash
}
fn timestamp(&self) -> Option<UnixTimestamp> {
self.timestamp
}
fn last_voted_slot(&self) -> Option<Slot> {
self.slots.last().copied() self.slots.last().copied()
} }
pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> { fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
self.slots.last().copied().map(|slot| (slot, self.hash)) self.slots.last().copied().map(|slot| (slot, self.hash))
} }
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
self.timestamp = ts
}
fn eq(&self, other: &dyn VoteTransaction) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.map_or(false, |x| x == self)
}
fn as_any(&self) -> &dyn Any {
self
}
} }
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Copy, Clone, AbiExample)]
pub struct Lockout { pub struct Lockout {
pub slot: Slot, pub slot: Slot,
pub confirmation_count: u32, pub confirmation_count: u32,
@ -99,6 +195,74 @@ impl Lockout {
} }
} }
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
pub struct VoteStateUpdate {
/// The proposed tower
pub lockouts: VecDeque<Lockout>,
/// The proposed root
pub root: Option<Slot>,
/// signature of the bank's state at the last slot
pub hash: Hash,
/// processing timestamp of last slot
pub timestamp: Option<UnixTimestamp>,
}
impl VoteStateUpdate {
pub fn new(lockouts: VecDeque<Lockout>, root: Option<Slot>, hash: Hash) -> Self {
Self {
lockouts,
root,
hash,
timestamp: None,
}
}
}
#[typetag::serde]
impl VoteTransaction for VoteStateUpdate {
fn slot(&self, i: usize) -> Slot {
self.lockouts[i].slot
}
fn len(&self) -> usize {
self.lockouts.len()
}
fn hash(&self) -> Hash {
self.hash
}
fn timestamp(&self) -> Option<UnixTimestamp> {
self.timestamp
}
fn last_voted_slot(&self) -> Option<Slot> {
self.lockouts.back().copied().map(|lockout| lockout.slot)
}
fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
self.lockouts
.back()
.copied()
.map(|lockout| (lockout.slot, self.hash))
}
fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
self.timestamp = ts
}
fn eq(&self, other: &dyn VoteTransaction) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.map_or(false, |x| x == self)
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
pub struct VoteInit { pub struct VoteInit {
pub node_pubkey: Pubkey, pub node_pubkey: Pubkey,
@ -301,7 +465,7 @@ impl VoteState {
fn check_slots_are_valid( fn check_slots_are_valid(
&self, &self,
vote: &Vote, vote: &(impl VoteTransaction + Debug),
slot_hashes: &[(Slot, Hash)], slot_hashes: &[(Slot, Hash)],
) -> Result<(), VoteError> { ) -> Result<(), VoteError> {
// index into the vote's slots, sarting at the newest // index into the vote's slots, sarting at the newest
@ -320,19 +484,19 @@ impl VoteState {
// //
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
// the oldest/smallest vote // the oldest/smallest vote
while i < vote.slots.len() && j > 0 { while i < vote.len() && j > 0 {
// 1) increment `i` to find the smallest slot `s` in `vote.slots` // 1) increment `i` to find the smallest slot `s` in `vote.slots`
// where `s` >= `last_voted_slot` // where `s` >= `last_voted_slot`
if self if self
.last_voted_slot() .last_voted_slot()
.map_or(false, |last_voted_slot| vote.slots[i] <= last_voted_slot) .map_or(false, |last_voted_slot| vote.slot(i) <= last_voted_slot)
{ {
i += 1; i += 1;
continue; continue;
} }
// 2) Find the hash for this slot `s`. // 2) Find the hash for this slot `s`.
if vote.slots[i] != slot_hashes[j - 1].0 { if vote.slot(i) != slot_hashes[j - 1].0 {
// Decrement `j` to find newer slots // Decrement `j` to find newer slots
j -= 1; j -= 1;
continue; continue;
@ -354,7 +518,7 @@ impl VoteState {
); );
return Err(VoteError::VoteTooOld); return Err(VoteError::VoteTooOld);
} }
if i != vote.slots.len() { if i != vote.len() {
// This means there existed some slot for which we couldn't find // This means there existed some slot for which we couldn't find
// a matching slot hash in step 2) // a matching slot hash in step 2)
info!( info!(
@ -364,13 +528,16 @@ impl VoteState {
inc_new_counter_info!("dropped-vote-slot", 1); inc_new_counter_info!("dropped-vote-slot", 1);
return Err(VoteError::SlotsMismatch); return Err(VoteError::SlotsMismatch);
} }
if slot_hashes[j].1 != vote.hash { if slot_hashes[j].1 != vote.hash() {
// This means the newest vote in the slot has a match that // This means the newest vote in the slot has a match that
// doesn't match the expected hash for that slot on this // doesn't match the expected hash for that slot on this
// fork // fork
warn!( warn!(
"{} dropped vote {:?} failed to match hash {} {}", "{} dropped vote {:?} failed to match hash {} {}",
self.node_pubkey, vote, vote.hash, slot_hashes[j].1 self.node_pubkey,
vote,
vote.hash(),
slot_hashes[j].1
); );
inc_new_counter_info!("dropped-vote-hash", 1); inc_new_counter_info!("dropped-vote-hash", 1);
return Err(VoteError::SlotHashMismatch); return Err(VoteError::SlotHashMismatch);
@ -947,13 +1114,11 @@ pub fn initialize_account<S: std::hash::BuildHasher>(
))) )))
} }
pub fn process_vote<S: std::hash::BuildHasher>( fn verify_and_get_vote_state<S: std::hash::BuildHasher>(
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
slot_hashes: &[SlotHash],
clock: &Clock, clock: &Clock,
vote: &Vote,
signers: &HashSet<Pubkey, S>, signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> { ) -> Result<VoteState, InstructionError> {
let versioned = State::<VoteStateVersions>::state(vote_account)?; let versioned = State::<VoteStateVersions>::state(vote_account)?;
if versioned.is_uninitialized() { if versioned.is_uninitialized() {
@ -964,6 +1129,18 @@ pub fn process_vote<S: std::hash::BuildHasher>(
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?; let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
verify_authorized_signer(&authorized_voter, signers)?; verify_authorized_signer(&authorized_voter, signers)?;
Ok(vote_state)
}
pub fn process_vote<S: std::hash::BuildHasher>(
vote_account: &KeyedAccount,
slot_hashes: &[SlotHash],
clock: &Clock,
vote: &Vote,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
vote_state.process_vote(vote, slot_hashes, clock.epoch)?; vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
if let Some(timestamp) = vote.timestamp { if let Some(timestamp) = vote.timestamp {
vote.slots vote.slots
@ -975,6 +1152,25 @@ pub fn process_vote<S: std::hash::BuildHasher>(
vote_account.set_state(&VoteStateVersions::new_current(vote_state)) vote_account.set_state(&VoteStateVersions::new_current(vote_state))
} }
pub fn process_vote_state_update<S: std::hash::BuildHasher>(
vote_account: &KeyedAccount,
slot_hashes: &[SlotHash],
clock: &Clock,
vote_state_update: &VoteStateUpdate,
signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
vote_state.check_slots_are_valid(vote_state_update, slot_hashes)?;
vote_state.process_new_vote_state(
vote_state_update.lockouts.clone(),
vote_state_update.root,
vote_state_update.timestamp,
clock.epoch,
)?;
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}
pub fn create_account_with_authorized( pub fn create_account_with_authorized(
node_pubkey: &Pubkey, node_pubkey: &Pubkey,
authorized_voter: &Pubkey, authorized_voter: &Pubkey,
@ -1194,13 +1390,12 @@ mod tests {
vote_state vote_state
.votes .votes
.resize(MAX_LOCKOUT_HISTORY, Lockout::default()); .resize(MAX_LOCKOUT_HISTORY, Lockout::default());
vote_state.root_slot = Some(1);
let versioned = VoteStateVersions::new_current(vote_state); let versioned = VoteStateVersions::new_current(vote_state);
assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err()); assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err());
VoteState::serialize(&versioned, &mut buffer).unwrap(); VoteState::serialize(&versioned, &mut buffer).unwrap();
assert_eq!( let des = VoteState::deserialize(&buffer).unwrap();
VoteStateVersions::new_current(VoteState::deserialize(&buffer).unwrap()), assert_eq!(des, versioned.convert_to_current(),);
versioned
);
} }
#[test] #[test]

View File

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
vote_instruction::{self, VoteInstruction}, vote_instruction::{self, VoteInstruction},
vote_state::Vote, vote_state::{Vote, VoteTransaction},
}, },
solana_sdk::{ solana_sdk::{
clock::Slot, clock::Slot,
@ -14,14 +14,30 @@ use {
}, },
}; };
pub type ParsedVote = (Pubkey, Vote, Option<Hash>); pub type ParsedVote = (Pubkey, Box<dyn VoteTransaction>, Option<Hash>);
fn parse_vote(vote_ix: &CompiledInstruction, vote_key: &Pubkey) -> Option<ParsedVote> { fn parse_vote(vote_ix: &CompiledInstruction, vote_key: &Pubkey) -> Option<ParsedVote> {
let vote_instruction = limited_deserialize(&vote_ix.data).ok(); let vote_instruction = limited_deserialize(&vote_ix.data).ok();
vote_instruction.and_then(|vote_instruction| match vote_instruction { vote_instruction.and_then(|vote_instruction| {
VoteInstruction::Vote(vote) => Some((*vote_key, vote, None)), let result: Option<ParsedVote> = match vote_instruction {
VoteInstruction::VoteSwitch(vote, hash) => Some((*vote_key, vote, Some(hash))), VoteInstruction::Vote(vote) => Some((*vote_key, Box::new(vote), None)),
_ => None, VoteInstruction::VoteSwitch(vote, hash) => {
Some((*vote_key, Box::new(vote), Some(hash)))
}
VoteInstruction::UpdateVoteState(vote_state_update) => {
Some((*vote_key, Box::new(vote_state_update), None))
}
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
Some((*vote_key, Box::new(vote_state_update), Some(hash)))
}
VoteInstruction::Authorize(_, _)
| VoteInstruction::AuthorizeChecked(_)
| VoteInstruction::InitializeAccount(_)
| VoteInstruction::UpdateCommission(_)
| VoteInstruction::UpdateValidatorIdentity
| VoteInstruction::Withdraw(_) => None,
};
result
}) })
} }
@ -123,7 +139,10 @@ mod test {
); );
let (key, vote, hash) = parse_vote_transaction(&vote_tx).unwrap(); let (key, vote, hash) = parse_vote_transaction(&vote_tx).unwrap();
assert_eq!(hash, input_hash); assert_eq!(hash, input_hash);
assert_eq!(vote, Vote::new(vec![42], bank_hash)); assert_eq!(
*vote.as_any().downcast_ref::<Vote>().unwrap(),
Vote::new(vec![42], bank_hash)
);
assert_eq!(key, vote_keypair.pubkey()); assert_eq!(key, vote_keypair.pubkey());
// Test bad program id fails // Test bad program id fails

View File

@ -1213,7 +1213,7 @@ mod tests {
hash: Hash::default(), hash: Hash::default(),
timestamp: None, timestamp: None,
}; };
subscriptions.notify_vote(&vote); subscriptions.notify_vote(Box::new(vote));
let response = receiver.recv(); let response = receiver.recv();
assert_eq!( assert_eq!(

View File

@ -37,7 +37,7 @@ use {
timing::timestamp, timing::timestamp,
transaction, transaction,
}, },
solana_vote_program::vote_state::Vote, solana_vote_program::vote_state::VoteTransaction,
std::{ std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
@ -90,7 +90,7 @@ impl From<NotificationEntry> for TimestampedNotificationEntry {
pub enum NotificationEntry { pub enum NotificationEntry {
Slot(SlotInfo), Slot(SlotInfo),
SlotUpdate(SlotUpdate), SlotUpdate(SlotUpdate),
Vote(Vote), Vote(Box<dyn VoteTransaction>),
Root(Slot), Root(Slot),
Bank(CommitmentSlots), Bank(CommitmentSlots),
Gossip(Slot), Gossip(Slot),
@ -612,7 +612,7 @@ impl RpcSubscriptions {
self.enqueue_notification(NotificationEntry::SignaturesReceived(slot_signatures)); self.enqueue_notification(NotificationEntry::SignaturesReceived(slot_signatures));
} }
pub fn notify_vote(&self, vote: &Vote) { pub fn notify_vote(&self, vote: Box<dyn VoteTransaction>) {
self.enqueue_notification(NotificationEntry::Vote(vote.clone())); self.enqueue_notification(NotificationEntry::Vote(vote.clone()));
} }
@ -695,10 +695,9 @@ impl RpcSubscriptions {
// in VoteState's from bank states built in ReplayStage. // in VoteState's from bank states built in ReplayStage.
NotificationEntry::Vote(ref vote_info) => { NotificationEntry::Vote(ref vote_info) => {
let rpc_vote = RpcVote { let rpc_vote = RpcVote {
// TODO: Remove clones slots: vote_info.slots(),
slots: vote_info.slots.clone(), hash: bs58::encode(vote_info.hash()).into_string(),
hash: bs58::encode(vote_info.hash).into_string(), timestamp: vote_info.timestamp(),
timestamp: vote_info.timestamp,
}; };
if let Some(sub) = subscriptions if let Some(sub) = subscriptions
.node_progress_watchers() .node_progress_watchers()

View File

@ -6146,7 +6146,10 @@ pub fn is_simple_vote_transaction(transaction: &SanitizedTransaction) -> bool {
{ {
return matches!( return matches!(
vote_instruction, vote_instruction,
VoteInstruction::Vote(_) | VoteInstruction::VoteSwitch(_, _) VoteInstruction::Vote(_)
| VoteInstruction::VoteSwitch(_, _)
| VoteInstruction::UpdateVoteState(_)
| VoteInstruction::UpdateVoteStateSwitch(_, _)
); );
} }
} }

View File

@ -48,7 +48,7 @@ pub fn find_and_send_votes(
if let Some(parsed_vote) = if let Some(parsed_vote) =
vote_transaction::parse_sanitized_vote_transaction(tx) vote_transaction::parse_sanitized_vote_transaction(tx)
{ {
if parsed_vote.1.slots.last().is_some() { if parsed_vote.1.last_voted_slot().is_some() {
let _ = vote_sender.send(parsed_vote); let _ = vote_sender.send(parsed_vote);
} }
} }

View File

@ -1,9 +1,7 @@
use { use {
crossbeam_channel::{Receiver, Sender}, crossbeam_channel::{Receiver, Sender},
solana_sdk::{hash::Hash, pubkey::Pubkey}, solana_vote_program::vote_transaction::ParsedVote,
solana_vote_program::vote_state::Vote,
}; };
pub type ReplayedVote = (Pubkey, Vote, Option<Hash>); pub type ReplayVoteSender = Sender<ParsedVote>;
pub type ReplayVoteSender = Sender<ReplayedVote>; pub type ReplayVoteReceiver = Receiver<ParsedVote>;
pub type ReplayVoteReceiver = Receiver<ReplayedVote>;

View File

@ -70,6 +70,45 @@ pub fn parse_vote(
}), }),
}) })
} }
VoteInstruction::UpdateVoteState(vote_state_update) => {
check_num_vote_accounts(&instruction.accounts, 4)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
"hash": vote_state_update.hash.to_string(),
"timestamp": vote_state_update.timestamp,
});
Ok(ParsedInstructionEnum {
instruction_type: "updatevotestate".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"slotHashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
"clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
"voteStateUpdate": vote_state_update,
}),
})
}
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
check_num_vote_accounts(&instruction.accounts, 4)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
"hash": vote_state_update.hash.to_string(),
"timestamp": vote_state_update.timestamp,
});
Ok(ParsedInstructionEnum {
instruction_type: "updatevotestateswitch".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"slotHashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
"clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
"voteStateUpdate": vote_state_update,
"hash": hash.to_string(),
}),
})
}
VoteInstruction::Withdraw(lamports) => { VoteInstruction::Withdraw(lamports) => {
check_num_vote_accounts(&instruction.accounts, 3)?; check_num_vote_accounts(&instruction.accounts, 3)?;
Ok(ParsedInstructionEnum { Ok(ParsedInstructionEnum {