Skip to main content

solana_runtime/
epoch_stakes.rs

1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4    crate::{stake_history::StakeHistory, stakes::SerdeStakesToStakeFormat},
5    serde::{
6        Deserialize, Deserializer, Serialize, Serializer,
7        de::{SeqAccess, Visitor},
8    },
9    solana_bls_signatures::pubkey::{
10        PopVerified, PubkeyAffine as BLSPubkeyAffine, PubkeyCompressed as BLSPubkeyCompressed,
11    },
12    solana_clock::Epoch,
13    solana_pubkey::Pubkey,
14    solana_stake_interface::state::Stake,
15    solana_vote::vote_account::{VoteAccounts, VoteAccountsHashMap},
16    solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE,
17    std::{
18        collections::HashMap,
19        fmt,
20        num::NonZero,
21        sync::{Arc, OnceLock},
22    },
23};
24
25pub type NodeIdToVoteAccounts = HashMap<Pubkey, NodeVoteAccounts>;
26pub type EpochAuthorizedVoters = HashMap<Pubkey, Pubkey>;
27
28/// Entry in the [`BLSPubkeyToRankMap`] associating a validator's identity
29/// pubkey and BLS pubkey with its stake.
30#[derive(Clone, Debug)]
31#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
32pub struct BLSPubkeyStakeEntry {
33    /// The address containing the vote account
34    pub vote_account_pubkey: Pubkey,
35    /// The identity of the validator specified in the vote account
36    pub node_pubkey: Pubkey,
37    /// The bls pubkey of the validator specified in the vote account
38    pub bls_pubkey: PopVerified<BLSPubkeyAffine>,
39    /// The stake of the validator
40    pub stake: NonZero<u64>,
41}
42
43/// Container to store a mapping from validator [`BLSPubkeyAffine`] to rank.
44///
45/// A validator with a smaller rank has a higher stake.
46/// Container also supports lookups from rank to [`BLSPubkeyStakeEntry`].
47#[derive(Clone, Debug)]
48#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
49pub struct BLSPubkeyToRankMap {
50    rank_map: HashMap<BLSPubkeyCompressed, u16>,
51    vote_pubkey_to_rank: HashMap<Pubkey, u16>,
52    sorted_pubkeys: Vec<BLSPubkeyStakeEntry>,
53    total_stake: NonZero<u64>,
54}
55
56// We cannot auto derive `AbiExample` for `BLSPubkeyToRankMap` because
57// the `BLSPubkeyAffine` type does not implement `AbiExample` or `Default`.
58#[cfg(feature = "frozen-abi")]
59impl solana_frozen_abi::abi_example::AbiExample for BLSPubkeyToRankMap {
60    fn example() -> Self {
61        Self {
62            rank_map: HashMap::new(),
63            vote_pubkey_to_rank: HashMap::new(),
64            sorted_pubkeys: Vec::new(),
65            total_stake: NonZero::new(1).unwrap(),
66        }
67    }
68}
69
70pub(crate) fn bls_pubkey_compressed_bytes_to_bls_pubkey(
71    bls_pubkey_compressed_bytes: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
72) -> Option<(BLSPubkeyCompressed, PopVerified<BLSPubkeyAffine>)> {
73    let bls_pubkey_compressed: BLSPubkeyCompressed =
74        bincode::deserialize(&bls_pubkey_compressed_bytes).ok()?;
75    let bls_pubkey_affine = BLSPubkeyAffine::try_from(bls_pubkey_compressed).ok()?;
76    // It is safe to use `new_unchecked` here because data coming from the vote
77    // state has already had its PoP verified.
78    let bls_pubkey_pop_verified = unsafe { PopVerified::new_unchecked(bls_pubkey_affine) };
79    Some((bls_pubkey_compressed, bls_pubkey_pop_verified))
80}
81
82impl BLSPubkeyToRankMap {
83    pub fn new(epoch_vote_accounts_hash_map: &VoteAccountsHashMap) -> Self {
84        let mut candidates = Vec::with_capacity(epoch_vote_accounts_hash_map.len());
85        let mut bls_pubkey_counts = HashMap::new();
86        let mut node_pubkey_counts = HashMap::new();
87        for (&vote_account_pubkey, (stake, account)) in epoch_vote_accounts_hash_map {
88            let Some(stake) = NonZero::new(*stake) else {
89                continue;
90            };
91            let node_pubkey = *account.vote_state_view().node_pubkey();
92            let Some((bls_pubkey_compressed, bls_pubkey)) = account
93                .vote_state_view()
94                .bls_pubkey_compressed()
95                .and_then(bls_pubkey_compressed_bytes_to_bls_pubkey)
96            else {
97                continue;
98            };
99            let entry = BLSPubkeyStakeEntry {
100                vote_account_pubkey,
101                node_pubkey,
102                bls_pubkey,
103                stake,
104            };
105            *bls_pubkey_counts.entry(bls_pubkey_compressed).or_insert(0) += 1;
106            *node_pubkey_counts.entry(node_pubkey).or_insert(0) += 1;
107            candidates.push((entry, bls_pubkey_compressed));
108        }
109        let mut keys_stake_entry_with_compressed: Vec<(BLSPubkeyStakeEntry, BLSPubkeyCompressed)> =
110            candidates
111                .into_iter()
112                .filter_map(|(entry, bls_pubkey_compressed)| {
113                    (bls_pubkey_counts[&bls_pubkey_compressed] == 1
114                        && node_pubkey_counts[&entry.node_pubkey] == 1)
115                        .then_some((entry, bls_pubkey_compressed))
116                })
117                .collect();
118        let total_stake = keys_stake_entry_with_compressed
119            .iter()
120            .fold(0u64, |stake, (entry, _)| {
121                stake.saturating_add(entry.stake.get())
122            });
123        let total_stake = NonZero::new(total_stake).expect("total stakes should not be 0");
124        keys_stake_entry_with_compressed.sort_by(
125            |(a_entry, a_pubkey_compressed), (b_entry, b_pubkey_compressed)| {
126                b_entry
127                    .stake
128                    .cmp(&a_entry.stake)
129                    .then(a_pubkey_compressed.cmp(b_pubkey_compressed))
130            },
131        );
132        let mut sorted_pubkeys = Vec::with_capacity(keys_stake_entry_with_compressed.len());
133        let mut bls_pubkey_to_rank_map =
134            HashMap::with_capacity(keys_stake_entry_with_compressed.len());
135        let mut vote_pubkey_to_rank_map =
136            HashMap::with_capacity(keys_stake_entry_with_compressed.len());
137        for (rank, (entry, bls_pubkey_compressed)) in
138            keys_stake_entry_with_compressed.into_iter().enumerate()
139        {
140            vote_pubkey_to_rank_map.insert(entry.vote_account_pubkey, rank as u16);
141            bls_pubkey_to_rank_map.insert(bls_pubkey_compressed, rank as u16);
142            sorted_pubkeys.push(entry);
143        }
144        Self {
145            rank_map: bls_pubkey_to_rank_map,
146            vote_pubkey_to_rank: vote_pubkey_to_rank_map,
147            sorted_pubkeys,
148            total_stake,
149        }
150    }
151
152    pub fn is_empty(&self) -> bool {
153        self.rank_map.is_empty()
154    }
155
156    pub fn len(&self) -> usize {
157        self.sorted_pubkeys.len()
158    }
159
160    pub fn total_stake(&self) -> NonZero<u64> {
161        self.total_stake
162    }
163
164    pub fn get_rank(&self, bls_pubkey: &PopVerified<BLSPubkeyAffine>) -> Option<&u16> {
165        let bls_pubkey_compressed = BLSPubkeyCompressed(bls_pubkey.to_bytes_compressed());
166        self.rank_map.get(&bls_pubkey_compressed)
167    }
168
169    pub fn get_rank_for_vote_pubkey(&self, vote_pubkey: &Pubkey) -> Option<&u16> {
170        self.vote_pubkey_to_rank.get(vote_pubkey)
171    }
172
173    pub fn get_pubkey_stake_entry(&self, index: usize) -> Option<&BLSPubkeyStakeEntry> {
174        self.sorted_pubkeys.get(index)
175    }
176}
177
178#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
179#[derive(Clone, Serialize, Debug, Deserialize, Default, PartialEq, Eq)]
180pub struct NodeVoteAccounts {
181    pub vote_accounts: Vec<Pubkey>,
182    pub total_stake: u64,
183}
184
185/// Simplified, intermediate representation of [`VersionedEpochStakes`]
186///
187/// Its bincode serializaiton format is identical as `VersionedEpochStakes`, but allows faster
188/// deserialization by ignoring serialized stake delegations entirely.
189#[derive(Clone, Debug, Deserialize)]
190#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
191pub(crate) enum DeserializableVersionedEpochStakes {
192    Current {
193        stakes: DeserializableEpochStakes,
194        total_stake: u64,
195        node_id_to_vote_accounts: NodeIdToVoteAccounts,
196        epoch_authorized_voters: EpochAuthorizedVoters,
197    },
198}
199
200#[derive(Clone, Debug, Serialize)]
201#[cfg_attr(
202    feature = "frozen-abi",
203    derive(AbiExample, AbiEnumVisitor, StableAbi, StableAbiSample)
204)]
205#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
206pub enum VersionedEpochStakes {
207    Current {
208        stakes: EpochStakes,
209        /// Total stake in Lamports
210        total_stake: u64,
211        node_id_to_vote_accounts: Arc<NodeIdToVoteAccounts>,
212        epoch_authorized_voters: Arc<EpochAuthorizedVoters>,
213        #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Default::default()"))]
214        #[serde(skip)]
215        bls_pubkey_to_rank_map: OnceLock<Arc<BLSPubkeyToRankMap>>,
216    },
217}
218
219impl From<DeserializableVersionedEpochStakes> for VersionedEpochStakes {
220    fn from(epoch_stakes: DeserializableVersionedEpochStakes) -> Self {
221        let DeserializableVersionedEpochStakes::Current {
222            stakes,
223            total_stake,
224            node_id_to_vote_accounts,
225            epoch_authorized_voters,
226        } = epoch_stakes;
227        Self::Current {
228            stakes: stakes.into(),
229            total_stake,
230            node_id_to_vote_accounts: Arc::new(node_id_to_vote_accounts),
231            epoch_authorized_voters: Arc::new(epoch_authorized_voters),
232            bls_pubkey_to_rank_map: OnceLock::new(),
233        }
234    }
235}
236
237impl VersionedEpochStakes {
238    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
239    pub(crate) fn new(stakes: SerdeStakesToStakeFormat, leader_schedule_epoch: Epoch) -> Self {
240        let stakes = EpochStakes::from(stakes);
241        let epoch_vote_accounts = stakes.vote_accounts();
242        let (total_stake, node_id_to_vote_accounts, epoch_authorized_voters) =
243            Self::parse_epoch_vote_accounts(epoch_vote_accounts.as_ref(), leader_schedule_epoch);
244        Self::Current {
245            stakes,
246            total_stake,
247            node_id_to_vote_accounts: Arc::new(node_id_to_vote_accounts),
248            epoch_authorized_voters: Arc::new(epoch_authorized_voters),
249            bls_pubkey_to_rank_map: OnceLock::new(),
250        }
251    }
252
253    #[cfg(feature = "dev-context-only-utils")]
254    pub fn new_for_tests(
255        vote_accounts_hash_map: VoteAccountsHashMap,
256        leader_schedule_epoch: Epoch,
257    ) -> Self {
258        Self::new(
259            SerdeStakesToStakeFormat::Account(crate::stakes::Stakes::new_for_tests(
260                0,
261                solana_vote::vote_account::VoteAccounts::from(Arc::new(vote_accounts_hash_map)),
262                imbl::HashMap::default(),
263            )),
264            leader_schedule_epoch,
265        )
266    }
267
268    pub fn stakes(&self) -> &EpochStakes {
269        match self {
270            Self::Current { stakes, .. } => stakes,
271        }
272    }
273
274    /// Returns the total stake in Lamports.
275    pub fn total_stake(&self) -> u64 {
276        match self {
277            Self::Current { total_stake, .. } => *total_stake,
278        }
279    }
280
281    #[cfg(feature = "dev-context-only-utils")]
282    pub fn set_total_stake(&mut self, total_stake: u64) {
283        match self {
284            Self::Current {
285                total_stake: total_stake_field,
286                ..
287            } => {
288                *total_stake_field = total_stake;
289            }
290        }
291    }
292
293    pub fn node_id_to_vote_accounts(&self) -> &Arc<NodeIdToVoteAccounts> {
294        match self {
295            Self::Current {
296                node_id_to_vote_accounts,
297                ..
298            } => node_id_to_vote_accounts,
299        }
300    }
301
302    pub fn node_id_to_stake(&self, node_id: &Pubkey) -> Option<u64> {
303        self.node_id_to_vote_accounts()
304            .get(node_id)
305            .map(|x| x.total_stake)
306    }
307
308    pub fn epoch_authorized_voters(&self) -> &Arc<EpochAuthorizedVoters> {
309        match self {
310            Self::Current {
311                epoch_authorized_voters,
312                ..
313            } => epoch_authorized_voters,
314        }
315    }
316
317    pub fn bls_pubkey_to_rank_map(&self) -> &Arc<BLSPubkeyToRankMap> {
318        match self {
319            Self::Current {
320                bls_pubkey_to_rank_map,
321                ..
322            } => bls_pubkey_to_rank_map.get_or_init(|| {
323                Arc::new(BLSPubkeyToRankMap::new(
324                    self.stakes().vote_accounts().as_ref(),
325                ))
326            }),
327        }
328    }
329
330    /// Returns the stake in Lamports for the given vote_account.
331    pub fn vote_account_stake(&self, vote_account: &Pubkey) -> u64 {
332        self.stakes()
333            .vote_accounts()
334            .get_delegated_stake(vote_account)
335    }
336
337    fn parse_epoch_vote_accounts(
338        epoch_vote_accounts: &VoteAccountsHashMap,
339        leader_schedule_epoch: Epoch,
340    ) -> (u64, NodeIdToVoteAccounts, EpochAuthorizedVoters) {
341        let mut node_id_to_vote_accounts: NodeIdToVoteAccounts = HashMap::new();
342        let mut epoch_authorized_voters: EpochAuthorizedVoters = HashMap::new();
343        let mut total_stake: u64 = 0;
344
345        for (key, (stake, account)) in epoch_vote_accounts.iter() {
346            total_stake += *stake;
347
348            if *stake == 0 {
349                continue;
350            }
351
352            let vote_state = account.vote_state_view();
353
354            if let Some(authorized_voter) = vote_state.get_authorized_voter(leader_schedule_epoch) {
355                let node_vote_accounts = node_id_to_vote_accounts
356                    .entry(*vote_state.node_pubkey())
357                    .or_default();
358
359                node_vote_accounts.total_stake += stake;
360                node_vote_accounts.vote_accounts.push(*key);
361
362                epoch_authorized_voters.insert(*key, *authorized_voter);
363            }
364        }
365
366        (
367            total_stake,
368            node_id_to_vote_accounts,
369            epoch_authorized_voters,
370        )
371    }
372}
373
374/// The current version of epoch stakes
375#[derive(Clone, Debug, Default)]
376#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
377#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
378pub struct EpochStakes {
379    epoch: Epoch,
380    vote_accounts: VoteAccounts,
381    stake_history: StakeHistory,
382}
383
384impl EpochStakes {
385    pub fn vote_accounts(&self) -> &VoteAccounts {
386        &self.vote_accounts
387    }
388    pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
389        self.vote_accounts.staked_nodes()
390    }
391}
392
393/// Customization of EpochStakes for snapshot serialization.
394///
395/// Needed because snapshots require additional fields no longer present in EpochStakes.
396impl Serialize for EpochStakes {
397    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
398    where
399        S: Serializer,
400    {
401        #[derive(Serialize)]
402        struct SerializableEpochStakes<'a> {
403            vote_accounts: &'a VoteAccounts,
404            stake_delegations: Vec<(Pubkey, Stake)>,
405            unused: u64,
406            epoch: Epoch,
407            stake_history: &'a StakeHistory,
408        }
409
410        SerializableEpochStakes {
411            vote_accounts: &self.vote_accounts,
412            stake_delegations: Vec::new(), // do not serialize any stake delegations
413            unused: 0,
414            epoch: self.epoch,
415            stake_history: &self.stake_history,
416        }
417        .serialize(serializer)
418    }
419}
420
421impl From<SerdeStakesToStakeFormat> for EpochStakes {
422    fn from(stakes: SerdeStakesToStakeFormat) -> Self {
423        let (epoch, vote_accounts, stake_history) = match stakes {
424            SerdeStakesToStakeFormat::Stake(stakes) => stakes.into_epoch_stakes_fields(),
425            SerdeStakesToStakeFormat::Account(stakes) => stakes.into_epoch_stakes_fields(),
426        };
427        Self {
428            epoch,
429            vote_accounts,
430            stake_history,
431        }
432    }
433}
434
435/// Customization of EpochStakes for snapshot deserialization.
436///
437/// Needed because snapshots contain additional fields no longer present in EpochStakes.
438#[derive(Clone, Debug, Deserialize)]
439#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
440pub(crate) struct DeserializableEpochStakes {
441    vote_accounts: VoteAccounts,
442    #[serde(deserialize_with = "deserialize_and_ignore_stake_delegations")]
443    _stake_delegations: (),
444    _unused: u64,
445    epoch: Epoch,
446    stake_history: StakeHistory,
447}
448
449impl From<DeserializableEpochStakes> for EpochStakes {
450    fn from(stakes: DeserializableEpochStakes) -> Self {
451        let DeserializableEpochStakes {
452            vote_accounts,
453            _stake_delegations: _,
454            _unused: _,
455            epoch,
456            stake_history,
457        } = stakes;
458        Self {
459            epoch,
460            vote_accounts,
461            stake_history,
462        }
463    }
464}
465
466/// Snapshot epoch stakes contain delegations, but the main EpochStakes no longer uses them.
467/// This fn does custom deserialization to visit-and-ignore the delegations,
468/// avoiding the need to construct an expensive imbl::HashMap.
469fn deserialize_and_ignore_stake_delegations<'de, D>(deserializer: D) -> Result<(), D::Error>
470where
471    D: Deserializer<'de>,
472{
473    struct IgnoredStakeDelegationsVisitor;
474
475    impl<'de> Visitor<'de> for IgnoredStakeDelegationsVisitor {
476        type Value = ();
477
478        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
479            formatter.write_str("a sequence of serialized stake delegations")
480        }
481
482        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
483        where
484            A: SeqAccess<'de>,
485        {
486            while seq.next_element::<(Pubkey, Stake)>()?.is_some() {
487                // nothing to do here, ignore the delegations
488            }
489            Ok(())
490        }
491    }
492
493    deserializer.deserialize_seq(IgnoredStakeDelegationsVisitor)
494}
495
496#[cfg(test)]
497pub(crate) mod tests {
498    use {
499        super::*,
500        crate::{stake_account::StakeAccount, stakes::Stakes},
501        solana_account::AccountSharedData,
502        solana_bls_signatures::keypair::Keypair as BLSKeypair,
503        solana_rent::Rent,
504        solana_vote::vote_account::VoteAccount,
505        solana_vote_program::vote_state::create_v4_account_with_authorized,
506        std::iter,
507        test_case::test_case,
508    };
509
510    struct VoteAccountInfo {
511        vote_account: Pubkey,
512        account: AccountSharedData,
513        authorized_voter: Pubkey,
514    }
515
516    fn new_vote_accounts(
517        num_nodes: usize,
518        num_vote_accounts_per_node: usize,
519        is_alpenglow: bool,
520    ) -> HashMap<Pubkey, Vec<VoteAccountInfo>> {
521        // Create some vote accounts for each pubkey
522        (0..num_nodes)
523            .map(|_| {
524                let node_id = solana_pubkey::new_rand();
525                (
526                    node_id,
527                    iter::repeat_with(|| {
528                        let authorized_voter = solana_pubkey::new_rand();
529                        let bls_pubkey_compressed: BLSPubkeyCompressed =
530                            (*BLSKeypair::new().public).into();
531                        let bls_pubkey_compressed_serialized =
532                            bincode::serialize(&bls_pubkey_compressed)
533                                .unwrap()
534                                .try_into()
535                                .unwrap();
536
537                        let bls_pubkey = if is_alpenglow {
538                            bls_pubkey_compressed_serialized
539                        } else {
540                            [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]
541                        };
542                        let account = create_v4_account_with_authorized(
543                            &node_id,
544                            &authorized_voter,
545                            bls_pubkey,
546                            &node_id,
547                            0,
548                            &node_id,
549                            0,
550                            &node_id,
551                            100,
552                        );
553                        VoteAccountInfo {
554                            vote_account: solana_pubkey::new_rand(),
555                            account,
556                            authorized_voter,
557                        }
558                    })
559                    .take(num_vote_accounts_per_node)
560                    .collect(),
561                )
562            })
563            .collect()
564    }
565
566    fn new_epoch_vote_accounts(
567        vote_accounts_map: &HashMap<Pubkey, Vec<VoteAccountInfo>>,
568        node_id_to_stake_fn: impl Fn(&Pubkey) -> u64,
569    ) -> VoteAccountsHashMap {
570        // Create and process the vote accounts
571        vote_accounts_map
572            .iter()
573            .flat_map(|(node_id, vote_accounts)| {
574                vote_accounts.iter().map(|v| {
575                    let vote_account = VoteAccount::try_from(v.account.clone()).unwrap();
576                    (v.vote_account, (node_id_to_stake_fn(node_id), vote_account))
577                })
578            })
579            .collect()
580    }
581
582    #[test_case(true; "alpenglow")]
583    #[test_case(false; "towerbft")]
584    fn test_parse_epoch_vote_accounts(is_alpenglow: bool) {
585        let stake_per_account = 100;
586        let num_vote_accounts_per_node = 2;
587        let num_nodes = 10;
588
589        let vote_accounts_map =
590            new_vote_accounts(num_nodes, num_vote_accounts_per_node, is_alpenglow);
591
592        let expected_authorized_voters: HashMap<_, _> = vote_accounts_map
593            .values()
594            .flat_map(|vote_accounts| {
595                vote_accounts
596                    .iter()
597                    .map(|v| (v.vote_account, v.authorized_voter))
598            })
599            .collect();
600
601        let expected_node_id_to_vote_accounts: HashMap<_, _> = vote_accounts_map
602            .iter()
603            .map(|(node_pubkey, vote_accounts)| {
604                let mut vote_accounts = vote_accounts
605                    .iter()
606                    .map(|v| v.vote_account)
607                    .collect::<Vec<_>>();
608                vote_accounts.sort();
609                let node_vote_accounts = NodeVoteAccounts {
610                    vote_accounts,
611                    total_stake: stake_per_account * num_vote_accounts_per_node as u64,
612                };
613                (*node_pubkey, node_vote_accounts)
614            })
615            .collect();
616
617        let epoch_vote_accounts =
618            new_epoch_vote_accounts(&vote_accounts_map, |_| stake_per_account);
619
620        let (total_stake, mut node_id_to_vote_accounts, epoch_authorized_voters) =
621            VersionedEpochStakes::parse_epoch_vote_accounts(&epoch_vote_accounts, 0);
622
623        // Verify the results
624        node_id_to_vote_accounts
625            .iter_mut()
626            .for_each(|(_, node_vote_accounts)| node_vote_accounts.vote_accounts.sort());
627
628        assert!(
629            node_id_to_vote_accounts.len() == expected_node_id_to_vote_accounts.len()
630                && node_id_to_vote_accounts
631                    .iter()
632                    .all(|(k, v)| expected_node_id_to_vote_accounts.get(k).unwrap() == v)
633        );
634        assert!(
635            epoch_authorized_voters.len() == expected_authorized_voters.len()
636                && epoch_authorized_voters
637                    .iter()
638                    .all(|(k, v)| expected_authorized_voters.get(k).unwrap() == v)
639        );
640        assert_eq!(
641            total_stake,
642            num_nodes as u64 * num_vote_accounts_per_node as u64 * 100
643        );
644    }
645
646    #[test_case(true; "alpenglow")]
647    #[test_case(false; "towerbft")]
648    fn test_node_id_to_stake(is_alpenglow: bool) {
649        let num_nodes = 10;
650        let num_vote_accounts_per_node = 2;
651
652        let vote_accounts_map =
653            new_vote_accounts(num_nodes, num_vote_accounts_per_node, is_alpenglow);
654        let node_id_to_stake_map = vote_accounts_map
655            .keys()
656            .enumerate()
657            .map(|(index, node_id)| (*node_id, ((index + 1) * 100) as u64))
658            .collect::<HashMap<_, _>>();
659        let epoch_vote_accounts = new_epoch_vote_accounts(&vote_accounts_map, |node_id| {
660            *node_id_to_stake_map.get(node_id).unwrap()
661        });
662        let epoch_stakes = VersionedEpochStakes::new_for_tests(epoch_vote_accounts, 0);
663
664        assert_eq!(epoch_stakes.total_stake(), 11000);
665        for (node_id, stake) in node_id_to_stake_map.iter() {
666            assert_eq!(
667                epoch_stakes.node_id_to_stake(node_id),
668                Some(*stake * num_vote_accounts_per_node as u64)
669            );
670        }
671    }
672
673    #[test]
674    fn test_bls_pubkey_rank_map() {
675        agave_logger::setup();
676        let num_nodes = 10;
677        let num_vote_accounts = num_nodes;
678
679        let vote_accounts_map = new_vote_accounts(num_nodes, 1, true);
680        let node_id_to_stake_map = vote_accounts_map
681            .keys()
682            .enumerate()
683            .map(|(index, node_id)| (*node_id, ((index + 1) * 100) as u64))
684            .collect::<HashMap<_, _>>();
685        let epoch_vote_accounts = new_epoch_vote_accounts(&vote_accounts_map, |node_id| {
686            *node_id_to_stake_map.get(node_id).unwrap()
687        });
688        let epoch_stakes = VersionedEpochStakes::new_for_tests(epoch_vote_accounts.clone(), 0);
689        let bls_pubkey_to_rank_map = epoch_stakes.bls_pubkey_to_rank_map();
690        let expected_num_vote_accounts = num_vote_accounts;
691        assert_eq!(bls_pubkey_to_rank_map.len(), expected_num_vote_accounts);
692        let expected_total_stake = epoch_stakes.total_stake();
693        assert_eq!(
694            bls_pubkey_to_rank_map.total_stake().get(),
695            expected_total_stake
696        );
697        for (vote_account_pubkey, (stake, vote_account)) in epoch_vote_accounts {
698            let vote_state_view = vote_account.vote_state_view();
699            let (_comp, bls_pubkey) = bls_pubkey_compressed_bytes_to_bls_pubkey(
700                vote_state_view.bls_pubkey_compressed().unwrap(),
701            )
702            .unwrap();
703            let node_pubkey = *vote_state_view.node_pubkey();
704            let index = bls_pubkey_to_rank_map.get_rank(&bls_pubkey).unwrap();
705            assert!(index >= &0 && index < &(expected_num_vote_accounts as u16));
706            assert_eq!(
707                bls_pubkey_to_rank_map.get_pubkey_stake_entry(*index as usize),
708                Some(&BLSPubkeyStakeEntry {
709                    vote_account_pubkey,
710                    node_pubkey,
711                    bls_pubkey,
712                    stake: NonZero::new(stake).unwrap(),
713                })
714            );
715        }
716
717        // Convert it to versioned and back, we should get the same rank map
718        let mut bank_epoch_stakes = HashMap::new();
719        bank_epoch_stakes.insert(0, epoch_stakes.clone());
720        let epoch_stakes = bank_epoch_stakes
721            .get(&0)
722            .expect("Epoch stakes should exist");
723        let bls_pubkey_to_rank_map2 = epoch_stakes.bls_pubkey_to_rank_map();
724        assert_eq!(bls_pubkey_to_rank_map2, bls_pubkey_to_rank_map);
725    }
726
727    #[test]
728    #[should_panic(expected = "total stakes should not be 0")]
729    fn test_multiple_vote_accounts_panics() {
730        agave_logger::setup();
731        let num_nodes = 10;
732
733        let vote_accounts_map = new_vote_accounts(num_nodes, 2, true);
734        let node_id_to_stake_map = vote_accounts_map
735            .keys()
736            .enumerate()
737            .map(|(index, node_id)| (*node_id, ((index + 1) * 100) as u64))
738            .collect::<HashMap<_, _>>();
739        let epoch_vote_accounts = new_epoch_vote_accounts(&vote_accounts_map, |node_id| {
740            *node_id_to_stake_map.get(node_id).unwrap()
741        });
742        let epoch_stakes = VersionedEpochStakes::new_for_tests(epoch_vote_accounts.clone(), 0);
743        epoch_stakes.bls_pubkey_to_rank_map();
744    }
745
746    #[test]
747    fn test_bls_pubkey_rank_map_excludes_duplicate_bls_and_identity() {
748        let new_bls_pubkey = || {
749            let bls_pubkey_compressed: BLSPubkeyCompressed = (*BLSKeypair::new().public).into();
750            let bls_pubkey_compressed_serialized = bincode::serialize(&bls_pubkey_compressed)
751                .unwrap()
752                .try_into()
753                .unwrap();
754            let (_bls_pubkey_compressed, bls_pubkey) =
755                bls_pubkey_compressed_bytes_to_bls_pubkey(bls_pubkey_compressed_serialized)
756                    .unwrap();
757            (bls_pubkey_compressed_serialized, bls_pubkey)
758        };
759
760        let (duplicate_bls_pubkey_serialized, duplicate_bls_pubkey) = new_bls_pubkey();
761        let (duplicate_node_bls_pubkey_serialized, duplicate_node_bls_pubkey) = new_bls_pubkey();
762        let (duplicate_node_bls_pubkey_serialized_2, duplicate_node_bls_pubkey_2) =
763            new_bls_pubkey();
764        let (shared_voter_bls_pubkey_serialized, shared_voter_bls_pubkey) = new_bls_pubkey();
765        let (shared_voter_bls_pubkey_serialized_2, shared_voter_bls_pubkey_2) = new_bls_pubkey();
766        let (unique_bls_pubkey_serialized, unique_bls_pubkey) = new_bls_pubkey();
767
768        let duplicate_bls_vote_pubkey = Pubkey::new_unique();
769        let duplicate_bls_vote_pubkey_2 = Pubkey::new_unique();
770        let duplicate_node_vote_pubkey = Pubkey::new_unique();
771        let duplicate_node_vote_pubkey_2 = Pubkey::new_unique();
772        let shared_voter_vote_pubkey = Pubkey::new_unique();
773        let shared_voter_vote_pubkey_2 = Pubkey::new_unique();
774        let unique_vote_pubkey = Pubkey::new_unique();
775
776        let duplicate_node_pubkey = Pubkey::new_unique();
777        let shared_authorized_voter = Pubkey::new_unique();
778        let shared_voter_node_pubkey = Pubkey::new_unique();
779        let shared_voter_node_pubkey_2 = Pubkey::new_unique();
780        let unique_node_pubkey = Pubkey::new_unique();
781        let unique_voter = Pubkey::new_unique();
782
783        let account = |node_pubkey, authorized_voter, bls_pubkey| {
784            VoteAccount::try_from(create_v4_account_with_authorized(
785                &node_pubkey,
786                &authorized_voter,
787                bls_pubkey,
788                &node_pubkey,
789                0,
790                &node_pubkey,
791                0,
792                &node_pubkey,
793                100,
794            ))
795            .unwrap()
796        };
797        let epoch_vote_accounts = VoteAccountsHashMap::from([
798            (
799                duplicate_bls_vote_pubkey,
800                (
801                    100,
802                    account(
803                        Pubkey::new_unique(),
804                        Pubkey::new_unique(),
805                        duplicate_bls_pubkey_serialized,
806                    ),
807                ),
808            ),
809            (
810                duplicate_bls_vote_pubkey_2,
811                (
812                    100,
813                    account(
814                        Pubkey::new_unique(),
815                        Pubkey::new_unique(),
816                        duplicate_bls_pubkey_serialized,
817                    ),
818                ),
819            ),
820            (
821                duplicate_node_vote_pubkey,
822                (
823                    100,
824                    account(
825                        duplicate_node_pubkey,
826                        Pubkey::new_unique(),
827                        duplicate_node_bls_pubkey_serialized,
828                    ),
829                ),
830            ),
831            (
832                duplicate_node_vote_pubkey_2,
833                (
834                    100,
835                    account(
836                        duplicate_node_pubkey,
837                        Pubkey::new_unique(),
838                        duplicate_node_bls_pubkey_serialized_2,
839                    ),
840                ),
841            ),
842            (
843                shared_voter_vote_pubkey,
844                (
845                    100,
846                    account(
847                        shared_voter_node_pubkey,
848                        shared_authorized_voter,
849                        shared_voter_bls_pubkey_serialized,
850                    ),
851                ),
852            ),
853            (
854                shared_voter_vote_pubkey_2,
855                (
856                    100,
857                    account(
858                        shared_voter_node_pubkey_2,
859                        shared_authorized_voter,
860                        shared_voter_bls_pubkey_serialized_2,
861                    ),
862                ),
863            ),
864            (
865                unique_vote_pubkey,
866                (
867                    50,
868                    account(
869                        unique_node_pubkey,
870                        unique_voter,
871                        unique_bls_pubkey_serialized,
872                    ),
873                ),
874            ),
875        ]);
876
877        let rank_map = BLSPubkeyToRankMap::new(&epoch_vote_accounts);
878
879        assert_eq!(rank_map.len(), 3);
880        assert_eq!(rank_map.total_stake().get(), 250);
881        for bls_pubkey in [
882            duplicate_bls_pubkey,
883            duplicate_node_bls_pubkey,
884            duplicate_node_bls_pubkey_2,
885        ] {
886            assert!(rank_map.get_rank(&bls_pubkey).is_none());
887        }
888        for vote_pubkey in [
889            duplicate_bls_vote_pubkey,
890            duplicate_bls_vote_pubkey_2,
891            duplicate_node_vote_pubkey,
892            duplicate_node_vote_pubkey_2,
893        ] {
894            assert!(rank_map.get_rank_for_vote_pubkey(&vote_pubkey).is_none());
895        }
896
897        for (vote_account_pubkey, node_pubkey, bls_pubkey) in [
898            (
899                shared_voter_vote_pubkey,
900                shared_voter_node_pubkey,
901                shared_voter_bls_pubkey,
902            ),
903            (
904                shared_voter_vote_pubkey_2,
905                shared_voter_node_pubkey_2,
906                shared_voter_bls_pubkey_2,
907            ),
908        ] {
909            let rank = *rank_map.get_rank(&bls_pubkey).unwrap();
910            assert_eq!(
911                rank_map.get_rank_for_vote_pubkey(&vote_account_pubkey),
912                Some(&rank)
913            );
914            assert_eq!(
915                rank_map.get_pubkey_stake_entry(rank as usize),
916                Some(&BLSPubkeyStakeEntry {
917                    vote_account_pubkey,
918                    node_pubkey,
919                    bls_pubkey,
920                    stake: NonZero::new(100).unwrap(),
921                })
922            );
923        }
924
925        let unique_rank = *rank_map.get_rank(&unique_bls_pubkey).unwrap();
926        assert_eq!(
927            rank_map.get_rank_for_vote_pubkey(&unique_vote_pubkey),
928            Some(&unique_rank)
929        );
930        assert_eq!(
931            rank_map.get_pubkey_stake_entry(unique_rank as usize),
932            Some(&BLSPubkeyStakeEntry {
933                vote_account_pubkey: unique_vote_pubkey,
934                node_pubkey: unique_node_pubkey,
935                bls_pubkey: unique_bls_pubkey,
936                stake: NonZero::new(50).unwrap(),
937            })
938        );
939        assert!(rank_map.get_pubkey_stake_entry(rank_map.len()).is_none());
940    }
941
942    #[test]
943    fn test_versioned_epoch_stakes_does_not_serialize_delegations() {
944        // test-only types to get the serialized EpochStakes that still have stake delegations
945        #[derive(Deserialize)]
946        enum SerializedVersionedEpochStakes {
947            Current {
948                stakes: SerializedEpochStakes,
949                total_stake: u64,
950                node_id_to_vote_accounts: NodeIdToVoteAccounts,
951                epoch_authorized_voters: EpochAuthorizedVoters,
952            },
953        }
954        #[derive(Deserialize)]
955        struct SerializedEpochStakes {
956            vote_accounts: VoteAccounts,
957            stake_delegations: Vec<(Pubkey, Stake)>,
958            unused: u64,
959            epoch: Epoch,
960            stake_history: StakeHistory,
961        }
962
963        let epoch = 42;
964        let delegated_amount = 456_789;
965        let rent = Rent::default();
966        let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
967            crate::stakes::tests::create_staked_node_accounts(123, &rent);
968        let vote_account = VoteAccount::try_from(vote_account).unwrap();
969        let vote_accounts = VoteAccounts::from(Arc::new(HashMap::from([(
970            vote_pubkey,
971            (delegated_amount, vote_account),
972        )])));
973        let stake_account = StakeAccount::try_from(stake_account).unwrap();
974        let stakes = Stakes::new_for_tests(
975            epoch,
976            vote_accounts,
977            imbl::HashMap::from_iter([(stake_pubkey, stake_account)]),
978        );
979
980        // ensure stake delegations start off *not* empty
981        assert!(!stakes.stake_delegations().is_empty());
982
983        let epoch_stakes = VersionedEpochStakes::new(SerdeStakesToStakeFormat::Account(stakes), 0);
984
985        assert_eq!(
986            epoch_stakes
987                .stakes()
988                .vote_accounts()
989                .get_delegated_stake(&vote_pubkey),
990            delegated_amount,
991        );
992
993        let serialized_bytes = bincode::serialize(&epoch_stakes).unwrap();
994        let serialized_epoch_stakes: SerializedVersionedEpochStakes =
995            bincode::deserialize(&serialized_bytes).unwrap();
996        match serialized_epoch_stakes {
997            SerializedVersionedEpochStakes::Current {
998                stakes,
999                total_stake,
1000                node_id_to_vote_accounts,
1001                epoch_authorized_voters,
1002            } => {
1003                assert_eq!(
1004                    stakes.vote_accounts.get_delegated_stake(&vote_pubkey),
1005                    delegated_amount,
1006                );
1007                assert!(stakes.stake_delegations.is_empty()); // delegations are *not* serialized
1008                assert_eq!(stakes.unused, 0);
1009                assert_eq!(stakes.epoch, epoch);
1010                assert_eq!(stakes.stake_history, StakeHistory::default());
1011                assert_eq!(total_stake, delegated_amount);
1012                assert_eq!(node_id_to_vote_accounts.len(), 1);
1013                assert_eq!(epoch_authorized_voters.len(), 1);
1014            }
1015        }
1016
1017        let deserialized_epoch_stakes: VersionedEpochStakes =
1018            bincode::deserialize::<DeserializableVersionedEpochStakes>(&serialized_bytes)
1019                .unwrap()
1020                .into();
1021        assert_eq!(
1022            deserialized_epoch_stakes
1023                .stakes()
1024                .vote_accounts()
1025                .get_delegated_stake(&vote_pubkey),
1026            delegated_amount,
1027        );
1028    }
1029}