solana_account_decoder/
parse_vote.rs

1use {
2    crate::{parse_account_data::ParseAccountError, StringAmount},
3    solana_sdk::{
4        clock::{Epoch, Slot},
5        pubkey::Pubkey,
6        vote::state::{BlockTimestamp, Lockout, VoteState},
7    },
8};
9
10pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, ParseAccountError> {
11    let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?;
12    let epoch_credits = vote_state
13        .epoch_credits()
14        .iter()
15        .map(|(epoch, credits, previous_credits)| UiEpochCredits {
16            epoch: *epoch,
17            credits: credits.to_string(),
18            previous_credits: previous_credits.to_string(),
19        })
20        .collect();
21    let votes = vote_state
22        .votes
23        .iter()
24        .map(|lockout| UiLockout {
25            slot: lockout.slot(),
26            confirmation_count: lockout.confirmation_count(),
27        })
28        .collect();
29    let authorized_voters = vote_state
30        .authorized_voters()
31        .iter()
32        .map(|(epoch, authorized_voter)| UiAuthorizedVoters {
33            epoch: *epoch,
34            authorized_voter: authorized_voter.to_string(),
35        })
36        .collect();
37    let prior_voters = vote_state
38        .prior_voters()
39        .buf()
40        .iter()
41        .filter(|(pubkey, _, _)| pubkey != &Pubkey::default())
42        .map(
43            |(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters {
44                authorized_pubkey: authorized_pubkey.to_string(),
45                epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch,
46                target_epoch: *target_epoch,
47            },
48        )
49        .collect();
50    Ok(VoteAccountType::Vote(UiVoteState {
51        node_pubkey: vote_state.node_pubkey.to_string(),
52        authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
53        commission: vote_state.commission,
54        votes,
55        root_slot: vote_state.root_slot,
56        authorized_voters,
57        prior_voters,
58        epoch_credits,
59        last_timestamp: vote_state.last_timestamp,
60    }))
61}
62
63/// A wrapper enum for consistency across programs
64#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase", tag = "type", content = "info")]
66pub enum VoteAccountType {
67    Vote(UiVoteState),
68}
69
70/// A duplicate representation of VoteState for pretty JSON serialization
71#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
72#[serde(rename_all = "camelCase")]
73pub struct UiVoteState {
74    node_pubkey: String,
75    authorized_withdrawer: String,
76    commission: u8,
77    votes: Vec<UiLockout>,
78    root_slot: Option<Slot>,
79    authorized_voters: Vec<UiAuthorizedVoters>,
80    prior_voters: Vec<UiPriorVoters>,
81    epoch_credits: Vec<UiEpochCredits>,
82    last_timestamp: BlockTimestamp,
83}
84
85#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "camelCase")]
87struct UiLockout {
88    slot: Slot,
89    confirmation_count: u32,
90}
91
92impl From<&Lockout> for UiLockout {
93    fn from(lockout: &Lockout) -> Self {
94        Self {
95            slot: lockout.slot(),
96            confirmation_count: lockout.confirmation_count(),
97        }
98    }
99}
100
101#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
102#[serde(rename_all = "camelCase")]
103struct UiAuthorizedVoters {
104    epoch: Epoch,
105    authorized_voter: String,
106}
107
108#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(rename_all = "camelCase")]
110struct UiPriorVoters {
111    authorized_pubkey: String,
112    epoch_of_last_authorized_switch: Epoch,
113    target_epoch: Epoch,
114}
115
116#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118struct UiEpochCredits {
119    epoch: Epoch,
120    credits: StringAmount,
121    previous_credits: StringAmount,
122}
123
124#[cfg(test)]
125mod test {
126    use {super::*, solana_sdk::vote::state::VoteStateVersions};
127
128    #[test]
129    fn test_parse_vote() {
130        let vote_state = VoteState::default();
131        let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
132        let versioned = VoteStateVersions::new_current(vote_state);
133        VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
134        let expected_vote_state = UiVoteState {
135            node_pubkey: Pubkey::default().to_string(),
136            authorized_withdrawer: Pubkey::default().to_string(),
137            ..UiVoteState::default()
138        };
139        assert_eq!(
140            parse_vote(&vote_account_data).unwrap(),
141            VoteAccountType::Vote(expected_vote_state)
142        );
143
144        let bad_data = vec![0; 4];
145        assert!(parse_vote(&bad_data).is_err());
146    }
147}