solana_vote/
vote_parser.rs

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
10// Used for locally forwarding processed vote transactions to consensus
11pub fn parse_sanitized_vote_transaction(tx: &impl SVMTransaction) -> Option<ParsedVote> {
12    // Check first instruction for a vote
13    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
24// Used for parsing gossip vote transactions
25pub fn parse_vote_transaction(tx: &Transaction) -> Option<ParsedVote> {
26    // Check first instruction for a vote
27    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    // Reimplemented locally from Vote program.
91    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        // Test bad program id fails
144        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}