Skip to main content

solana_runtime/stakes/
serde_stakes.rs

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/// Wrapper struct with custom serialization to support serializing
16/// `Stakes<StakeAccount>` as `Stakes<Stake>` without doing an intermediate
17/// clone of the stake data.
18#[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/// Simplified, intermediate representation of [`Stakes<T>`]
183///
184/// Its bincode serializaiton format is identical as Stakes<T>, but allows faster
185/// deserialization without creating imbl::HashMap (such conversion is deferred until
186/// data is actually needed).
187#[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), // lamports
293            );
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, // authorized
300                    &vote_pubkey,
301                    &vote_account,
302                    &rent,
303                    rng.random_range(0..1_000_000), // lamports
304                );
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        // DeserializableStakes doesn't preserve same order of elements as Stakes, compare converted
330        let other_stakes = Stakes::from_deserialized(other.stakes);
331        assert_eq!(other_stakes, dummy.stakes.into());
332    }
333}