Skip to main content

solana_runtime/stakes/
serde_stakes.rs

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/// Wrapper struct with custom serialization to support serializing
19/// `Stakes<StakeAccount>` as `Stakes<Stake>` without doing an intermediate
20/// clone of the stake data.
21#[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/// Simplified, intermediate representation of [`Stakes<T>`]
186///
187/// Its bincode serializaiton format is identical as Stakes<T>, but allows faster
188/// deserialization without creating imbl::HashMap (such conversion is deferred until
189/// data is actually needed).
190#[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    // Sampled as `StakeAccount`s (as the serialize side does) reduced to the written `Delegation`.
199    #[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), // lamports
272            );
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, // authorized
279                    &vote_pubkey,
280                    &vote_account,
281                    &rent,
282                    rng.random_range(0..1_000_000), // lamports
283                );
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        // DeserializableDelegationStakes doesn't preserve same order of elements as Stakes, compare converted
310        let other_stakes = Stakes::from_deserialized(other.stakes);
311        assert_eq!(other_stakes, dummy.stakes.into());
312    }
313}