solana_ledger/
leader_schedule.rs1use {
2 rand::distributions::{Distribution, WeightedIndex},
3 rand_chacha::{rand_core::SeedableRng, ChaChaRng},
4 solana_clock::Epoch,
5 solana_pubkey::Pubkey,
6 std::{collections::HashMap, convert::identity, ops::Index, sync::Arc},
7};
8
9mod identity_keyed;
10mod vote_keyed;
11pub use {
12 identity_keyed::LeaderSchedule as IdentityKeyedLeaderSchedule,
13 vote_keyed::LeaderSchedule as VoteKeyedLeaderSchedule,
14};
15
16#[derive(Clone, Debug)]
18pub struct FixedSchedule {
19 pub leader_schedule: Arc<LeaderSchedule>,
20}
21
22pub type LeaderSchedule = Box<dyn LeaderScheduleVariant>;
24
25pub trait LeaderScheduleVariant:
26 std::fmt::Debug + Send + Sync + Index<u64, Output = Pubkey>
27{
28 fn get_slot_leaders(&self) -> &[Pubkey];
29 fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Arc<Vec<usize>>>;
30
31 fn get_vote_key_at_slot_index(&self, _epoch_slot_index: usize) -> Option<&Pubkey> {
34 None
35 }
36
37 fn get_leader_upcoming_slots(
38 &self,
39 pubkey: &Pubkey,
40 offset: usize, ) -> Box<dyn Iterator<Item = usize>> {
42 let index = self
43 .get_leader_slots_map()
44 .get(pubkey)
45 .cloned()
46 .unwrap_or_default();
47 let num_slots = self.num_slots();
48 let size = index.len();
49 #[allow(clippy::reversed_empty_ranges)]
50 let range = if index.is_empty() {
51 1..=0 } else {
53 let offset = index
54 .binary_search(&(offset % num_slots))
55 .unwrap_or_else(identity)
56 + offset / num_slots * size;
57 offset..=usize::MAX
58 };
59 Box::new(range.map(move |k| index[k % size] + k / size * num_slots))
64 }
65
66 fn num_slots(&self) -> usize {
67 self.get_slot_leaders().len()
68 }
69}
70
71fn stake_weighted_slot_leaders(
73 mut keyed_stakes: Vec<(&Pubkey, u64)>,
74 epoch: Epoch,
75 len: u64,
76 repeat: u64,
77) -> Vec<Pubkey> {
78 sort_stakes(&mut keyed_stakes);
79 let (keys, stakes): (Vec<_>, Vec<_>) = keyed_stakes.into_iter().unzip();
80 let weighted_index = WeightedIndex::new(stakes).unwrap();
81 let mut seed = [0u8; 32];
82 seed[0..8].copy_from_slice(&epoch.to_le_bytes());
83 let rng = &mut ChaChaRng::from_seed(seed);
84 let mut current_slot_leader = Pubkey::default();
85 (0..len)
86 .map(|i| {
87 if i % repeat == 0 {
88 current_slot_leader = keys[weighted_index.sample(rng)];
89 }
90 current_slot_leader
91 })
92 .collect()
93}
94
95fn sort_stakes(stakes: &mut Vec<(&Pubkey, u64)>) {
96 stakes.sort_unstable_by(|(l_pubkey, l_stake), (r_pubkey, r_stake)| {
100 if r_stake == l_stake {
101 r_pubkey.cmp(l_pubkey)
102 } else {
103 r_stake.cmp(l_stake)
104 }
105 });
106
107 stakes.dedup();
109}
110
111#[cfg(test)]
112mod tests {
113 use {super::*, itertools::Itertools, rand::Rng, std::iter::repeat_with};
114
115 #[test]
116 fn test_get_leader_upcoming_slots() {
117 const NUM_SLOTS: usize = 97;
118 let mut rng = rand::thread_rng();
119 let pubkeys: Vec<_> = repeat_with(Pubkey::new_unique).take(4).collect();
120 let schedule: Vec<_> = repeat_with(|| pubkeys[rng.gen_range(0..3)])
121 .take(19)
122 .collect();
123 let schedule = IdentityKeyedLeaderSchedule::new_from_schedule(schedule);
124 let leaders = (0..NUM_SLOTS)
125 .map(|i| (schedule[i as u64], i))
126 .into_group_map();
127 for pubkey in &pubkeys {
128 let index = leaders.get(pubkey).cloned().unwrap_or_default();
129 for offset in 0..NUM_SLOTS {
130 let schedule: Vec<_> = schedule
131 .get_leader_upcoming_slots(pubkey, offset)
132 .take_while(|s| *s < NUM_SLOTS)
133 .collect();
134 let index: Vec<_> = index.iter().copied().skip_while(|s| *s < offset).collect();
135 assert_eq!(schedule, index);
136 }
137 }
138 }
139
140 #[test]
141 fn test_sort_stakes_basic() {
142 let pubkey0 = solana_pubkey::new_rand();
143 let pubkey1 = solana_pubkey::new_rand();
144 let mut stakes = vec![(&pubkey0, 1), (&pubkey1, 2)];
145 sort_stakes(&mut stakes);
146 assert_eq!(stakes, vec![(&pubkey1, 2), (&pubkey0, 1)]);
147 }
148
149 #[test]
150 fn test_sort_stakes_with_dup() {
151 let pubkey0 = solana_pubkey::new_rand();
152 let pubkey1 = solana_pubkey::new_rand();
153 let mut stakes = vec![(&pubkey0, 1), (&pubkey1, 2), (&pubkey0, 1)];
154 sort_stakes(&mut stakes);
155 assert_eq!(stakes, vec![(&pubkey1, 2), (&pubkey0, 1)]);
156 }
157
158 #[test]
159 fn test_sort_stakes_with_equal_stakes() {
160 let pubkey0 = Pubkey::default();
161 let pubkey1 = solana_pubkey::new_rand();
162 let mut stakes = vec![(&pubkey0, 1), (&pubkey1, 1)];
163 sort_stakes(&mut stakes);
164 assert_eq!(stakes, vec![(&pubkey1, 1), (&pubkey0, 1)]);
165 }
166}