solana_runtime/
epoch_stakes.rs

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