solana_account_decoder/
parse_vote.rs

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