1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4 super::{StakeAccount, Stakes},
5 crate::stake_history::StakeHistory,
6 imbl::HashMap as ImblHashMap,
7 serde::{Deserialize, Serialize, Serializer, ser::SerializeMap},
8 solana_clock::Epoch,
9 solana_pubkey::Pubkey,
10 solana_stake_interface::state::Stake,
11 solana_vote::vote_account::VoteAccounts,
12 std::{collections::HashMap, sync::Arc},
13};
14
15#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
19#[derive(Debug, Clone)]
20pub enum SerdeStakesToStakeFormat {
21 Stake(Stakes<Stake>),
22 Account(Stakes<StakeAccount>),
23}
24
25impl SerdeStakesToStakeFormat {
26 pub fn vote_accounts(&self) -> &VoteAccounts {
27 match self {
28 Self::Stake(stakes) => stakes.vote_accounts(),
29 Self::Account(stakes) => stakes.vote_accounts(),
30 }
31 }
32
33 pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
34 match self {
35 Self::Stake(stakes) => stakes.staked_nodes(),
36 Self::Account(stakes) => stakes.staked_nodes(),
37 }
38 }
39}
40
41#[cfg(feature = "dev-context-only-utils")]
42impl PartialEq<Self> for SerdeStakesToStakeFormat {
43 fn eq(&self, other: &Self) -> bool {
44 match (self, other) {
45 (Self::Stake(stakes), Self::Stake(other)) => stakes == other,
46 (Self::Account(stakes), Self::Account(other)) => stakes == other,
47 (Self::Stake(stakes), Self::Account(other)) => {
48 stakes == &Stakes::<Stake>::from(other.clone())
49 }
50 (Self::Account(stakes), Self::Stake(other)) => {
51 other == &Stakes::<Stake>::from(stakes.clone())
52 }
53 }
54 }
55}
56
57impl From<Stakes<StakeAccount>> for SerdeStakesToStakeFormat {
58 fn from(stakes: Stakes<StakeAccount>) -> Self {
59 Self::Account(stakes)
60 }
61}
62
63impl Serialize for SerdeStakesToStakeFormat {
64 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
65 where
66 S: Serializer,
67 {
68 match self {
69 Self::Stake(stakes) => stakes.serialize(serializer),
70 Self::Account(stakes) => serialize_stake_accounts_to_stake_format(stakes, serializer),
71 }
72 }
73}
74
75#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
76pub(crate) fn serialize_stake_accounts_to_delegation_format<S: Serializer>(
77 stakes: &Stakes<StakeAccount>,
78 serializer: S,
79) -> Result<S::Ok, S::Error> {
80 SerdeStakeAccountsToDelegationFormat::from(stakes.clone()).serialize(serializer)
81}
82
83fn serialize_stake_accounts_to_stake_format<S: Serializer>(
84 stakes: &Stakes<StakeAccount>,
85 serializer: S,
86) -> Result<S::Ok, S::Error> {
87 SerdeStakeAccountsToStakeFormat::from(stakes.clone()).serialize(serializer)
88}
89
90impl From<Stakes<StakeAccount>> for SerdeStakeAccountsToDelegationFormat {
91 fn from(stakes: Stakes<StakeAccount>) -> Self {
92 let Stakes {
93 vote_accounts,
94 stake_delegations,
95 delegated_stakes: _,
96 unused,
97 epoch,
98 stake_history,
99 } = stakes;
100
101 Self {
102 vote_accounts,
103 stake_delegations: SerdeStakeAccountMapToDelegationFormat(stake_delegations),
104 unused,
105 epoch,
106 stake_history,
107 }
108 }
109}
110
111impl From<Stakes<StakeAccount>> for SerdeStakeAccountsToStakeFormat {
112 fn from(stakes: Stakes<StakeAccount>) -> Self {
113 let Stakes {
114 vote_accounts,
115 stake_delegations,
116 delegated_stakes: _,
117 unused,
118 epoch,
119 stake_history,
120 } = stakes;
121
122 Self {
123 vote_accounts,
124 stake_delegations: SerdeStakeAccountMapToStakeFormat(stake_delegations),
125 unused,
126 epoch,
127 stake_history,
128 }
129 }
130}
131
132#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
133#[derive(Serialize)]
134struct SerdeStakeAccountsToDelegationFormat {
135 vote_accounts: VoteAccounts,
136 stake_delegations: SerdeStakeAccountMapToDelegationFormat,
137 unused: u64,
138 epoch: Epoch,
139 stake_history: StakeHistory,
140}
141
142#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
143#[derive(Serialize)]
144struct SerdeStakeAccountsToStakeFormat {
145 vote_accounts: VoteAccounts,
146 stake_delegations: SerdeStakeAccountMapToStakeFormat,
147 unused: u64,
148 epoch: Epoch,
149 stake_history: StakeHistory,
150}
151
152#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
153struct SerdeStakeAccountMapToDelegationFormat(ImblHashMap<Pubkey, StakeAccount>);
154impl Serialize for SerdeStakeAccountMapToDelegationFormat {
155 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156 where
157 S: Serializer,
158 {
159 let mut s = serializer.serialize_map(Some(self.0.len()))?;
160 for (pubkey, stake_account) in self.0.iter() {
161 s.serialize_entry(pubkey, stake_account.delegation())?;
162 }
163 s.end()
164 }
165}
166
167#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
168struct SerdeStakeAccountMapToStakeFormat(ImblHashMap<Pubkey, StakeAccount>);
169impl Serialize for SerdeStakeAccountMapToStakeFormat {
170 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171 where
172 S: Serializer,
173 {
174 let mut s = serializer.serialize_map(Some(self.0.len()))?;
175 for (pubkey, stake_account) in self.0.iter() {
176 s.serialize_entry(pubkey, stake_account.stake())?;
177 }
178 s.end()
179 }
180}
181
182#[derive(Clone, Debug, Deserialize)]
188#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
189pub(crate) struct DeserializableStakes<T> {
190 pub vote_accounts: VoteAccounts,
191 pub stake_delegations: Vec<(Pubkey, T)>,
192 pub unused: u64,
193 pub epoch: Epoch,
194 pub stake_history: StakeHistory,
195}
196
197#[cfg(test)]
198mod tests {
199 use {
200 super::*,
201 crate::{stake_utils, stakes::StakesCache},
202 rand::Rng,
203 serde::Deserialize,
204 solana_rent::Rent,
205 solana_stake_interface::state::Delegation,
206 solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE,
207 solana_vote_program::vote_state,
208 };
209
210 #[test]
211 fn test_serde_stakes_to_stake_format() {
212 let mut stake_delegations = ImblHashMap::new();
213 let vote_pubkey = Pubkey::new_unique();
214 let node_pubkey = Pubkey::new_unique();
215 stake_delegations.insert(
216 Pubkey::new_unique(),
217 StakeAccount::try_from(stake_utils::create_stake_account(
218 &Pubkey::new_unique(),
219 &vote_pubkey,
220 &vote_state::create_v4_account_with_authorized(
221 &node_pubkey,
222 &vote_pubkey,
223 [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
224 &vote_pubkey,
225 0,
226 &vote_pubkey,
227 0,
228 &node_pubkey,
229 1_000_000_000,
230 ),
231 &Rent::default(),
232 1_000_000_000,
233 ))
234 .unwrap(),
235 );
236
237 let stake_account_stakes = Stakes {
238 vote_accounts: VoteAccounts::default(),
239 stake_delegations,
240 delegated_stakes: ImblHashMap::default(),
241 unused: 0,
242 epoch: 0,
243 stake_history: StakeHistory::default(),
244 };
245
246 let wrapped_stakes = SerdeStakesToStakeFormat::Account(stake_account_stakes.clone());
247 let serialized_stakes = bincode::serialize(&wrapped_stakes).unwrap();
248 let stake_stakes = Stakes::from_deserialized(
249 bincode::deserialize::<DeserializableStakes<Stake>>(&serialized_stakes).unwrap(),
250 );
251 let expected_stake_stakes = Stakes::<Stake>::from(stake_account_stakes);
252 assert_eq!(expected_stake_stakes, stake_stakes);
253 }
254
255 #[test]
256 fn test_serde_stakes_to_delegation_format() {
257 #[derive(Debug, Serialize)]
258 struct SerializableDummy {
259 head: String,
260 #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
261 stakes: Stakes<StakeAccount>,
262 tail: String,
263 }
264
265 #[derive(Debug, Deserialize)]
266 struct DeserializableDummy {
267 head: String,
268 stakes: DeserializableStakes<Delegation>,
269 tail: String,
270 }
271
272 let mut rng = rand::rng();
273 let stakes_cache = StakesCache::new(Stakes {
274 unused: rng.random(),
275 epoch: rng.random(),
276 ..Stakes::default()
277 });
278 for _ in 0..rng.random_range(5usize..10) {
279 let vote_pubkey = solana_pubkey::new_rand();
280 let node_pubkey = solana_pubkey::new_rand();
281 let commission = rng.random_range(0..101);
282 let commission_bps = commission * 100;
283 let vote_account = vote_state::create_v4_account_with_authorized(
284 &node_pubkey,
285 &vote_pubkey,
286 [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
287 &vote_pubkey,
288 commission_bps,
289 &vote_pubkey,
290 0,
291 &node_pubkey,
292 rng.random_range(0..1_000_000), );
294 stakes_cache.check_and_store(&vote_pubkey, &vote_account, None, true);
295 for _ in 0..rng.random_range(10usize..20) {
296 let stake_pubkey = solana_pubkey::new_rand();
297 let rent = Rent::free();
298 let stake_account = stake_utils::create_stake_account(
299 &stake_pubkey, &vote_pubkey,
301 &vote_account,
302 &rent,
303 rng.random_range(0..1_000_000), );
305 stakes_cache.check_and_store(&stake_pubkey, &stake_account, None, true);
306 }
307 }
308 let stakes: Stakes<StakeAccount> = stakes_cache.stakes().clone();
309 assert!(stakes.vote_accounts.as_ref().len() >= 5);
310 assert!(stakes.stake_delegations.len() >= 50);
311 let dummy = SerializableDummy {
312 head: String::from("dummy-head"),
313 stakes: stakes.clone(),
314 tail: String::from("dummy-tail"),
315 };
316 assert!(dummy.stakes.vote_accounts().as_ref().len() >= 5);
317 let data = bincode::serialize(&dummy).unwrap();
318 let other: DeserializableDummy = bincode::deserialize(&data).unwrap();
319 assert_eq!(other.head, dummy.head);
320 assert_eq!(other.tail, dummy.tail);
321
322 assert!(other.stakes.vote_accounts.as_ref().len() >= 5);
323 assert_eq!(other.stakes.vote_accounts, stakes.vote_accounts);
324
325 assert_eq!(other.stakes.epoch, stakes.epoch);
326 assert_eq!(other.stakes.stake_history, stakes.stake_history);
327
328 assert!(other.stakes.stake_delegations.len() >= 50);
329 let other_stakes = Stakes::from_deserialized(other.stakes);
331 assert_eq!(other_stakes, dummy.stakes.into());
332 }
333}