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