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#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "camelCase", tag = "type", content = "info")]
64pub enum VoteAccountType {
65 Vote(UiVoteState),
66}
67
68#[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}