clone_solana_epoch_schedule/
lib.rs1#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
16#![no_std]
17#[cfg(feature = "frozen-abi")]
18extern crate std;
19
20#[cfg(feature = "sysvar")]
21pub mod sysvar;
22
23use clone_solana_sdk_macro::CloneZeroed;
24#[cfg(feature = "serde")]
25use serde_derive::{Deserialize, Serialize};
26
27const DEFAULT_SLOTS_PER_EPOCH: u64 = 432_000;
29#[cfg(test)]
30static_assertions::const_assert_eq!(
31 DEFAULT_SLOTS_PER_EPOCH,
32 clone_solana_clock::DEFAULT_SLOTS_PER_EPOCH
33);
34pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH;
36
37pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3;
42
43pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
47
48#[repr(C)]
49#[cfg_attr(
50 feature = "frozen-abi",
51 derive(clone_solana_frozen_abi_macro::AbiExample)
52)]
53#[cfg_attr(
54 feature = "serde",
55 derive(Deserialize, Serialize),
56 serde(rename_all = "camelCase")
57)]
58#[derive(Debug, CloneZeroed, PartialEq, Eq)]
59pub struct EpochSchedule {
60 pub slots_per_epoch: u64,
62
63 pub leader_schedule_slot_offset: u64,
66
67 pub warmup: bool,
69
70 pub first_normal_epoch: u64,
74
75 pub first_normal_slot: u64,
79}
80
81impl Default for EpochSchedule {
82 fn default() -> Self {
83 Self::custom(
84 DEFAULT_SLOTS_PER_EPOCH,
85 DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
86 true,
87 )
88 }
89}
90
91impl EpochSchedule {
92 pub fn new(slots_per_epoch: u64) -> Self {
93 Self::custom(slots_per_epoch, slots_per_epoch, true)
94 }
95 pub fn without_warmup() -> Self {
96 Self::custom(
97 DEFAULT_SLOTS_PER_EPOCH,
98 DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
99 false,
100 )
101 }
102 pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self {
103 assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH);
104 let (first_normal_epoch, first_normal_slot) = if warmup {
105 let next_power_of_two = slots_per_epoch.next_power_of_two();
106 let log2_slots_per_epoch = next_power_of_two
107 .trailing_zeros()
108 .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
109
110 (
111 u64::from(log2_slots_per_epoch),
112 next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH),
113 )
114 } else {
115 (0, 0)
116 };
117 EpochSchedule {
118 slots_per_epoch,
119 leader_schedule_slot_offset,
120 warmup,
121 first_normal_epoch,
122 first_normal_slot,
123 }
124 }
125
126 pub fn get_slots_in_epoch(&self, epoch: u64) -> u64 {
128 if epoch < self.first_normal_epoch {
129 2u64.saturating_pow(
130 (epoch as u32).saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()),
131 )
132 } else {
133 self.slots_per_epoch
134 }
135 }
136
137 pub fn get_leader_schedule_epoch(&self, slot: u64) -> u64 {
140 if slot < self.first_normal_slot {
141 self.get_epoch_and_slot_index(slot).0.saturating_add(1)
143 } else {
144 let new_slots_since_first_normal_slot = slot.saturating_sub(self.first_normal_slot);
145 let new_first_normal_leader_schedule_slot =
146 new_slots_since_first_normal_slot.saturating_add(self.leader_schedule_slot_offset);
147 let new_epochs_since_first_normal_leader_schedule =
148 new_first_normal_leader_schedule_slot
149 .checked_div(self.slots_per_epoch)
150 .unwrap_or(0);
151 self.first_normal_epoch
152 .saturating_add(new_epochs_since_first_normal_leader_schedule)
153 }
154 }
155
156 pub fn get_epoch(&self, slot: u64) -> u64 {
158 self.get_epoch_and_slot_index(slot).0
159 }
160
161 pub fn get_epoch_and_slot_index(&self, slot: u64) -> (u64, u64) {
163 if slot < self.first_normal_slot {
164 let epoch = slot
165 .saturating_add(MINIMUM_SLOTS_PER_EPOCH)
166 .saturating_add(1)
167 .next_power_of_two()
168 .trailing_zeros()
169 .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros())
170 .saturating_sub(1);
171
172 let epoch_len =
173 2u64.saturating_pow(epoch.saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()));
174
175 (
176 u64::from(epoch),
177 slot.saturating_sub(epoch_len.saturating_sub(MINIMUM_SLOTS_PER_EPOCH)),
178 )
179 } else {
180 let normal_slot_index = slot.saturating_sub(self.first_normal_slot);
181 let normal_epoch_index = normal_slot_index
182 .checked_div(self.slots_per_epoch)
183 .unwrap_or(0);
184 let epoch = self.first_normal_epoch.saturating_add(normal_epoch_index);
185 let slot_index = normal_slot_index
186 .checked_rem(self.slots_per_epoch)
187 .unwrap_or(0);
188 (epoch, slot_index)
189 }
190 }
191
192 pub fn get_first_slot_in_epoch(&self, epoch: u64) -> u64 {
193 if epoch <= self.first_normal_epoch {
194 2u64.saturating_pow(epoch as u32)
195 .saturating_sub(1)
196 .saturating_mul(MINIMUM_SLOTS_PER_EPOCH)
197 } else {
198 epoch
199 .saturating_sub(self.first_normal_epoch)
200 .saturating_mul(self.slots_per_epoch)
201 .saturating_add(self.first_normal_slot)
202 }
203 }
204
205 pub fn get_last_slot_in_epoch(&self, epoch: u64) -> u64 {
206 self.get_first_slot_in_epoch(epoch)
207 .saturating_add(self.get_slots_in_epoch(epoch))
208 .saturating_sub(1)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_epoch_schedule() {
218 for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 {
223 let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true);
224
225 assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0);
226 assert_eq!(
227 epoch_schedule.get_last_slot_in_epoch(0),
228 MINIMUM_SLOTS_PER_EPOCH - 1
229 );
230
231 let mut last_leader_schedule = 0;
232 let mut last_epoch = 0;
233 let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH;
234 for slot in 0..(2 * slots_per_epoch) {
235 let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot);
239 if leader_schedule != last_leader_schedule {
240 assert_eq!(leader_schedule, last_leader_schedule + 1);
241 last_leader_schedule = leader_schedule;
242 }
243
244 let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot);
245
246 if epoch != last_epoch {
248 assert_eq!(epoch, last_epoch + 1);
249 last_epoch = epoch;
250 assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot);
251 assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1);
252
253 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
257 if slots_in_epoch != last_slots_in_epoch && slots_in_epoch != slots_per_epoch {
258 assert_eq!(slots_in_epoch, last_slots_in_epoch * 2);
259 }
260 last_slots_in_epoch = slots_in_epoch;
261 }
262 assert!(offset < last_slots_in_epoch);
264 }
265
266 assert!(last_leader_schedule != 0); assert!(last_epoch != 0);
269 assert!(last_slots_in_epoch == slots_per_epoch);
271 }
272 }
273
274 #[test]
275 fn test_clone() {
276 let epoch_schedule = EpochSchedule {
277 slots_per_epoch: 1,
278 leader_schedule_slot_offset: 2,
279 warmup: true,
280 first_normal_epoch: 4,
281 first_normal_slot: 5,
282 };
283 #[allow(clippy::clone_on_copy)]
284 let cloned_epoch_schedule = epoch_schedule.clone();
285 assert_eq!(cloned_epoch_schedule, epoch_schedule);
286 }
287}