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