atlas_runtime/
stakes.rs

1//! Stakes serve as a cache of stake and vote accounts to derive
2//! node stakes
3#[cfg(feature = "dev-context-only-utils")]
4use solana_stake_interface::state::Stake;
5use {
6    crate::{stake_account, stake_history::StakeHistory},
7    im::HashMap as ImHashMap,
8    log::error,
9    num_derive::ToPrimitive,
10    rayon::{prelude::*, ThreadPool},
11    solana_account::{AccountSharedData, ReadableAccount},
12    solana_clock::Epoch,
13    solana_pubkey::Pubkey,
14    solana_stake_interface::state::{Delegation, StakeActivationStatus},
15    solana_vote::vote_account::{VoteAccount, VoteAccounts},
16    solana_vote_interface::state::VoteStateVersions,
17    std::{
18        collections::HashMap,
19        ops::Add,
20        sync::{Arc, RwLock, RwLockReadGuard},
21    },
22    thiserror::Error,
23};
24
25mod serde_stakes;
26pub(crate) use serde_stakes::serialize_stake_accounts_to_delegation_format;
27pub use serde_stakes::SerdeStakesToStakeFormat;
28
29#[derive(Debug, Error)]
30pub enum Error {
31    #[error("Invalid delegation: {0}")]
32    InvalidDelegation(Pubkey),
33    #[error(transparent)]
34    InvalidStakeAccount(#[from] stake_account::Error),
35    #[error("Stake account not found: {0}")]
36    StakeAccountNotFound(Pubkey),
37    #[error("Vote account mismatch: {0}")]
38    VoteAccountMismatch(Pubkey),
39    #[error("Vote account not cached: {0}")]
40    VoteAccountNotCached(Pubkey),
41    #[error("Vote account not found: {0}")]
42    VoteAccountNotFound(Pubkey),
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, ToPrimitive)]
46pub enum InvalidCacheEntryReason {
47    Missing,
48    BadState,
49    WrongOwner,
50}
51
52type StakeAccount = stake_account::StakeAccount<Delegation>;
53
54#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
55#[derive(Default, Debug)]
56pub(crate) struct StakesCache(RwLock<Stakes<StakeAccount>>);
57
58impl StakesCache {
59    pub(crate) fn new(stakes: Stakes<StakeAccount>) -> Self {
60        Self(RwLock::new(stakes))
61    }
62
63    pub(crate) fn stakes(&self) -> RwLockReadGuard<Stakes<StakeAccount>> {
64        self.0.read().unwrap()
65    }
66
67    pub(crate) fn check_and_store(
68        &self,
69        pubkey: &Pubkey,
70        account: &impl ReadableAccount,
71        new_rate_activation_epoch: Option<Epoch>,
72    ) {
73        // TODO: If the account is already cached as a vote or stake account
74        // but the owner changes, then this needs to evict the account from
75        // the cache. see:
76        // https://github.com/atlas-labs/atlas/pull/24200#discussion_r849935444
77        let owner = account.owner();
78        // Zero lamport accounts are not stored in accounts-db
79        // and so should be removed from cache as well.
80        if account.lamports() == 0 {
81            if solana_vote_program::check_id(owner) {
82                let _old_vote_account = {
83                    let mut stakes = self.0.write().unwrap();
84                    stakes.remove_vote_account(pubkey)
85                };
86            } else if solana_stake_program::check_id(owner) {
87                let mut stakes = self.0.write().unwrap();
88                stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch);
89            }
90            return;
91        }
92        debug_assert_ne!(account.lamports(), 0u64);
93        if solana_vote_program::check_id(owner) {
94            if VoteStateVersions::is_correct_size_and_initialized(account.data()) {
95                match VoteAccount::try_from(account.to_account_shared_data()) {
96                    Ok(vote_account) => {
97                        // drop the old account after releasing the lock
98                        let _old_vote_account = {
99                            let mut stakes = self.0.write().unwrap();
100                            stakes.upsert_vote_account(
101                                pubkey,
102                                vote_account,
103                                new_rate_activation_epoch,
104                            )
105                        };
106                    }
107                    Err(_) => {
108                        // drop the old account after releasing the lock
109                        let _old_vote_account = {
110                            let mut stakes = self.0.write().unwrap();
111                            stakes.remove_vote_account(pubkey)
112                        };
113                    }
114                }
115            } else {
116                // drop the old account after releasing the lock
117                let _old_vote_account = {
118                    let mut stakes = self.0.write().unwrap();
119                    stakes.remove_vote_account(pubkey)
120                };
121            };
122        } else if solana_stake_program::check_id(owner) {
123            match StakeAccount::try_from(account.to_account_shared_data()) {
124                Ok(stake_account) => {
125                    let mut stakes = self.0.write().unwrap();
126                    stakes.upsert_stake_delegation(
127                        *pubkey,
128                        stake_account,
129                        new_rate_activation_epoch,
130                    );
131                }
132                Err(_) => {
133                    let mut stakes = self.0.write().unwrap();
134                    stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch);
135                }
136            }
137        }
138    }
139
140    pub(crate) fn activate_epoch(
141        &self,
142        next_epoch: Epoch,
143        thread_pool: &ThreadPool,
144        new_rate_activation_epoch: Option<Epoch>,
145    ) {
146        let mut stakes = self.0.write().unwrap();
147        stakes.activate_epoch(next_epoch, thread_pool, new_rate_activation_epoch)
148    }
149}
150
151/// The generic type T is either Delegation or StakeAccount.
152/// [`Stakes<Delegation>`] is equivalent to the old code and is used for backward
153/// compatibility in [`crate::bank::BankFieldsToDeserialize`].
154/// But banks cache [`Stakes<StakeAccount>`] which includes the entire stake
155/// account and StakeStateV2 deserialized from the account. Doing so, will remove
156/// the need to load the stake account from accounts-db when working with
157/// stake-delegations.
158#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
159#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
160pub struct Stakes<T: Clone> {
161    /// vote accounts
162    vote_accounts: VoteAccounts,
163
164    /// stake_delegations
165    stake_delegations: ImHashMap<Pubkey, T>,
166
167    /// unused
168    unused: u64,
169
170    /// current epoch, used to calculate current stake
171    epoch: Epoch,
172
173    /// history of staking levels
174    stake_history: StakeHistory,
175}
176
177impl<T: Clone> Stakes<T> {
178    pub fn vote_accounts(&self) -> &VoteAccounts {
179        &self.vote_accounts
180    }
181
182    pub(crate) fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
183        self.vote_accounts.staked_nodes()
184    }
185}
186
187impl Stakes<StakeAccount> {
188    /// Creates a Stake<StakeAccount> from Stake<Delegation> by loading the
189    /// full account state for respective stake pubkeys. get_account function
190    /// should return the account at the respective slot where stakes where
191    /// cached.
192    pub(crate) fn new<F>(stakes: &Stakes<Delegation>, get_account: F) -> Result<Self, Error>
193    where
194        F: Fn(&Pubkey) -> Option<AccountSharedData> + Sync,
195    {
196        let stake_delegations = stakes
197            .stake_delegations
198            .iter()
199            // im::HashMap doesn't support rayon so we manually build a temporary vector. Note this is
200            // what std HashMap::par_iter() does internally too.
201            .collect::<Vec<_>>()
202            .into_par_iter()
203            // We use fold/reduce to aggregate the results, which does a bit more work than calling
204            // collect()/collect_vec_list() and then im::HashMap::from_iter(collected.into_iter()),
205            // but it does it in background threads, so effectively it's faster.
206            .try_fold(ImHashMap::new, |mut map, (pubkey, delegation)| {
207                let Some(stake_account) = get_account(pubkey) else {
208                    return Err(Error::StakeAccountNotFound(*pubkey));
209                };
210
211                // Assert that all valid vote-accounts referenced in stake delegations are already
212                // contained in `stakes.vote_account`.
213                let voter_pubkey = &delegation.voter_pubkey;
214                if stakes.vote_accounts.get(voter_pubkey).is_none() {
215                    if let Some(account) = get_account(voter_pubkey) {
216                        if VoteStateVersions::is_correct_size_and_initialized(account.data())
217                            && VoteAccount::try_from(account.clone()).is_ok()
218                        {
219                            error!("vote account not cached: {voter_pubkey}, {account:?}");
220                            return Err(Error::VoteAccountNotCached(*voter_pubkey));
221                        }
222                    }
223                }
224
225                let stake_account = StakeAccount::try_from(stake_account)?;
226                // Sanity check that the delegation is consistent with what is
227                // stored in the account.
228                if stake_account.delegation() == delegation {
229                    map.insert(*pubkey, stake_account);
230                    Ok(map)
231                } else {
232                    Err(Error::InvalidDelegation(*pubkey))
233                }
234            })
235            .try_reduce(ImHashMap::new, |a, b| Ok(a.union(b)))?;
236
237        // Assert that cached vote accounts are consistent with accounts-db.
238        //
239        // This currently includes ~5500 accounts, parallelizing brings minor
240        // (sub 2s) improvements.
241        for (pubkey, vote_account) in stakes.vote_accounts.iter() {
242            let Some(account) = get_account(pubkey) else {
243                return Err(Error::VoteAccountNotFound(*pubkey));
244            };
245            let vote_account = vote_account.account();
246            if vote_account != &account {
247                error!("vote account mismatch: {pubkey}, {vote_account:?}, {account:?}");
248                return Err(Error::VoteAccountMismatch(*pubkey));
249            }
250        }
251
252        Ok(Self {
253            vote_accounts: stakes.vote_accounts.clone(),
254            stake_delegations,
255            unused: stakes.unused,
256            epoch: stakes.epoch,
257            stake_history: stakes.stake_history.clone(),
258        })
259    }
260
261    #[cfg(feature = "dev-context-only-utils")]
262    pub fn new_for_tests(
263        epoch: Epoch,
264        vote_accounts: VoteAccounts,
265        stake_delegations: ImHashMap<Pubkey, StakeAccount>,
266    ) -> Self {
267        Self {
268            vote_accounts,
269            stake_delegations,
270            unused: 0,
271            epoch,
272            stake_history: StakeHistory::default(),
273        }
274    }
275
276    pub(crate) fn history(&self) -> &StakeHistory {
277        &self.stake_history
278    }
279
280    fn activate_epoch(
281        &mut self,
282        next_epoch: Epoch,
283        thread_pool: &ThreadPool,
284        new_rate_activation_epoch: Option<Epoch>,
285    ) {
286        let stake_delegations: Vec<_> = self.stake_delegations.values().collect();
287        // Wrap up the prev epoch by adding new stake history entry for the
288        // prev epoch.
289        let stake_history_entry = thread_pool.install(|| {
290            stake_delegations
291                .par_iter()
292                .fold(StakeActivationStatus::default, |acc, stake_account| {
293                    let delegation = stake_account.delegation();
294                    acc + delegation.stake_activating_and_deactivating(
295                        self.epoch,
296                        &self.stake_history,
297                        new_rate_activation_epoch,
298                    )
299                })
300                .reduce(StakeActivationStatus::default, Add::add)
301        });
302        self.stake_history.add(self.epoch, stake_history_entry);
303        self.epoch = next_epoch;
304        // Refresh the stake distribution of vote accounts for the next epoch,
305        // using new stake history.
306        self.vote_accounts = refresh_vote_accounts(
307            thread_pool,
308            self.epoch,
309            &self.vote_accounts,
310            &stake_delegations,
311            &self.stake_history,
312            new_rate_activation_epoch,
313        );
314    }
315
316    /// Sum the stakes that point to the given voter_pubkey
317    fn calculate_stake(
318        stake_delegations: &ImHashMap<Pubkey, StakeAccount>,
319        voter_pubkey: &Pubkey,
320        epoch: Epoch,
321        stake_history: &StakeHistory,
322        new_rate_activation_epoch: Option<Epoch>,
323    ) -> u64 {
324        stake_delegations
325            .values()
326            .map(StakeAccount::delegation)
327            .filter(|delegation| &delegation.voter_pubkey == voter_pubkey)
328            .map(|delegation| delegation.stake(epoch, stake_history, new_rate_activation_epoch))
329            .sum()
330    }
331
332    fn remove_vote_account(&mut self, vote_pubkey: &Pubkey) -> Option<VoteAccount> {
333        self.vote_accounts.remove(vote_pubkey).map(|(_, a)| a)
334    }
335
336    fn remove_stake_delegation(
337        &mut self,
338        stake_pubkey: &Pubkey,
339        new_rate_activation_epoch: Option<Epoch>,
340    ) {
341        if let Some(stake_account) = self.stake_delegations.remove(stake_pubkey) {
342            let removed_delegation = stake_account.delegation();
343            let removed_stake = removed_delegation.stake(
344                self.epoch,
345                &self.stake_history,
346                new_rate_activation_epoch,
347            );
348            self.vote_accounts
349                .sub_stake(&removed_delegation.voter_pubkey, removed_stake);
350        }
351    }
352
353    fn upsert_vote_account(
354        &mut self,
355        vote_pubkey: &Pubkey,
356        vote_account: VoteAccount,
357        new_rate_activation_epoch: Option<Epoch>,
358    ) -> Option<VoteAccount> {
359        debug_assert_ne!(vote_account.lamports(), 0u64);
360
361        let stake_delegations = &self.stake_delegations;
362        self.vote_accounts.insert(*vote_pubkey, vote_account, || {
363            Self::calculate_stake(
364                stake_delegations,
365                vote_pubkey,
366                self.epoch,
367                &self.stake_history,
368                new_rate_activation_epoch,
369            )
370        })
371    }
372
373    fn upsert_stake_delegation(
374        &mut self,
375        stake_pubkey: Pubkey,
376        stake_account: StakeAccount,
377        new_rate_activation_epoch: Option<Epoch>,
378    ) {
379        debug_assert_ne!(stake_account.lamports(), 0u64);
380        let delegation = stake_account.delegation();
381        let voter_pubkey = delegation.voter_pubkey;
382        let stake = delegation.stake(self.epoch, &self.stake_history, new_rate_activation_epoch);
383        match self.stake_delegations.insert(stake_pubkey, stake_account) {
384            None => self.vote_accounts.add_stake(&voter_pubkey, stake),
385            Some(old_stake_account) => {
386                let old_delegation = old_stake_account.delegation();
387                let old_voter_pubkey = old_delegation.voter_pubkey;
388                let old_stake = old_delegation.stake(
389                    self.epoch,
390                    &self.stake_history,
391                    new_rate_activation_epoch,
392                );
393                if voter_pubkey != old_voter_pubkey || stake != old_stake {
394                    self.vote_accounts.sub_stake(&old_voter_pubkey, old_stake);
395                    self.vote_accounts.add_stake(&voter_pubkey, stake);
396                }
397            }
398        }
399    }
400
401    pub(crate) fn stake_delegations(&self) -> &ImHashMap<Pubkey, StakeAccount> {
402        &self.stake_delegations
403    }
404
405    pub(crate) fn highest_staked_node(&self) -> Option<&Pubkey> {
406        let vote_account = self.vote_accounts.find_max_by_delegated_stake()?;
407        Some(vote_account.node_pubkey())
408    }
409}
410
411/// This conversion is very memory intensive so should only be used in
412/// development contexts.
413#[cfg(feature = "dev-context-only-utils")]
414impl From<Stakes<StakeAccount>> for Stakes<Delegation> {
415    fn from(stakes: Stakes<StakeAccount>) -> Self {
416        let stake_delegations = stakes
417            .stake_delegations
418            .into_iter()
419            .map(|(pubkey, stake_account)| (pubkey, *stake_account.delegation()))
420            .collect();
421        Self {
422            vote_accounts: stakes.vote_accounts,
423            stake_delegations,
424            unused: stakes.unused,
425            epoch: stakes.epoch,
426            stake_history: stakes.stake_history,
427        }
428    }
429}
430
431/// This conversion is very memory intensive so should only be used in
432/// development contexts.
433#[cfg(feature = "dev-context-only-utils")]
434impl From<Stakes<StakeAccount>> for Stakes<Stake> {
435    fn from(stakes: Stakes<StakeAccount>) -> Self {
436        let stake_delegations = stakes
437            .stake_delegations
438            .into_iter()
439            .map(|(pubkey, stake_account)| (pubkey, *stake_account.stake()))
440            .collect();
441        Self {
442            vote_accounts: stakes.vote_accounts,
443            stake_delegations,
444            unused: stakes.unused,
445            epoch: stakes.epoch,
446            stake_history: stakes.stake_history,
447        }
448    }
449}
450
451/// This conversion is memory intensive so should only be used in development
452/// contexts.
453#[cfg(feature = "dev-context-only-utils")]
454impl From<Stakes<Stake>> for Stakes<Delegation> {
455    fn from(stakes: Stakes<Stake>) -> Self {
456        let stake_delegations = stakes
457            .stake_delegations
458            .into_iter()
459            .map(|(pubkey, stake)| (pubkey, stake.delegation))
460            .collect();
461        Self {
462            vote_accounts: stakes.vote_accounts,
463            stake_delegations,
464            unused: stakes.unused,
465            epoch: stakes.epoch,
466            stake_history: stakes.stake_history,
467        }
468    }
469}
470
471fn refresh_vote_accounts(
472    thread_pool: &ThreadPool,
473    epoch: Epoch,
474    vote_accounts: &VoteAccounts,
475    stake_delegations: &[&StakeAccount],
476    stake_history: &StakeHistory,
477    new_rate_activation_epoch: Option<Epoch>,
478) -> VoteAccounts {
479    type StakesHashMap = HashMap</*voter:*/ Pubkey, /*stake:*/ u64>;
480    fn merge(mut stakes: StakesHashMap, other: StakesHashMap) -> StakesHashMap {
481        if stakes.len() < other.len() {
482            return merge(other, stakes);
483        }
484        for (pubkey, stake) in other {
485            *stakes.entry(pubkey).or_default() += stake;
486        }
487        stakes
488    }
489    let delegated_stakes = thread_pool.install(|| {
490        stake_delegations
491            .par_iter()
492            .fold(HashMap::default, |mut delegated_stakes, stake_account| {
493                let delegation = stake_account.delegation();
494                let entry = delegated_stakes.entry(delegation.voter_pubkey).or_default();
495                *entry += delegation.stake(epoch, stake_history, new_rate_activation_epoch);
496                delegated_stakes
497            })
498            .reduce(HashMap::default, merge)
499    });
500    vote_accounts
501        .iter()
502        .map(|(&vote_pubkey, vote_account)| {
503            let delegated_stake = delegated_stakes
504                .get(&vote_pubkey)
505                .copied()
506                .unwrap_or_default();
507            (vote_pubkey, (delegated_stake, vote_account.clone()))
508        })
509        .collect()
510}
511
512#[cfg(test)]
513pub(crate) mod tests {
514    use {
515        super::*,
516        rayon::ThreadPoolBuilder,
517        solana_account::WritableAccount,
518        solana_pubkey::Pubkey,
519        solana_rent::Rent,
520        solana_stake_interface as stake,
521        solana_stake_program::stake_state,
522        solana_vote_interface::state::{VoteStateV3, VoteStateVersions},
523        solana_vote_program::vote_state,
524    };
525
526    //  set up some dummies for a staked node     ((     vote      )  (     stake     ))
527    pub(crate) fn create_staked_node_accounts(
528        stake: u64,
529    ) -> ((Pubkey, AccountSharedData), (Pubkey, AccountSharedData)) {
530        let vote_pubkey = solana_pubkey::new_rand();
531        let vote_account =
532            vote_state::create_account(&vote_pubkey, &solana_pubkey::new_rand(), 0, 1);
533        let stake_pubkey = solana_pubkey::new_rand();
534        (
535            (vote_pubkey, vote_account),
536            (
537                stake_pubkey,
538                create_stake_account(stake, &vote_pubkey, &stake_pubkey),
539            ),
540        )
541    }
542
543    //   add stake to a vote_pubkey                               (   stake    )
544    pub(crate) fn create_stake_account(
545        stake: u64,
546        vote_pubkey: &Pubkey,
547        stake_pubkey: &Pubkey,
548    ) -> AccountSharedData {
549        stake_state::create_account(
550            stake_pubkey,
551            vote_pubkey,
552            &vote_state::create_account(vote_pubkey, &solana_pubkey::new_rand(), 0, 1),
553            &Rent::free(),
554            stake,
555        )
556    }
557
558    #[test]
559    fn test_stakes_basic() {
560        for i in 0..4 {
561            let stakes_cache = StakesCache::new(Stakes {
562                epoch: i,
563                ..Stakes::default()
564            });
565
566            let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) =
567                create_staked_node_accounts(10);
568
569            stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
570            stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
571            let stake = stake_state::stake_from(&stake_account).unwrap();
572            {
573                let stakes = stakes_cache.stakes();
574                let vote_accounts = stakes.vote_accounts();
575                assert!(vote_accounts.get(&vote_pubkey).is_some());
576                assert_eq!(
577                    vote_accounts.get_delegated_stake(&vote_pubkey),
578                    stake.stake(i, &StakeHistory::default(), None)
579                );
580            }
581
582            stake_account.set_lamports(42);
583            stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
584            {
585                let stakes = stakes_cache.stakes();
586                let vote_accounts = stakes.vote_accounts();
587                assert!(vote_accounts.get(&vote_pubkey).is_some());
588                assert_eq!(
589                    vote_accounts.get_delegated_stake(&vote_pubkey),
590                    stake.stake(i, &StakeHistory::default(), None)
591                ); // stays old stake, because only 10 is activated
592            }
593
594            // activate more
595            let mut stake_account =
596                create_stake_account(42, &vote_pubkey, &solana_pubkey::new_rand());
597            stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
598            let stake = stake_state::stake_from(&stake_account).unwrap();
599            {
600                let stakes = stakes_cache.stakes();
601                let vote_accounts = stakes.vote_accounts();
602                assert!(vote_accounts.get(&vote_pubkey).is_some());
603                assert_eq!(
604                    vote_accounts.get_delegated_stake(&vote_pubkey),
605                    stake.stake(i, &StakeHistory::default(), None)
606                ); // now stake of 42 is activated
607            }
608
609            stake_account.set_lamports(0);
610            stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
611            {
612                let stakes = stakes_cache.stakes();
613                let vote_accounts = stakes.vote_accounts();
614                assert!(vote_accounts.get(&vote_pubkey).is_some());
615                assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
616            }
617        }
618    }
619
620    #[test]
621    fn test_stakes_highest() {
622        let stakes_cache = StakesCache::default();
623
624        assert_eq!(stakes_cache.stakes().highest_staked_node(), None);
625
626        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
627            create_staked_node_accounts(10);
628
629        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
630        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
631
632        let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) =
633            create_staked_node_accounts(20);
634
635        stakes_cache.check_and_store(&vote11_pubkey, &vote11_account, None);
636        stakes_cache.check_and_store(&stake11_pubkey, &stake11_account, None);
637
638        let vote11_node_pubkey = vote_state::from(&vote11_account).unwrap().node_pubkey;
639
640        let highest_staked_node = stakes_cache.stakes().highest_staked_node().copied();
641        assert_eq!(highest_staked_node, Some(vote11_node_pubkey));
642    }
643
644    #[test]
645    fn test_stakes_vote_account_disappear_reappear() {
646        let stakes_cache = StakesCache::new(Stakes {
647            epoch: 4,
648            ..Stakes::default()
649        });
650
651        let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
652            create_staked_node_accounts(10);
653
654        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
655        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
656
657        {
658            let stakes = stakes_cache.stakes();
659            let vote_accounts = stakes.vote_accounts();
660            assert!(vote_accounts.get(&vote_pubkey).is_some());
661            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 10);
662        }
663
664        vote_account.set_lamports(0);
665        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
666
667        {
668            let stakes = stakes_cache.stakes();
669            let vote_accounts = stakes.vote_accounts();
670            assert!(vote_accounts.get(&vote_pubkey).is_none());
671            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
672        }
673
674        vote_account.set_lamports(1);
675        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
676
677        {
678            let stakes = stakes_cache.stakes();
679            let vote_accounts = stakes.vote_accounts();
680            assert!(vote_accounts.get(&vote_pubkey).is_some());
681            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 10);
682        }
683
684        // Vote account too big
685        let cache_data = vote_account.data().to_vec();
686        let mut pushed = vote_account.data().to_vec();
687        pushed.push(0);
688        vote_account.set_data(pushed);
689        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
690
691        {
692            let stakes = stakes_cache.stakes();
693            let vote_accounts = stakes.vote_accounts();
694            assert!(vote_accounts.get(&vote_pubkey).is_none());
695            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
696        }
697
698        // Vote account uninitialized
699        let default_vote_state = VoteStateV3::default();
700        let versioned = VoteStateVersions::new_v3(default_vote_state);
701        vote_state::to(&versioned, &mut vote_account).unwrap();
702        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
703
704        {
705            let stakes = stakes_cache.stakes();
706            let vote_accounts = stakes.vote_accounts();
707            assert!(vote_accounts.get(&vote_pubkey).is_none());
708            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
709        }
710
711        vote_account.set_data(cache_data);
712        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
713
714        {
715            let stakes = stakes_cache.stakes();
716            let vote_accounts = stakes.vote_accounts();
717            assert!(vote_accounts.get(&vote_pubkey).is_some());
718            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 10);
719        }
720    }
721
722    #[test]
723    fn test_stakes_change_delegate() {
724        let stakes_cache = StakesCache::new(Stakes {
725            epoch: 4,
726            ..Stakes::default()
727        });
728
729        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
730            create_staked_node_accounts(10);
731
732        let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) =
733            create_staked_node_accounts(10);
734
735        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
736        stakes_cache.check_and_store(&vote_pubkey2, &vote_account2, None);
737
738        // delegates to vote_pubkey
739        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
740
741        let stake = stake_state::stake_from(&stake_account).unwrap();
742
743        {
744            let stakes = stakes_cache.stakes();
745            let vote_accounts = stakes.vote_accounts();
746            assert!(vote_accounts.get(&vote_pubkey).is_some());
747            assert_eq!(
748                vote_accounts.get_delegated_stake(&vote_pubkey),
749                stake.stake(stakes.epoch, &stakes.stake_history, None)
750            );
751            assert!(vote_accounts.get(&vote_pubkey2).is_some());
752            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey2), 0);
753        }
754
755        // delegates to vote_pubkey2
756        stakes_cache.check_and_store(&stake_pubkey, &stake_account2, None);
757
758        {
759            let stakes = stakes_cache.stakes();
760            let vote_accounts = stakes.vote_accounts();
761            assert!(vote_accounts.get(&vote_pubkey).is_some());
762            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
763            assert!(vote_accounts.get(&vote_pubkey2).is_some());
764            assert_eq!(
765                vote_accounts.get_delegated_stake(&vote_pubkey2),
766                stake.stake(stakes.epoch, &stakes.stake_history, None)
767            );
768        }
769    }
770    #[test]
771    fn test_stakes_multiple_stakers() {
772        let stakes_cache = StakesCache::new(Stakes {
773            epoch: 4,
774            ..Stakes::default()
775        });
776
777        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
778            create_staked_node_accounts(10);
779
780        let stake_pubkey2 = solana_pubkey::new_rand();
781        let stake_account2 = create_stake_account(10, &vote_pubkey, &stake_pubkey2);
782
783        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
784
785        // delegates to vote_pubkey
786        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
787        stakes_cache.check_and_store(&stake_pubkey2, &stake_account2, None);
788
789        {
790            let stakes = stakes_cache.stakes();
791            let vote_accounts = stakes.vote_accounts();
792            assert!(vote_accounts.get(&vote_pubkey).is_some());
793            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 20);
794        }
795    }
796
797    #[test]
798    fn test_activate_epoch() {
799        let stakes_cache = StakesCache::default();
800
801        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
802            create_staked_node_accounts(10);
803
804        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
805        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
806        let stake = stake_state::stake_from(&stake_account).unwrap();
807
808        {
809            let stakes = stakes_cache.stakes();
810            let vote_accounts = stakes.vote_accounts();
811            assert_eq!(
812                vote_accounts.get_delegated_stake(&vote_pubkey),
813                stake.stake(stakes.epoch, &stakes.stake_history, None)
814            );
815        }
816        let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
817        stakes_cache.activate_epoch(3, &thread_pool, None);
818        {
819            let stakes = stakes_cache.stakes();
820            let vote_accounts = stakes.vote_accounts();
821            assert_eq!(
822                vote_accounts.get_delegated_stake(&vote_pubkey),
823                stake.stake(stakes.epoch, &stakes.stake_history, None)
824            );
825        }
826    }
827
828    #[test]
829    fn test_stakes_not_delegate() {
830        let stakes_cache = StakesCache::new(Stakes {
831            epoch: 4,
832            ..Stakes::default()
833        });
834
835        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
836            create_staked_node_accounts(10);
837
838        stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
839        stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
840
841        {
842            let stakes = stakes_cache.stakes();
843            let vote_accounts = stakes.vote_accounts();
844            assert!(vote_accounts.get(&vote_pubkey).is_some());
845            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 10);
846        }
847
848        // not a stake account, and whacks above entry
849        stakes_cache.check_and_store(
850            &stake_pubkey,
851            &AccountSharedData::new(1, 0, &stake::program::id()),
852            None,
853        );
854        {
855            let stakes = stakes_cache.stakes();
856            let vote_accounts = stakes.vote_accounts();
857            assert!(vote_accounts.get(&vote_pubkey).is_some());
858            assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey), 0);
859        }
860    }
861}