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#[derive(Clone, Debug)]
31#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
32pub struct BLSPubkeyStakeEntry {
33 pub vote_account_pubkey: Pubkey,
35 pub node_pubkey: Pubkey,
37 pub bls_pubkey: PopVerified<BLSPubkeyAffine>,
39 pub stake: NonZero<u64>,
41}
42
43#[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#[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 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#[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: 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 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 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#[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
393impl 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(), 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#[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
466fn 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 }
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 (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 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 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 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 #[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 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()); 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}