clone_solana_runtime/
epoch_stakes.rs

1use {
2    crate::stakes::{serde_stakes_to_delegation_format, SerdeStakesToStakeFormat, StakesEnum},
3    clone_solana_sdk::{clock::Epoch, pubkey::Pubkey},
4    clone_solana_vote::vote_account::VoteAccountsHashMap,
5    serde::{Deserialize, Serialize},
6    std::{collections::HashMap, sync::Arc},
7};
8
9pub type NodeIdToVoteAccounts = HashMap<Pubkey, NodeVoteAccounts>;
10pub type EpochAuthorizedVoters = HashMap<Pubkey, Pubkey>;
11
12#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
13#[derive(Clone, Serialize, Debug, Deserialize, Default, PartialEq, Eq)]
14pub struct NodeVoteAccounts {
15    pub vote_accounts: Vec<Pubkey>,
16    pub total_stake: u64,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
21#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
22pub struct EpochStakes {
23    #[serde(with = "serde_stakes_to_delegation_format")]
24    stakes: Arc<StakesEnum>,
25    total_stake: u64,
26    node_id_to_vote_accounts: Arc<NodeIdToVoteAccounts>,
27    epoch_authorized_voters: Arc<EpochAuthorizedVoters>,
28}
29
30impl EpochStakes {
31    pub(crate) fn new(stakes: Arc<StakesEnum>, leader_schedule_epoch: Epoch) -> Self {
32        let epoch_vote_accounts = stakes.vote_accounts();
33        let (total_stake, node_id_to_vote_accounts, epoch_authorized_voters) =
34            Self::parse_epoch_vote_accounts(epoch_vote_accounts.as_ref(), leader_schedule_epoch);
35        Self {
36            stakes,
37            total_stake,
38            node_id_to_vote_accounts: Arc::new(node_id_to_vote_accounts),
39            epoch_authorized_voters: Arc::new(epoch_authorized_voters),
40        }
41    }
42
43    #[cfg(feature = "dev-context-only-utils")]
44    pub fn new_for_tests(
45        vote_accounts_hash_map: VoteAccountsHashMap,
46        leader_schedule_epoch: Epoch,
47    ) -> Self {
48        Self::new(
49            Arc::new(StakesEnum::Accounts(crate::stakes::Stakes::new_for_tests(
50                0,
51                clone_solana_vote::vote_account::VoteAccounts::from(Arc::new(
52                    vote_accounts_hash_map,
53                )),
54                im::HashMap::default(),
55            ))),
56            leader_schedule_epoch,
57        )
58    }
59
60    pub fn stakes(&self) -> &StakesEnum {
61        &self.stakes
62    }
63
64    pub fn total_stake(&self) -> u64 {
65        self.total_stake
66    }
67
68    /// For tests
69    pub fn set_total_stake(&mut self, total_stake: u64) {
70        self.total_stake = total_stake;
71    }
72
73    pub fn node_id_to_vote_accounts(&self) -> &Arc<NodeIdToVoteAccounts> {
74        &self.node_id_to_vote_accounts
75    }
76
77    pub fn node_id_to_stake(&self, node_id: &Pubkey) -> Option<u64> {
78        self.node_id_to_vote_accounts
79            .get(node_id)
80            .map(|x| x.total_stake)
81    }
82
83    pub fn epoch_authorized_voters(&self) -> &Arc<EpochAuthorizedVoters> {
84        &self.epoch_authorized_voters
85    }
86
87    pub fn vote_account_stake(&self, vote_account: &Pubkey) -> u64 {
88        self.stakes
89            .vote_accounts()
90            .get_delegated_stake(vote_account)
91    }
92
93    fn parse_epoch_vote_accounts(
94        epoch_vote_accounts: &VoteAccountsHashMap,
95        leader_schedule_epoch: Epoch,
96    ) -> (u64, NodeIdToVoteAccounts, EpochAuthorizedVoters) {
97        let mut node_id_to_vote_accounts: NodeIdToVoteAccounts = HashMap::new();
98        let total_stake = epoch_vote_accounts
99            .iter()
100            .map(|(_, (stake, _))| stake)
101            .sum();
102        let epoch_authorized_voters = epoch_vote_accounts
103            .iter()
104            .filter_map(|(key, (stake, account))| {
105                let vote_state = account.vote_state();
106
107                if *stake > 0 {
108                    if let Some(authorized_voter) = vote_state
109                        .authorized_voters()
110                        .get_authorized_voter(leader_schedule_epoch)
111                    {
112                        let node_vote_accounts = node_id_to_vote_accounts
113                            .entry(vote_state.node_pubkey)
114                            .or_default();
115
116                        node_vote_accounts.total_stake += stake;
117                        node_vote_accounts.vote_accounts.push(*key);
118
119                        Some((*key, authorized_voter))
120                    } else {
121                        None
122                    }
123                } else {
124                    None
125                }
126            })
127            .collect();
128        (
129            total_stake,
130            node_id_to_vote_accounts,
131            epoch_authorized_voters,
132        )
133    }
134}
135
136#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
137#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum VersionedEpochStakes {
140    Current {
141        stakes: SerdeStakesToStakeFormat,
142        total_stake: u64,
143        node_id_to_vote_accounts: Arc<NodeIdToVoteAccounts>,
144        epoch_authorized_voters: Arc<EpochAuthorizedVoters>,
145    },
146}
147
148impl From<VersionedEpochStakes> for EpochStakes {
149    fn from(versioned: VersionedEpochStakes) -> Self {
150        let VersionedEpochStakes::Current {
151            stakes,
152            total_stake,
153            node_id_to_vote_accounts,
154            epoch_authorized_voters,
155        } = versioned;
156
157        Self {
158            stakes: Arc::new(stakes.into()),
159            total_stake,
160            node_id_to_vote_accounts,
161            epoch_authorized_voters,
162        }
163    }
164}
165
166/// Only the `StakesEnum::Delegations` variant is unable to be serialized as a
167/// `StakesEnum::Stakes` variant, so leave those entries and split off the other
168/// epoch stakes enum variants into a new map which will be serialized into the
169/// new `versioned_epoch_stakes` snapshot field.  After a cluster transitions to
170/// serializing epoch stakes in the new format, `StakesEnum::Delegations`
171/// variants for recent epochs will no longer be created and can be deprecated.
172pub(crate) fn split_epoch_stakes(
173    bank_epoch_stakes: HashMap<Epoch, EpochStakes>,
174) -> (
175    HashMap<Epoch, EpochStakes>,
176    HashMap<Epoch, VersionedEpochStakes>,
177) {
178    let mut old_epoch_stakes = HashMap::new();
179    let mut versioned_epoch_stakes = HashMap::new();
180    for (epoch, epoch_stakes) in bank_epoch_stakes.into_iter() {
181        let EpochStakes {
182            stakes,
183            total_stake,
184            node_id_to_vote_accounts,
185            epoch_authorized_voters,
186        } = epoch_stakes;
187        match stakes.as_ref() {
188            StakesEnum::Delegations(_) => {
189                old_epoch_stakes.insert(
190                    epoch,
191                    EpochStakes {
192                        stakes: stakes.clone(),
193                        total_stake,
194                        node_id_to_vote_accounts,
195                        epoch_authorized_voters,
196                    },
197                );
198            }
199            StakesEnum::Accounts(stakes) => {
200                versioned_epoch_stakes.insert(
201                    epoch,
202                    VersionedEpochStakes::Current {
203                        stakes: SerdeStakesToStakeFormat::Account(stakes.clone()),
204                        total_stake,
205                        node_id_to_vote_accounts,
206                        epoch_authorized_voters,
207                    },
208                );
209            }
210            StakesEnum::Stakes(stakes) => {
211                versioned_epoch_stakes.insert(
212                    epoch,
213                    VersionedEpochStakes::Current {
214                        stakes: SerdeStakesToStakeFormat::Stake(stakes.clone()),
215                        total_stake,
216                        node_id_to_vote_accounts,
217                        epoch_authorized_voters,
218                    },
219                );
220            }
221        }
222    }
223    (old_epoch_stakes, versioned_epoch_stakes)
224}
225
226#[cfg(test)]
227pub(crate) mod tests {
228    use {
229        super::*,
230        crate::{
231            stake_account::StakeAccount,
232            stakes::{Stakes, StakesCache},
233        },
234        clone_solana_sdk::{account::AccountSharedData, rent::Rent},
235        clone_solana_stake_program::stake_state::{self, Delegation, Stake},
236        clone_solana_vote::vote_account::VoteAccount,
237        clone_solana_vote_program::vote_state::{self, create_account_with_authorized},
238        std::iter,
239    };
240
241    struct VoteAccountInfo {
242        vote_account: Pubkey,
243        account: AccountSharedData,
244        authorized_voter: Pubkey,
245    }
246
247    fn new_vote_accounts(
248        num_nodes: usize,
249        num_vote_accounts_per_node: usize,
250    ) -> HashMap<Pubkey, Vec<VoteAccountInfo>> {
251        // Create some vote accounts for each pubkey
252        (0..num_nodes)
253            .map(|_| {
254                let node_id = clone_solana_pubkey::new_rand();
255                (
256                    node_id,
257                    iter::repeat_with(|| {
258                        let authorized_voter = clone_solana_pubkey::new_rand();
259                        VoteAccountInfo {
260                            vote_account: clone_solana_pubkey::new_rand(),
261                            account: create_account_with_authorized(
262                                &node_id,
263                                &authorized_voter,
264                                &node_id,
265                                0,
266                                100,
267                            ),
268                            authorized_voter,
269                        }
270                    })
271                    .take(num_vote_accounts_per_node)
272                    .collect(),
273                )
274            })
275            .collect()
276    }
277
278    fn new_epoch_vote_accounts(
279        vote_accounts_map: &HashMap<Pubkey, Vec<VoteAccountInfo>>,
280        node_id_to_stake_fn: impl Fn(&Pubkey) -> u64,
281    ) -> VoteAccountsHashMap {
282        // Create and process the vote accounts
283        vote_accounts_map
284            .iter()
285            .flat_map(|(node_id, vote_accounts)| {
286                vote_accounts.iter().map(|v| {
287                    let vote_account = VoteAccount::try_from(v.account.clone()).unwrap();
288                    (v.vote_account, (node_id_to_stake_fn(node_id), vote_account))
289                })
290            })
291            .collect()
292    }
293
294    #[test]
295    fn test_parse_epoch_vote_accounts() {
296        let stake_per_account = 100;
297        let num_vote_accounts_per_node = 2;
298        let num_nodes = 10;
299
300        let vote_accounts_map = new_vote_accounts(num_nodes, num_vote_accounts_per_node);
301
302        let expected_authorized_voters: HashMap<_, _> = vote_accounts_map
303            .iter()
304            .flat_map(|(_, vote_accounts)| {
305                vote_accounts
306                    .iter()
307                    .map(|v| (v.vote_account, v.authorized_voter))
308            })
309            .collect();
310
311        let expected_node_id_to_vote_accounts: HashMap<_, _> = vote_accounts_map
312            .iter()
313            .map(|(node_pubkey, vote_accounts)| {
314                let mut vote_accounts = vote_accounts
315                    .iter()
316                    .map(|v| (v.vote_account))
317                    .collect::<Vec<_>>();
318                vote_accounts.sort();
319                let node_vote_accounts = NodeVoteAccounts {
320                    vote_accounts,
321                    total_stake: stake_per_account * num_vote_accounts_per_node as u64,
322                };
323                (*node_pubkey, node_vote_accounts)
324            })
325            .collect();
326
327        let epoch_vote_accounts =
328            new_epoch_vote_accounts(&vote_accounts_map, |_| stake_per_account);
329
330        let (total_stake, mut node_id_to_vote_accounts, epoch_authorized_voters) =
331            EpochStakes::parse_epoch_vote_accounts(&epoch_vote_accounts, 0);
332
333        // Verify the results
334        node_id_to_vote_accounts
335            .iter_mut()
336            .for_each(|(_, node_vote_accounts)| node_vote_accounts.vote_accounts.sort());
337
338        assert!(
339            node_id_to_vote_accounts.len() == expected_node_id_to_vote_accounts.len()
340                && node_id_to_vote_accounts
341                    .iter()
342                    .all(|(k, v)| expected_node_id_to_vote_accounts.get(k).unwrap() == v)
343        );
344        assert!(
345            epoch_authorized_voters.len() == expected_authorized_voters.len()
346                && epoch_authorized_voters
347                    .iter()
348                    .all(|(k, v)| expected_authorized_voters.get(k).unwrap() == v)
349        );
350        assert_eq!(
351            total_stake,
352            num_nodes as u64 * num_vote_accounts_per_node as u64 * 100
353        );
354    }
355
356    fn create_test_stakes() -> Stakes<StakeAccount<Delegation>> {
357        let stakes_cache = StakesCache::new(Stakes::default());
358
359        let vote_pubkey = Pubkey::new_unique();
360        let vote_account = vote_state::create_account_with_authorized(
361            &Pubkey::new_unique(),
362            &Pubkey::new_unique(),
363            &Pubkey::new_unique(),
364            0,
365            1,
366        );
367
368        let stake = 1_000_000_000;
369        let stake_pubkey = Pubkey::new_unique();
370        let stake_account = stake_state::create_account(
371            &Pubkey::new_unique(),
372            &vote_pubkey,
373            &vote_account,
374            &Rent::default(),
375            stake,
376        );
377
378        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
379        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
380
381        let stakes = Stakes::clone(&stakes_cache.stakes());
382
383        stakes
384    }
385
386    #[test]
387    fn test_split_epoch_stakes_empty() {
388        let bank_epoch_stakes = HashMap::new();
389        let (old, versioned) = split_epoch_stakes(bank_epoch_stakes);
390        assert!(old.is_empty());
391        assert!(versioned.is_empty());
392    }
393
394    #[test]
395    fn test_split_epoch_stakes_delegations() {
396        let mut bank_epoch_stakes = HashMap::new();
397        let epoch = 0;
398        let stakes = Arc::new(StakesEnum::Delegations(create_test_stakes().into()));
399        let epoch_stakes = EpochStakes {
400            stakes,
401            total_stake: 100,
402            node_id_to_vote_accounts: Arc::new(HashMap::new()),
403            epoch_authorized_voters: Arc::new(HashMap::new()),
404        };
405        bank_epoch_stakes.insert(epoch, epoch_stakes.clone());
406
407        let (old, versioned) = split_epoch_stakes(bank_epoch_stakes);
408
409        assert_eq!(old.len(), 1);
410        assert_eq!(old.get(&epoch), Some(&epoch_stakes));
411        assert!(versioned.is_empty());
412    }
413
414    #[test]
415    fn test_split_epoch_stakes_accounts() {
416        let mut bank_epoch_stakes = HashMap::new();
417        let epoch = 0;
418        let test_stakes = create_test_stakes();
419        let stakes = Arc::new(StakesEnum::Accounts(test_stakes.clone()));
420        let epoch_stakes = EpochStakes {
421            stakes,
422            total_stake: 100,
423            node_id_to_vote_accounts: Arc::new(HashMap::new()),
424            epoch_authorized_voters: Arc::new(HashMap::new()),
425        };
426        bank_epoch_stakes.insert(epoch, epoch_stakes.clone());
427
428        let (old, versioned) = split_epoch_stakes(bank_epoch_stakes);
429
430        assert!(old.is_empty());
431        assert_eq!(versioned.len(), 1);
432        assert_eq!(
433            versioned.get(&epoch),
434            Some(&VersionedEpochStakes::Current {
435                stakes: SerdeStakesToStakeFormat::Account(test_stakes),
436                total_stake: epoch_stakes.total_stake,
437                node_id_to_vote_accounts: epoch_stakes.node_id_to_vote_accounts,
438                epoch_authorized_voters: epoch_stakes.epoch_authorized_voters,
439            })
440        );
441    }
442
443    #[test]
444    fn test_split_epoch_stakes_stakes() {
445        let mut bank_epoch_stakes = HashMap::new();
446        let epoch = 0;
447        let test_stakes: Stakes<Stake> = create_test_stakes().into();
448        let stakes = Arc::new(StakesEnum::Stakes(test_stakes.clone()));
449        let epoch_stakes = EpochStakes {
450            stakes,
451            total_stake: 100,
452            node_id_to_vote_accounts: Arc::new(HashMap::new()),
453            epoch_authorized_voters: Arc::new(HashMap::new()),
454        };
455        bank_epoch_stakes.insert(epoch, epoch_stakes.clone());
456
457        let (old, versioned) = split_epoch_stakes(bank_epoch_stakes);
458
459        assert!(old.is_empty());
460        assert_eq!(versioned.len(), 1);
461        assert_eq!(
462            versioned.get(&epoch),
463            Some(&VersionedEpochStakes::Current {
464                stakes: SerdeStakesToStakeFormat::Stake(test_stakes),
465                total_stake: epoch_stakes.total_stake,
466                node_id_to_vote_accounts: epoch_stakes.node_id_to_vote_accounts,
467                epoch_authorized_voters: epoch_stakes.epoch_authorized_voters,
468            })
469        );
470    }
471
472    #[test]
473    fn test_split_epoch_stakes_mixed() {
474        let mut bank_epoch_stakes = HashMap::new();
475
476        // Delegations
477        let epoch1 = 0;
478        let stakes1 = Arc::new(StakesEnum::Delegations(Stakes::default()));
479        let epoch_stakes1 = EpochStakes {
480            stakes: stakes1,
481            total_stake: 100,
482            node_id_to_vote_accounts: Arc::new(HashMap::new()),
483            epoch_authorized_voters: Arc::new(HashMap::new()),
484        };
485        bank_epoch_stakes.insert(epoch1, epoch_stakes1);
486
487        // Accounts
488        let epoch2 = 1;
489        let stakes2 = Arc::new(StakesEnum::Accounts(Stakes::default()));
490        let epoch_stakes2 = EpochStakes {
491            stakes: stakes2,
492            total_stake: 200,
493            node_id_to_vote_accounts: Arc::new(HashMap::new()),
494            epoch_authorized_voters: Arc::new(HashMap::new()),
495        };
496        bank_epoch_stakes.insert(epoch2, epoch_stakes2);
497
498        // Stakes
499        let epoch3 = 2;
500        let stakes3 = Arc::new(StakesEnum::Stakes(Stakes::default()));
501        let epoch_stakes3 = EpochStakes {
502            stakes: stakes3,
503            total_stake: 300,
504            node_id_to_vote_accounts: Arc::new(HashMap::new()),
505            epoch_authorized_voters: Arc::new(HashMap::new()),
506        };
507        bank_epoch_stakes.insert(epoch3, epoch_stakes3);
508
509        let (old, versioned) = split_epoch_stakes(bank_epoch_stakes);
510
511        assert_eq!(old.len(), 1);
512        assert!(old.contains_key(&epoch1));
513
514        assert_eq!(versioned.len(), 2);
515        assert_eq!(
516            versioned.get(&epoch2),
517            Some(&VersionedEpochStakes::Current {
518                stakes: SerdeStakesToStakeFormat::Account(Stakes::default()),
519                total_stake: 200,
520                node_id_to_vote_accounts: Arc::default(),
521                epoch_authorized_voters: Arc::default(),
522            })
523        );
524        assert_eq!(
525            versioned.get(&epoch3),
526            Some(&VersionedEpochStakes::Current {
527                stakes: SerdeStakesToStakeFormat::Stake(Stakes::default()),
528                total_stake: 300,
529                node_id_to_vote_accounts: Arc::default(),
530                epoch_authorized_voters: Arc::default(),
531            })
532        );
533    }
534
535    #[test]
536    fn test_node_id_to_stake() {
537        let num_nodes = 10;
538        let num_vote_accounts_per_node = 2;
539
540        let vote_accounts_map = new_vote_accounts(num_nodes, num_vote_accounts_per_node);
541        let node_id_to_stake_map = vote_accounts_map
542            .keys()
543            .enumerate()
544            .map(|(index, node_id)| (*node_id, ((index + 1) * 100) as u64))
545            .collect::<HashMap<_, _>>();
546        let epoch_vote_accounts = new_epoch_vote_accounts(&vote_accounts_map, |node_id| {
547            *node_id_to_stake_map.get(node_id).unwrap()
548        });
549        let epoch_stakes = EpochStakes::new_for_tests(epoch_vote_accounts, 0);
550
551        assert_eq!(epoch_stakes.total_stake(), 11000);
552        for (node_id, stake) in node_id_to_stake_map.iter() {
553            assert_eq!(
554                epoch_stakes.node_id_to_stake(node_id),
555                Some(*stake * num_vote_accounts_per_node as u64)
556            );
557        }
558    }
559}