1use {
2 crate::vote_transaction::VoteTransaction, solana_bincode::limited_deserialize,
3 solana_hash::Hash, solana_pubkey::Pubkey, solana_signature::Signature,
4 solana_svm_transaction::svm_transaction::SVMTransaction, solana_transaction::Transaction,
5 solana_vote_interface::instruction::VoteInstruction,
6};
7
8pub type ParsedVote = (Pubkey, VoteTransaction, Option<Hash>, Signature);
9
10pub fn parse_sanitized_vote_transaction(tx: &impl SVMTransaction) -> Option<ParsedVote> {
12 let (program_id, first_instruction) = tx.program_instructions_iter().next()?;
14 if !solana_sdk_ids::vote::check_id(program_id) {
15 return None;
16 }
17 let first_account = usize::from(*first_instruction.accounts.first()?);
18 let key = tx.account_keys().get(first_account)?;
19 let (vote, switch_proof_hash) = parse_vote_instruction_data(first_instruction.data)?;
20 let signature = tx.signatures().first().cloned().unwrap_or_default();
21 Some((*key, vote, switch_proof_hash, signature))
22}
23
24pub fn parse_vote_transaction(tx: &Transaction) -> Option<ParsedVote> {
26 let message = tx.message();
28 let first_instruction = message.instructions.first()?;
29 let program_id_index = usize::from(first_instruction.program_id_index);
30 let program_id = message.account_keys.get(program_id_index)?;
31 if !solana_sdk_ids::vote::check_id(program_id) {
32 return None;
33 }
34 let first_account = usize::from(*first_instruction.accounts.first()?);
35 let key = message.account_keys.get(first_account)?;
36 let (vote, switch_proof_hash) = parse_vote_instruction_data(&first_instruction.data)?;
37 let signature = tx.signatures.first().cloned().unwrap_or_default();
38 Some((*key, vote, switch_proof_hash, signature))
39}
40
41fn parse_vote_instruction_data(
42 vote_instruction_data: &[u8],
43) -> Option<(VoteTransaction, Option<Hash>)> {
44 match limited_deserialize(
45 vote_instruction_data,
46 solana_packet::PACKET_DATA_SIZE as u64,
47 )
48 .ok()?
49 {
50 VoteInstruction::Vote(vote) => Some((VoteTransaction::from(vote), None)),
51 VoteInstruction::VoteSwitch(vote, hash) => Some((VoteTransaction::from(vote), Some(hash))),
52 VoteInstruction::UpdateVoteState(vote_state_update) => {
53 Some((VoteTransaction::from(vote_state_update), None))
54 }
55 VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
56 Some((VoteTransaction::from(vote_state_update), Some(hash)))
57 }
58 VoteInstruction::CompactUpdateVoteState(vote_state_update) => {
59 Some((VoteTransaction::from(vote_state_update), None))
60 }
61 VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, hash) => {
62 Some((VoteTransaction::from(vote_state_update), Some(hash)))
63 }
64 VoteInstruction::TowerSync(tower_sync) => Some((VoteTransaction::from(tower_sync), None)),
65 VoteInstruction::TowerSyncSwitch(tower_sync, hash) => {
66 Some((VoteTransaction::from(tower_sync), Some(hash)))
67 }
68 VoteInstruction::Authorize(_, _)
69 | VoteInstruction::AuthorizeChecked(_)
70 | VoteInstruction::AuthorizeWithSeed(_)
71 | VoteInstruction::AuthorizeCheckedWithSeed(_)
72 | VoteInstruction::InitializeAccount(_)
73 | VoteInstruction::UpdateCommission(_)
74 | VoteInstruction::UpdateValidatorIdentity
75 | VoteInstruction::Withdraw(_) => None,
76 }
77}
78
79#[cfg(test)]
80mod test {
81 use {
82 super::*,
83 solana_clock::Slot,
84 solana_keypair::Keypair,
85 solana_sha256_hasher::hash,
86 solana_signer::Signer,
87 solana_vote_interface::{instruction as vote_instruction, state::Vote},
88 };
89
90 fn new_vote_transaction(
92 slots: Vec<Slot>,
93 bank_hash: Hash,
94 blockhash: Hash,
95 node_keypair: &Keypair,
96 vote_keypair: &Keypair,
97 authorized_voter_keypair: &Keypair,
98 switch_proof_hash: Option<Hash>,
99 ) -> Transaction {
100 let votes = Vote::new(slots, bank_hash);
101 let vote_ix = if let Some(switch_proof_hash) = switch_proof_hash {
102 vote_instruction::vote_switch(
103 &vote_keypair.pubkey(),
104 &authorized_voter_keypair.pubkey(),
105 votes,
106 switch_proof_hash,
107 )
108 } else {
109 vote_instruction::vote(
110 &vote_keypair.pubkey(),
111 &authorized_voter_keypair.pubkey(),
112 votes,
113 )
114 };
115
116 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
117
118 vote_tx.partial_sign(&[node_keypair], blockhash);
119 vote_tx.partial_sign(&[authorized_voter_keypair], blockhash);
120 vote_tx
121 }
122
123 fn run_test_parse_vote_transaction(input_hash: Option<Hash>) {
124 let node_keypair = Keypair::new();
125 let vote_keypair = Keypair::new();
126 let auth_voter_keypair = Keypair::new();
127 let bank_hash = Hash::default();
128 let vote_tx = new_vote_transaction(
129 vec![42],
130 bank_hash,
131 Hash::default(),
132 &node_keypair,
133 &vote_keypair,
134 &auth_voter_keypair,
135 input_hash,
136 );
137 let (key, vote, hash, signature) = parse_vote_transaction(&vote_tx).unwrap();
138 assert_eq!(hash, input_hash);
139 assert_eq!(vote, VoteTransaction::from(Vote::new(vec![42], bank_hash)));
140 assert_eq!(key, vote_keypair.pubkey());
141 assert_eq!(signature, vote_tx.signatures[0]);
142
143 let mut vote_ix = vote_instruction::vote(
145 &vote_keypair.pubkey(),
146 &auth_voter_keypair.pubkey(),
147 Vote::new(vec![1, 2], Hash::default()),
148 );
149 vote_ix.program_id = Pubkey::default();
150 let vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
151 assert!(parse_vote_transaction(&vote_tx).is_none());
152 }
153
154 #[test]
155 fn test_parse_vote_transaction() {
156 run_test_parse_vote_transaction(None);
157 run_test_parse_vote_transaction(Some(hash(&[42u8])));
158 }
159}