solana_runtime/stakes/
serde_stakes.rs

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