Skip to main content

solana_runtime/stakes/
serde_stakes.rs

1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3#[cfg(any(test, feature = "frozen-abi"))]
4use wincode::SchemaWrite;
5use {
6    super::{StakeAccount, Stakes},
7    crate::stake_history::StakeHistory,
8    solana_clock::Epoch,
9    solana_pubkey::Pubkey,
10    solana_stake_interface::state::{Delegation, Stake},
11    solana_vote::vote_account::VoteAccounts,
12    std::{collections::HashMap, sync::Arc},
13    wincode::SchemaRead,
14};
15
16/// Wrapper struct with custom serialization to support serializing
17/// `Stakes<StakeAccount>` as `Stakes<Stake>` without doing an intermediate
18/// clone of the stake data.
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
63/// Simplified, intermediate representation of [`Stakes<T>`]
64///
65/// Its bincode serializaiton format is identical as Stakes<T>, but allows faster
66/// deserialization without creating imbl::HashMap (such conversion is deferred until
67/// data is actually needed).
68#[cfg_attr(
69    feature = "frozen-abi",
70    derive(SchemaWrite, StableAbi, StableAbiSample)
71)]
72#[derive(Clone, Debug, SchemaRead)]
73#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
74pub(crate) struct DeserializableDelegationStakes {
75    pub vote_accounts: VoteAccounts,
76    // Sampled as `StakeAccount`s (as the serialize side does) reduced to the written `Delegation`.
77    #[cfg_attr(
78        feature = "frozen-abi",
79        stable_abi_sample(with = "stable_abi_sample_stake_delegations(rng)")
80    )]
81    pub stake_delegations: Vec<(Pubkey, Delegation)>,
82    pub unused: u64,
83    pub epoch: Epoch,
84    pub stake_history: StakeHistory,
85}
86
87#[cfg(feature = "frozen-abi")]
88fn stable_abi_sample_stake_delegations(
89    rng: &mut (impl solana_frozen_abi::rand::RngCore + ?Sized),
90) -> Vec<(Pubkey, Delegation)> {
91    use solana_frozen_abi::stable_abi::{context::SequenceLenMax, sample_collection_sized};
92    let stake_accounts: Vec<(Pubkey, StakeAccount)> =
93        sample_collection_sized(rng, SequenceLenMax(1));
94    stake_accounts
95        .into_iter()
96        .map(|(pubkey, account)| (pubkey, *account.delegation()))
97        .collect()
98}
99
100#[cfg(test)]
101mod tests {
102    use {
103        super::*,
104        crate::{serde_snapshot::deserialize_wincode_from, stake_utils, stakes::StakesCache},
105        rand::Rng,
106        solana_rent::Rent,
107        solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE,
108        solana_vote_program::vote_state,
109    };
110
111    #[test]
112    fn test_stakes_to_delegation_format() {
113        #[derive(Debug, SchemaWrite)]
114        struct SerializableDummy {
115            head: String,
116            stakes: Stakes<StakeAccount>,
117            tail: String,
118        }
119
120        #[derive(Debug, SchemaRead)]
121        struct DeserializableDummy {
122            head: String,
123            stakes: DeserializableDelegationStakes,
124            tail: String,
125        }
126
127        let mut rng = rand::rng();
128        let stakes_cache = StakesCache::new(Stakes {
129            unused: rng.random(),
130            epoch: rng.random(),
131            ..Stakes::default()
132        });
133        for _ in 0..rng.random_range(5usize..10) {
134            let vote_pubkey = solana_pubkey::new_rand();
135            let node_pubkey = solana_pubkey::new_rand();
136            let commission = rng.random_range(0..101);
137            let commission_bps = commission * 100;
138            let vote_account = vote_state::create_v4_account_with_authorized(
139                &node_pubkey,
140                &vote_pubkey,
141                [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
142                &vote_pubkey,
143                commission_bps,
144                &vote_pubkey,
145                0,
146                &node_pubkey,
147                rng.random_range(0..1_000_000), // lamports
148            );
149            stakes_cache.check_and_store(&vote_pubkey, &vote_account, None, false, false);
150            for _ in 0..rng.random_range(10usize..20) {
151                let stake_pubkey = solana_pubkey::new_rand();
152                let rent = Rent::free();
153                let stake_account = stake_utils::create_stake_account(
154                    &stake_pubkey, // authorized
155                    &vote_pubkey,
156                    &vote_account,
157                    &rent,
158                    rng.random_range(0..1_000_000), // lamports
159                );
160                stakes_cache.check_and_store(&stake_pubkey, &stake_account, None, false, false);
161            }
162        }
163        let stakes: Stakes<StakeAccount> = stakes_cache.stakes().clone();
164        assert!(stakes.vote_accounts.as_ref().len() >= 5);
165        assert!(stakes.stake_delegations.len() >= 50);
166        let dummy = SerializableDummy {
167            head: String::from("dummy-head"),
168            stakes: stakes.clone(),
169            tail: String::from("dummy-tail"),
170        };
171        assert!(dummy.stakes.vote_accounts().as_ref().len() >= 5);
172        let data = wincode::serialize(&dummy).unwrap();
173        let other: DeserializableDummy =
174            deserialize_wincode_from(std::io::Cursor::new(&data)).unwrap();
175        assert_eq!(other.head, dummy.head);
176        assert_eq!(other.tail, dummy.tail);
177
178        assert!(other.stakes.vote_accounts.as_ref().len() >= 5);
179        assert_eq!(other.stakes.vote_accounts, stakes.vote_accounts);
180
181        assert_eq!(other.stakes.epoch, stakes.epoch);
182        assert_eq!(other.stakes.stake_history, stakes.stake_history);
183
184        assert!(other.stakes.stake_delegations.len() >= 50);
185        // DeserializableDelegationStakes doesn't preserve same order of elements as Stakes, compare converted
186        let other_stakes = Stakes::from_deserialized(other.stakes);
187        assert_eq!(other_stakes, dummy.stakes.into());
188    }
189}