Skip to main content

solana_runtime/
leader_schedule_utils.rs

1use {
2    crate::bank::Bank,
3    solana_clock::{Epoch, Slot},
4    solana_epoch_schedule::EpochSchedule,
5    solana_leader_schedule::{LeaderSchedule, NUM_CONSECUTIVE_LEADER_SLOTS},
6    solana_pubkey::Pubkey,
7    solana_vote::vote_account::VoteAccountsHashMap,
8    std::collections::HashMap,
9};
10
11/// Return the leader schedule for the given epoch.
12pub fn leader_schedule(epoch: Epoch, bank: &Bank) -> Option<LeaderSchedule> {
13    leader_schedule_from_vote_accounts(
14        epoch,
15        bank.epoch_schedule(),
16        bank.epoch_vote_accounts(epoch)?,
17    )
18}
19
20/// Return the leader schedule for the given epoch using vote accounts directly.
21/// This is useful for computing the leader schedule during snapshot restoration
22/// before a Bank is fully constructed.
23pub fn leader_schedule_from_vote_accounts(
24    epoch: Epoch,
25    epoch_schedule: &EpochSchedule,
26    epoch_vote_accounts: &VoteAccountsHashMap,
27) -> Option<LeaderSchedule> {
28    Some(LeaderSchedule::new(
29        epoch_vote_accounts,
30        epoch,
31        epoch_schedule
32            .get_slots_in_epoch(epoch)
33            .try_into()
34            .expect("number of slots in epoch must fit in usize"),
35        NUM_CONSECUTIVE_LEADER_SLOTS,
36    ))
37}
38
39/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
40pub type LeaderScheduleByIdentity = HashMap<String, Vec<usize>>;
41
42pub fn leader_schedule_by_identity<'a>(
43    upcoming_leaders: impl Iterator<Item = (usize, &'a Pubkey)>,
44) -> LeaderScheduleByIdentity {
45    let mut leader_schedule_by_identity = HashMap::new();
46
47    for (slot_index, identity_pubkey) in upcoming_leaders {
48        leader_schedule_by_identity
49            .entry(identity_pubkey)
50            .or_insert_with(Vec::new)
51            .push(slot_index);
52    }
53
54    leader_schedule_by_identity
55        .into_iter()
56        .map(|(identity_pubkey, slot_indices)| (identity_pubkey.to_string(), slot_indices))
57        .collect()
58}
59
60/// Return the leader for the given slot.
61pub fn slot_leader_at(slot: Slot, bank: &Bank) -> Option<Pubkey> {
62    let (epoch, slot_index) = bank.get_epoch_and_slot_index(slot);
63
64    leader_schedule(epoch, bank).map(|leader_schedule| leader_schedule[slot_index].id)
65}
66
67// Returns the number of ticks remaining from the specified tick_height to the end of the
68// slot implied by the tick_height
69pub fn num_ticks_left_in_slot(bank: &Bank, tick_height: u64) -> u64 {
70    bank.ticks_per_slot() - tick_height % bank.ticks_per_slot()
71}
72
73pub fn first_of_consecutive_leader_slots(slot: Slot) -> Slot {
74    let num_consecutive_leader_slots = NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64;
75    (slot / num_consecutive_leader_slots) * num_consecutive_leader_slots
76}
77
78/// Returns the last slot in the leader window that contains `slot`
79#[inline]
80pub fn last_of_consecutive_leader_slots(slot: Slot) -> Slot {
81    first_of_consecutive_leader_slots(slot) + NUM_CONSECUTIVE_LEADER_SLOTS.get() as u64 - 1
82}
83
84/// Returns the index within the leader slot range that contains `slot`
85#[inline]
86pub fn leader_slot_index(slot: Slot) -> usize {
87    slot as usize % NUM_CONSECUTIVE_LEADER_SLOTS
88}
89
90/// Returns the number of slots left after `slot` in the leader window
91/// that contains `slot`
92#[inline]
93pub fn remaining_slots_in_window(slot: Slot) -> usize {
94    NUM_CONSECUTIVE_LEADER_SLOTS
95        .get()
96        .checked_sub(leader_slot_index(slot))
97        .unwrap()
98}
99
100#[cfg(test)]
101mod tests {
102    use {
103        super::*,
104        crate::genesis_utils::{
105            bootstrap_validator_stake_lamports, create_genesis_config_with_leader,
106        },
107    };
108
109    #[test]
110    fn test_leader_schedule_via_bank() {
111        let pubkey = solana_pubkey::new_rand();
112        let genesis_config =
113            create_genesis_config_with_leader(0, &pubkey, bootstrap_validator_stake_lamports())
114                .genesis_config;
115
116        let bank = Bank::new_for_tests(&genesis_config);
117        let leader_schedule = leader_schedule(0, &bank).unwrap();
118
119        assert_eq!(leader_schedule[0].id, pubkey);
120        assert_eq!(leader_schedule[1].id, pubkey);
121        assert_eq!(leader_schedule[2].id, pubkey);
122    }
123
124    #[test]
125    fn test_leader_scheduler1_basic() {
126        let pubkey = solana_pubkey::new_rand();
127        let genesis_config =
128            create_genesis_config_with_leader(42, &pubkey, bootstrap_validator_stake_lamports())
129                .genesis_config;
130        let bank = Bank::new_for_tests(&genesis_config);
131        assert_eq!(slot_leader_at(bank.slot(), &bank).unwrap(), pubkey);
132    }
133
134    #[test]
135    fn test_leader_span_math() {
136        // All of the test cases assume a 4 slot leader span and need to be
137        // adjusted if it changes.
138        assert_eq!(NUM_CONSECUTIVE_LEADER_SLOTS.get(), 4);
139
140        assert_eq!(first_of_consecutive_leader_slots(0), 0);
141        assert_eq!(first_of_consecutive_leader_slots(1), 0);
142        assert_eq!(first_of_consecutive_leader_slots(2), 0);
143        assert_eq!(first_of_consecutive_leader_slots(3), 0);
144        assert_eq!(first_of_consecutive_leader_slots(4), 4);
145
146        assert_eq!(last_of_consecutive_leader_slots(0), 3);
147        assert_eq!(last_of_consecutive_leader_slots(1), 3);
148        assert_eq!(last_of_consecutive_leader_slots(2), 3);
149        assert_eq!(last_of_consecutive_leader_slots(3), 3);
150        assert_eq!(last_of_consecutive_leader_slots(4), 7);
151
152        assert_eq!(leader_slot_index(0), 0);
153        assert_eq!(leader_slot_index(1), 1);
154        assert_eq!(leader_slot_index(2), 2);
155        assert_eq!(leader_slot_index(3), 3);
156        assert_eq!(leader_slot_index(4), 0);
157        assert_eq!(leader_slot_index(5), 1);
158        assert_eq!(leader_slot_index(6), 2);
159        assert_eq!(leader_slot_index(7), 3);
160
161        assert_eq!(remaining_slots_in_window(0), 4);
162        assert_eq!(remaining_slots_in_window(1), 3);
163        assert_eq!(remaining_slots_in_window(2), 2);
164        assert_eq!(remaining_slots_in_window(3), 1);
165        assert_eq!(remaining_slots_in_window(4), 4);
166    }
167}