1use {
2 crate::{parse_account_data::ParseAccountError, StringAmount},
3 serde::{Deserialize, Serialize},
4 solana_clock::{Epoch, Slot},
5 solana_pubkey::Pubkey,
6 solana_vote_interface::state::{BlockTimestamp, LandedVote, Lockout, VoteStateV4},
7};
8
9pub fn parse_vote(data: &[u8], vote_pubkey: &Pubkey) -> Result<VoteAccountType, ParseAccountError> {
10 let vote_state =
11 VoteStateV4::deserialize(data, vote_pubkey).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.votes.iter().map(UiLandedVote::from).collect();
22 let authorized_voters = vote_state
23 .authorized_voters
24 .iter()
25 .map(|(epoch, authorized_voter)| UiAuthorizedVoters {
26 epoch: *epoch,
27 authorized_voter: authorized_voter.to_string(),
28 })
29 .collect();
30 Ok(VoteAccountType::Vote(UiVoteState {
31 node_pubkey: vote_state.node_pubkey.to_string(),
32 authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
33 commission: (vote_state.inflation_rewards_commission_bps / 100) as u8,
34 votes,
35 root_slot: vote_state.root_slot,
36 authorized_voters,
37 prior_voters: Vec::new(), epoch_credits,
39 last_timestamp: vote_state.last_timestamp,
40 inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
41 inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
42 block_revenue_collector: vote_state.block_revenue_collector.to_string(),
43 block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
44 pending_delegator_rewards: vote_state.pending_delegator_rewards.to_string(),
45 bls_pubkey_compressed: vote_state
46 .bls_pubkey_compressed
47 .map(|bytes| bs58::encode(bytes).into_string()),
48 }))
49}
50
51#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(rename_all = "camelCase", tag = "type", content = "info")]
54pub enum VoteAccountType {
55 Vote(UiVoteState),
56}
57
58#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
60#[serde(rename_all = "camelCase")]
61pub struct UiVoteState {
62 node_pubkey: String,
63 authorized_withdrawer: String,
64 commission: u8,
65 votes: Vec<UiLandedVote>,
66 root_slot: Option<Slot>,
67 authorized_voters: Vec<UiAuthorizedVoters>,
68 prior_voters: Vec<UiPriorVoters>,
69 epoch_credits: Vec<UiEpochCredits>,
70 last_timestamp: BlockTimestamp,
71 inflation_rewards_commission_bps: u16,
73 inflation_rewards_collector: String,
74 block_revenue_collector: String,
75 block_revenue_commission_bps: u16,
76 pending_delegator_rewards: StringAmount,
77 bls_pubkey_compressed: Option<String>,
78}
79
80#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
81#[serde(rename_all = "camelCase")]
82struct UiLockout {
83 slot: Slot,
84 confirmation_count: u32,
85}
86
87impl From<&Lockout> for UiLockout {
88 fn from(lockout: &Lockout) -> Self {
89 Self {
90 slot: lockout.slot(),
91 confirmation_count: lockout.confirmation_count(),
92 }
93 }
94}
95
96#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
97#[serde(rename_all = "camelCase")]
98struct UiLandedVote {
99 latency: u8,
100 #[serde(flatten)]
104 lockout: UiLockout,
105}
106
107impl From<&LandedVote> for UiLandedVote {
108 fn from(landed_vote: &LandedVote) -> Self {
109 Self {
110 latency: landed_vote.latency,
111 lockout: UiLockout::from(&landed_vote.lockout),
112 }
113 }
114}
115
116#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118struct UiAuthorizedVoters {
119 epoch: Epoch,
120 authorized_voter: String,
121}
122
123#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
124#[serde(rename_all = "camelCase")]
125struct UiPriorVoters {
126 authorized_pubkey: String,
127 epoch_of_last_authorized_switch: Epoch,
128 target_epoch: Epoch,
129}
130
131#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(rename_all = "camelCase")]
133struct UiEpochCredits {
134 epoch: Epoch,
135 credits: StringAmount,
136 previous_credits: StringAmount,
137}
138
139#[cfg(test)]
140mod test {
141 use {super::*, solana_vote_interface::state::VoteStateVersions};
142
143 #[test]
144 fn test_parse_vote() {
145 let vote_pubkey = Pubkey::new_unique();
146 let vote_state = VoteStateV4::default();
147 let mut vote_account_data: Vec<u8> = vec![0; VoteStateV4::size_of()];
148 let versioned = VoteStateVersions::new_v4(vote_state.clone());
149 VoteStateV4::serialize(&versioned, &mut vote_account_data).unwrap();
150 let expected_vote_state = UiVoteState {
151 node_pubkey: Pubkey::default().to_string(),
152 authorized_withdrawer: Pubkey::default().to_string(),
153 commission: 0,
154 votes: vec![],
155 root_slot: None,
156 authorized_voters: vec![],
157 prior_voters: vec![],
158 epoch_credits: vec![],
159 last_timestamp: BlockTimestamp::default(),
160 inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
161 inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
162 block_revenue_collector: vote_state.block_revenue_collector.to_string(),
163 block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
164 pending_delegator_rewards: vote_state.pending_delegator_rewards.to_string(),
165 bls_pubkey_compressed: None,
166 };
167 assert_eq!(
168 parse_vote(&vote_account_data, &vote_pubkey).unwrap(),
169 VoteAccountType::Vote(expected_vote_state)
170 );
171
172 let bad_data = vec![0; 4];
173 assert!(parse_vote(&bad_data, &vote_pubkey).is_err());
174 }
175
176 #[test]
177 fn test_ui_landed_vote_flatten() {
178 let ui_landed_vote = UiLandedVote {
179 latency: 5,
180 lockout: UiLockout {
181 slot: 12345,
182 confirmation_count: 10,
183 },
184 };
185
186 let json = serde_json::to_value(&ui_landed_vote).unwrap();
187
188 assert_eq!(json["latency"], 5);
190 assert_eq!(json["slot"], 12345);
191 assert_eq!(json["confirmationCount"], 10);
192
193 assert!(json.get("lockout").is_none());
195
196 let json_str = r#"{"latency": 5, "slot": 12345, "confirmationCount": 10}"#;
198 let deserialized: UiLandedVote = serde_json::from_str(json_str).unwrap();
199 assert_eq!(deserialized, ui_landed_vote);
200 }
201}