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