diff --git a/core/src/cluster_info_vote_listener.rs b/core/src/cluster_info_vote_listener.rs index b546c6a83..c4da64334 100644 --- a/core/src/cluster_info_vote_listener.rs +++ b/core/src/cluster_info_vote_listener.rs @@ -25,8 +25,13 @@ use { rpc_subscriptions::RpcSubscriptions, }, solana_runtime::{ - bank::Bank, bank_forks::BankForks, commitment::VOTE_THRESHOLD_SIZE, - epoch_stakes::EpochStakes, vote_sender_types::ReplayVoteReceiver, + bank::Bank, + bank_forks::BankForks, + commitment::VOTE_THRESHOLD_SIZE, + epoch_stakes::EpochStakes, + vote_parser::{self, ParsedVote}, + vote_sender_types::ReplayVoteReceiver, + vote_transaction::VoteTransaction, }, solana_sdk::{ clock::{Slot, DEFAULT_MS_PER_SLOT, DEFAULT_TICKS_PER_SLOT}, @@ -36,10 +41,6 @@ use { slot_hashes, transaction::Transaction, }, - solana_vote_program::{ - vote_state::VoteTransaction, - vote_transaction::{self, ParsedVote}, - }, std::{ collections::{HashMap, HashSet}, iter::repeat, @@ -308,7 +309,7 @@ impl ClusterInfoVoteListener { !packet_batch.packets[0].meta.discard() }) .filter_map(|(tx, packet_batch)| { - let (vote_account_key, vote, _) = vote_transaction::parse_vote_transaction(&tx)?; + let (vote_account_key, vote, _) = vote_parser::parse_vote_transaction(&tx)?; let slot = vote.last_voted_slot()?; let epoch = epoch_schedule.get_epoch(slot); let authorized_voter = root_bank @@ -705,7 +706,7 @@ impl ClusterInfoVoteListener { // Process votes from gossip and ReplayStage let votes = gossip_vote_txs .iter() - .filter_map(vote_transaction::parse_vote_transaction) + .filter_map(vote_parser::parse_vote_transaction) .zip(repeat(/*is_gossip:*/ true)) .chain(replayed_votes.into_iter().zip(repeat(/*is_gossip:*/ false))); for ((vote_pubkey, vote, _), is_gossip) in votes { @@ -823,7 +824,7 @@ mod tests { pubkey::Pubkey, signature::{Keypair, Signature, Signer}, }, - solana_vote_program::vote_state::Vote, + solana_vote_program::{vote_state::Vote, vote_transaction}, std::{ collections::BTreeSet, iter::repeat_with, diff --git a/core/src/verified_vote_packets.rs b/core/src/verified_vote_packets.rs index ff8eefe48..75e0a0525 100644 --- a/core/src/verified_vote_packets.rs +++ b/core/src/verified_vote_packets.rs @@ -1,12 +1,11 @@ use { crate::{cluster_info_vote_listener::VerifiedLabelVotePacketsReceiver, result::Result}, solana_perf::packet::PacketBatch, - solana_runtime::bank::Bank, + solana_runtime::{bank::Bank, vote_transaction::VoteTransaction}, solana_sdk::{ account::from_account, clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signature, slot_hashes::SlotHashes, sysvar, }, - solana_vote_program::vote_state::VoteTransaction, std::{ collections::{BTreeMap, HashMap, HashSet}, sync::Arc, diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index 0268c73ee..b266311f7 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -53,7 +53,7 @@ use { }, }, solana_rayon_threadlimit::get_thread_count, - solana_runtime::bank_forks::BankForks, + solana_runtime::{bank_forks::BankForks, vote_parser}, solana_sdk::{ clock::{Slot, DEFAULT_MS_PER_SLOT, DEFAULT_SLOTS_PER_EPOCH}, feature_set::FeatureSet, @@ -70,9 +70,7 @@ use { socket::SocketAddrSpace, streamer::{PacketBatchReceiver, PacketBatchSender}, }, - solana_vote_program::{ - vote_state::MAX_LOCKOUT_HISTORY, vote_transaction::parse_vote_transaction, - }, + solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY, std::{ borrow::Cow, collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, @@ -1037,7 +1035,7 @@ impl ClusterInfo { }; let vote_index = vote_index.unwrap_or(num_crds_votes); if (vote_index as usize) >= MAX_LOCKOUT_HISTORY { - let (_, vote, hash) = parse_vote_transaction(&vote).unwrap(); + let (_, vote, hash) = vote_parser::parse_vote_transaction(&vote).unwrap(); panic!( "invalid vote index: {}, switch: {}, vote slots: {:?}, tower: {:?}", vote_index, diff --git a/gossip/src/crds_value.rs b/gossip/src/crds_value.rs index 20906a63d..57b6b9ab7 100644 --- a/gossip/src/crds_value.rs +++ b/gossip/src/crds_value.rs @@ -9,6 +9,7 @@ use { bincode::{serialize, serialized_size}, rand::{CryptoRng, Rng}, serde::de::{Deserialize, Deserializer}, + solana_runtime::vote_parser, solana_sdk::{ clock::Slot, hash::Hash, @@ -18,7 +19,6 @@ use { timing::timestamp, transaction::Transaction, }, - solana_vote_program::vote_transaction::parse_vote_transaction, std::{ borrow::{Borrow, Cow}, cmp::Ordering, @@ -307,7 +307,7 @@ impl Sanitize for Vote { impl Vote { // Returns None if cannot parse transaction into a vote. pub fn new(from: Pubkey, transaction: Transaction, wallclock: u64) -> Option { - parse_vote_transaction(&transaction).map(|(_, vote, _)| Self { + vote_parser::parse_vote_transaction(&transaction).map(|(_, vote, _)| Self { from, transaction, wallclock, diff --git a/local-cluster/tests/local_cluster_slow.rs b/local-cluster/tests/local_cluster_slow.rs index 7ebe8e92f..83ba94fa6 100644 --- a/local-cluster/tests/local_cluster_slow.rs +++ b/local-cluster/tests/local_cluster_slow.rs @@ -28,6 +28,7 @@ use { local_cluster::{ClusterConfig, LocalCluster}, validator_configs::*, }, + solana_runtime::vote_parser, solana_sdk::{ clock::{Slot, MAX_PROCESSING_AGE}, hash::Hash, @@ -499,7 +500,7 @@ fn test_duplicate_shreds_broadcast_leader() { .filter_map(|(label, leader_vote_tx)| { // Filter out votes not from the bad leader if label.pubkey() == bad_leader_id { - let vote = vote_transaction::parse_vote_transaction(&leader_vote_tx) + let vote = vote_parser::parse_vote_transaction(&leader_vote_tx) .map(|(_, vote, _)| vote) .unwrap(); // Filter out empty votes diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 255c7c382..d9ebfb337 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -41,73 +41,6 @@ pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64; // Offset of VoteState::prior_voters, for determining initialization status without deserialization const DEFAULT_PRIOR_VOTERS_OFFSET: usize = 82; -#[derive(Debug, PartialEq)] -pub enum VoteTransaction { - Vote(Vote), - VoteStateUpdate(VoteStateUpdate), -} - -impl VoteTransaction { - pub fn slots(&self) -> Vec { - match self { - VoteTransaction::Vote(vote) => vote.slots.clone(), - VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update - .lockouts - .iter() - .map(|lockout| lockout.slot) - .collect(), - } - } - - pub fn is_empty(&self) -> bool { - match self { - VoteTransaction::Vote(vote) => vote.slots.is_empty(), - VoteTransaction::VoteStateUpdate(vote_state_update) => { - vote_state_update.lockouts.is_empty() - } - } - } - - pub fn hash(&self) -> Hash { - match self { - VoteTransaction::Vote(vote) => vote.hash, - VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash, - } - } - - pub fn timestamp(&self) -> Option { - match self { - VoteTransaction::Vote(vote) => vote.timestamp, - VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp, - } - } - - pub fn last_voted_slot(&self) -> Option { - match self { - VoteTransaction::Vote(vote) => vote.slots.last().copied(), - VoteTransaction::VoteStateUpdate(vote_state_update) => { - Some(vote_state_update.lockouts.back()?.slot) - } - } - } - - pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> { - Some((self.last_voted_slot()?, self.hash())) - } -} - -impl From for VoteTransaction { - fn from(vote: Vote) -> Self { - VoteTransaction::Vote(vote) - } -} - -impl From for VoteTransaction { - fn from(vote_state_update: VoteStateUpdate) -> Self { - VoteTransaction::VoteStateUpdate(vote_state_update) - } -} - #[frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH")] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)] pub struct Vote { diff --git a/programs/vote/src/vote_transaction.rs b/programs/vote/src/vote_transaction.rs index 99eb7a9b2..cf84ebc01 100644 --- a/programs/vote/src/vote_transaction.rs +++ b/programs/vote/src/vote_transaction.rs @@ -1,68 +1,13 @@ use { - crate::{ - vote_instruction::{self, VoteInstruction}, - vote_state::{Vote, VoteTransaction}, - }, + crate::{vote_instruction, vote_state::Vote}, solana_sdk::{ clock::Slot, hash::Hash, - instruction::CompiledInstruction, - program_utils::limited_deserialize, - pubkey::Pubkey, signature::{Keypair, Signer}, - transaction::{SanitizedTransaction, Transaction}, + transaction::Transaction, }, }; -pub type ParsedVote = (Pubkey, VoteTransaction, Option); - -fn parse_vote(vote: &CompiledInstruction) -> Option<(VoteTransaction, Option)> { - match limited_deserialize(&vote.data).ok()? { - VoteInstruction::Vote(vote) => Some((VoteTransaction::from(vote), None)), - VoteInstruction::VoteSwitch(vote, hash) => Some((VoteTransaction::from(vote), Some(hash))), - VoteInstruction::UpdateVoteState(vote_state_update) => { - Some((VoteTransaction::from(vote_state_update), None)) - } - VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => { - Some((VoteTransaction::from(vote_state_update), Some(hash))) - } - VoteInstruction::Authorize(_, _) - | VoteInstruction::AuthorizeChecked(_) - | VoteInstruction::InitializeAccount(_) - | VoteInstruction::UpdateCommission(_) - | VoteInstruction::UpdateValidatorIdentity - | VoteInstruction::Withdraw(_) => None, - } -} - -pub fn parse_sanitized_vote_transaction(tx: &SanitizedTransaction) -> Option { - // Check first instruction for a vote - let message = tx.message(); - let (program_id, first_instruction) = message.program_instructions_iter().next()?; - if !crate::check_id(program_id) { - return None; - } - let first_account = usize::from(*first_instruction.accounts.first()?); - let key = message.get_account_key(first_account)?; - let (vote, switch_proof_hash) = parse_vote(first_instruction)?; - Some((*key, vote, switch_proof_hash)) -} - -pub fn parse_vote_transaction(tx: &Transaction) -> Option { - // Check first instruction for a vote - let message = tx.message(); - let first_instruction = message.instructions.first()?; - let program_id_index = usize::from(first_instruction.program_id_index); - let program_id = message.account_keys.get(program_id_index)?; - if !crate::check_id(program_id) { - return None; - } - let first_account = usize::from(*first_instruction.accounts.first()?); - let key = message.account_keys.get(first_account)?; - let (vote, switch_proof_hash) = parse_vote(first_instruction)?; - Some((*key, vote, switch_proof_hash)) -} - pub fn new_vote_transaction( slots: Vec, bank_hash: Hash, @@ -94,44 +39,3 @@ pub fn new_vote_transaction( vote_tx.partial_sign(&[authorized_voter_keypair], blockhash); vote_tx } - -#[cfg(test)] -mod test { - use {super::*, solana_sdk::hash::hash}; - - fn run_test_parse_vote_transaction(input_hash: Option) { - let node_keypair = Keypair::new(); - let vote_keypair = Keypair::new(); - let auth_voter_keypair = Keypair::new(); - let bank_hash = Hash::default(); - let vote_tx = new_vote_transaction( - vec![42], - bank_hash, - Hash::default(), - &node_keypair, - &vote_keypair, - &auth_voter_keypair, - input_hash, - ); - let (key, vote, hash) = parse_vote_transaction(&vote_tx).unwrap(); - assert_eq!(hash, input_hash); - assert_eq!(vote, VoteTransaction::from(Vote::new(vec![42], bank_hash))); - assert_eq!(key, vote_keypair.pubkey()); - - // Test bad program id fails - let mut vote_ix = vote_instruction::vote( - &vote_keypair.pubkey(), - &auth_voter_keypair.pubkey(), - Vote::new(vec![1, 2], Hash::default()), - ); - vote_ix.program_id = Pubkey::default(); - let vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey())); - assert!(parse_vote_transaction(&vote_tx).is_none()); - } - - #[test] - fn test_parse_vote_transaction() { - run_test_parse_vote_transaction(None); - run_test_parse_vote_transaction(Some(hash(&[42u8]))); - } -} diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index 09e8fbd87..afbfaa176 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -594,6 +594,7 @@ mod tests { create_genesis_config, create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs, }, + vote_transaction::VoteTransaction, }, solana_sdk::{ account::ReadableAccount, @@ -611,7 +612,7 @@ mod tests { transaction::{self, Transaction}, }, solana_stake_program::stake_state, - solana_vote_program::vote_state::{Vote, VoteTransaction}, + solana_vote_program::vote_state::Vote, std::{ sync::{ atomic::{AtomicBool, AtomicU64}, diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 4982a272e..26b159e76 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -30,6 +30,7 @@ use { bank::{Bank, TransactionLogInfo}, bank_forks::BankForks, commitment::{BlockCommitmentCache, CommitmentSlots}, + vote_transaction::VoteTransaction, }, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, @@ -40,7 +41,6 @@ use { transaction, }, solana_transaction_status::ConfirmedBlock, - solana_vote_program::vote_state::VoteTransaction, std::{ cell::RefCell, collections::{HashMap, VecDeque}, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5a04f6417..a59e94c5a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -61,6 +61,7 @@ use { system_instruction_processor::{get_system_account_kind, SystemAccountKind}, transaction_batch::TransactionBatch, vote_account::VoteAccount, + vote_parser, }, byteorder::{ByteOrder, LittleEndian}, dashmap::DashMap, @@ -117,7 +118,6 @@ use { nonce, nonce_account, packet::PACKET_DATA_SIZE, precompiles::get_precompiles, - program_utils::limited_deserialize, pubkey::Pubkey, saturating_add_assign, secp256k1_program, signature::{Keypair, Signature}, @@ -135,10 +135,7 @@ use { solana_stake_program::stake_state::{ self, InflationPointCalculationEvent, PointValue, StakeState, }, - solana_vote_program::{ - vote_instruction::VoteInstruction, - vote_state::{VoteState, VoteStateVersions}, - }, + solana_vote_program::vote_state::{VoteState, VoteStateVersions}, std::{ borrow::Cow, cell::RefCell, @@ -3912,7 +3909,7 @@ impl Bank { } } - let is_vote = is_simple_vote_transaction(tx); + let is_vote = vote_parser::is_simple_vote_transaction(tx); let store = match transaction_log_collector_config.filter { TransactionLogCollectorFilter::All => { !is_vote || !filtered_mentioned_addresses.is_empty() @@ -6409,29 +6406,6 @@ pub fn goto_end_of_slot(bank: &mut Bank) { } } -fn is_simple_vote_transaction(transaction: &SanitizedTransaction) -> bool { - if transaction.message().instructions().len() == 1 { - let (program_pubkey, instruction) = transaction - .message() - .program_instructions_iter() - .next() - .unwrap(); - if program_pubkey == &solana_vote_program::id() { - if let Ok(vote_instruction) = limited_deserialize::(&instruction.data) - { - return matches!( - vote_instruction, - VoteInstruction::Vote(_) - | VoteInstruction::VoteSwitch(_, _) - | VoteInstruction::UpdateVoteState(_) - | VoteInstruction::UpdateVoteStateSwitch(_, _) - ); - } - } - } - false -} - #[cfg(test)] pub(crate) mod tests { #[allow(deprecated)] diff --git a/runtime/src/bank_utils.rs b/runtime/src/bank_utils.rs index f27cca400..ba9d20d2e 100644 --- a/runtime/src/bank_utils.rs +++ b/runtime/src/bank_utils.rs @@ -2,10 +2,10 @@ use { crate::{ bank::{Bank, TransactionResults}, genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs}, + vote_parser, vote_sender_types::ReplayVoteSender, }, solana_sdk::{pubkey::Pubkey, signature::Signer, transaction::SanitizedTransaction}, - solana_vote_program::vote_transaction, }; pub fn setup_bank_and_vote_pubkeys_for_tests( @@ -45,9 +45,7 @@ pub fn find_and_send_votes( .zip(execution_results.iter()) .for_each(|(tx, result)| { if tx.is_simple_vote_transaction() && result.was_executed_successfully() { - if let Some(parsed_vote) = - vote_transaction::parse_sanitized_vote_transaction(tx) - { + if let Some(parsed_vote) = vote_parser::parse_sanitized_vote_transaction(tx) { if parsed_vote.1.last_voted_slot().is_some() { let _ = vote_sender.send(parsed_vote); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50039a8b4..dd9d5df3d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -58,7 +58,9 @@ mod system_instruction_processor; pub mod transaction_batch; pub mod transaction_cost_metrics_sender; pub mod vote_account; +pub mod vote_parser; pub mod vote_sender_types; +pub mod vote_transaction; pub mod waitable_condvar; #[macro_use] diff --git a/runtime/src/vote_parser.rs b/runtime/src/vote_parser.rs new file mode 100644 index 000000000..fa04f216b --- /dev/null +++ b/runtime/src/vote_parser.rs @@ -0,0 +1,133 @@ +use { + crate::vote_transaction::VoteTransaction, + solana_sdk::{ + hash::Hash, + program_utils::limited_deserialize, + pubkey::Pubkey, + transaction::{SanitizedTransaction, Transaction}, + }, + solana_vote_program::vote_instruction::VoteInstruction, +}; + +pub type ParsedVote = (Pubkey, VoteTransaction, Option); + +// Used for filtering out votes from the transaction log collector +pub(crate) fn is_simple_vote_transaction(transaction: &SanitizedTransaction) -> bool { + if transaction.message().instructions().len() == 1 { + let (program_pubkey, instruction) = transaction + .message() + .program_instructions_iter() + .next() + .unwrap(); + if program_pubkey == &solana_vote_program::id() { + if let Ok(vote_instruction) = limited_deserialize::(&instruction.data) + { + return matches!( + vote_instruction, + VoteInstruction::Vote(_) + | VoteInstruction::VoteSwitch(_, _) + | VoteInstruction::UpdateVoteState(_) + | VoteInstruction::UpdateVoteStateSwitch(_, _) + ); + } + } + } + false +} + +// Used for locally forwarding processed vote transactions to consensus +pub fn parse_sanitized_vote_transaction(tx: &SanitizedTransaction) -> Option { + // Check first instruction for a vote + let message = tx.message(); + let (program_id, first_instruction) = message.program_instructions_iter().next()?; + if !solana_vote_program::check_id(program_id) { + return None; + } + let first_account = usize::from(*first_instruction.accounts.first()?); + let key = message.get_account_key(first_account)?; + let (vote, switch_proof_hash) = parse_vote_instruction_data(&first_instruction.data)?; + Some((*key, vote, switch_proof_hash)) +} + +// Used for parsing gossip vote transactions +pub fn parse_vote_transaction(tx: &Transaction) -> Option { + // Check first instruction for a vote + let message = tx.message(); + let first_instruction = message.instructions.first()?; + let program_id_index = usize::from(first_instruction.program_id_index); + let program_id = message.account_keys.get(program_id_index)?; + if !solana_vote_program::check_id(program_id) { + return None; + } + let first_account = usize::from(*first_instruction.accounts.first()?); + let key = message.account_keys.get(first_account)?; + let (vote, switch_proof_hash) = parse_vote_instruction_data(&first_instruction.data)?; + Some((*key, vote, switch_proof_hash)) +} + +fn parse_vote_instruction_data( + vote_instruction_data: &[u8], +) -> Option<(VoteTransaction, Option)> { + match limited_deserialize(vote_instruction_data).ok()? { + VoteInstruction::Vote(vote) => Some((VoteTransaction::from(vote), None)), + VoteInstruction::VoteSwitch(vote, hash) => Some((VoteTransaction::from(vote), Some(hash))), + VoteInstruction::UpdateVoteState(vote_state_update) => { + Some((VoteTransaction::from(vote_state_update), None)) + } + VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => { + Some((VoteTransaction::from(vote_state_update), Some(hash))) + } + VoteInstruction::Authorize(_, _) + | VoteInstruction::AuthorizeChecked(_) + | VoteInstruction::InitializeAccount(_) + | VoteInstruction::UpdateCommission(_) + | VoteInstruction::UpdateValidatorIdentity + | VoteInstruction::Withdraw(_) => None, + } +} + +#[cfg(test)] +mod test { + use solana_sdk::signature::{Keypair, Signer}; + use solana_vote_program::{ + vote_instruction, vote_state::Vote, vote_transaction::new_vote_transaction, + }; + + use {super::*, solana_sdk::hash::hash}; + + fn run_test_parse_vote_transaction(input_hash: Option) { + let node_keypair = Keypair::new(); + let vote_keypair = Keypair::new(); + let auth_voter_keypair = Keypair::new(); + let bank_hash = Hash::default(); + let vote_tx = new_vote_transaction( + vec![42], + bank_hash, + Hash::default(), + &node_keypair, + &vote_keypair, + &auth_voter_keypair, + input_hash, + ); + let (key, vote, hash) = parse_vote_transaction(&vote_tx).unwrap(); + assert_eq!(hash, input_hash); + assert_eq!(vote, VoteTransaction::from(Vote::new(vec![42], bank_hash))); + assert_eq!(key, vote_keypair.pubkey()); + + // Test bad program id fails + let mut vote_ix = vote_instruction::vote( + &vote_keypair.pubkey(), + &auth_voter_keypair.pubkey(), + Vote::new(vec![1, 2], Hash::default()), + ); + vote_ix.program_id = Pubkey::default(); + let vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey())); + assert!(parse_vote_transaction(&vote_tx).is_none()); + } + + #[test] + fn test_parse_vote_transaction() { + run_test_parse_vote_transaction(None); + run_test_parse_vote_transaction(Some(hash(&[42u8]))); + } +} diff --git a/runtime/src/vote_sender_types.rs b/runtime/src/vote_sender_types.rs index 339ecc573..c6644bd42 100644 --- a/runtime/src/vote_sender_types.rs +++ b/runtime/src/vote_sender_types.rs @@ -1,6 +1,6 @@ use { + crate::vote_parser::ParsedVote, crossbeam_channel::{Receiver, Sender}, - solana_vote_program::vote_transaction::ParsedVote, }; pub type ReplayVoteSender = Sender; diff --git a/runtime/src/vote_transaction.rs b/runtime/src/vote_transaction.rs new file mode 100644 index 000000000..c075ecc3d --- /dev/null +++ b/runtime/src/vote_transaction.rs @@ -0,0 +1,74 @@ +use { + solana_sdk::{ + clock::{Slot, UnixTimestamp}, + hash::Hash, + }, + solana_vote_program::vote_state::{Vote, VoteStateUpdate}, +}; + +#[derive(Debug, PartialEq)] +pub enum VoteTransaction { + Vote(Vote), + VoteStateUpdate(VoteStateUpdate), +} + +impl VoteTransaction { + pub fn slots(&self) -> Vec { + match self { + VoteTransaction::Vote(vote) => vote.slots.clone(), + VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update + .lockouts + .iter() + .map(|lockout| lockout.slot) + .collect(), + } + } + + pub fn is_empty(&self) -> bool { + match self { + VoteTransaction::Vote(vote) => vote.slots.is_empty(), + VoteTransaction::VoteStateUpdate(vote_state_update) => { + vote_state_update.lockouts.is_empty() + } + } + } + + pub fn hash(&self) -> Hash { + match self { + VoteTransaction::Vote(vote) => vote.hash, + VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash, + } + } + + pub fn timestamp(&self) -> Option { + match self { + VoteTransaction::Vote(vote) => vote.timestamp, + VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp, + } + } + + pub fn last_voted_slot(&self) -> Option { + match self { + VoteTransaction::Vote(vote) => vote.slots.last().copied(), + VoteTransaction::VoteStateUpdate(vote_state_update) => { + Some(vote_state_update.lockouts.back()?.slot) + } + } + } + + pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> { + Some((self.last_voted_slot()?, self.hash())) + } +} + +impl From for VoteTransaction { + fn from(vote: Vote) -> Self { + VoteTransaction::Vote(vote) + } +} + +impl From for VoteTransaction { + fn from(vote_state_update: VoteStateUpdate) -> Self { + VoteTransaction::VoteStateUpdate(vote_state_update) + } +} diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 43008efa0..3efc5824c 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -61,6 +61,7 @@ impl SanitizedTransaction { }; let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| { + // TODO: Move to `vote_parser` runtime module let mut ix_iter = message.program_instructions_iter(); ix_iter.next().map(|(program_id, _ix)| program_id) == Some(&crate::vote::program::id()) });