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