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#[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#[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 #[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), );
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, &vote_pubkey,
156 &vote_account,
157 &rent,
158 rng.random_range(0..1_000_000), );
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 let other_stakes = Stakes::from_deserialized(other.stakes);
187 assert_eq!(other_stakes, dummy.stakes.into());
188 }
189}