cbe_program/
epoch_schedule.rs1pub use crate::clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH};
15use cbe_sdk_macro::CloneZeroed;
16
17pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH;
19
20pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3;
25
26pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
30
31#[repr(C)]
32#[derive(Debug, CloneZeroed, Copy, PartialEq, Eq, Deserialize, Serialize, AbiExample)]
33#[serde(rename_all = "camelCase")]
34pub struct EpochSchedule {
35 pub slots_per_epoch: u64,
37
38 pub leader_schedule_slot_offset: u64,
41
42 pub warmup: bool,
44
45 pub first_normal_epoch: Epoch,
49
50 pub first_normal_slot: Slot,
54}
55
56impl Default for EpochSchedule {
57 fn default() -> Self {
58 Self::custom(
59 DEFAULT_SLOTS_PER_EPOCH,
60 DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
61 true,
62 )
63 }
64}
65
66impl EpochSchedule {
67 pub fn new(slots_per_epoch: u64) -> Self {
68 Self::custom(slots_per_epoch, slots_per_epoch, true)
69 }
70 pub fn without_warmup() -> Self {
71 Self::custom(
72 DEFAULT_SLOTS_PER_EPOCH,
73 DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
74 false,
75 )
76 }
77 pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self {
78 assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH);
79 let (first_normal_epoch, first_normal_slot) = if warmup {
80 let next_power_of_two = slots_per_epoch.next_power_of_two();
81 let log2_slots_per_epoch = next_power_of_two
82 .trailing_zeros()
83 .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
84
85 (
86 u64::from(log2_slots_per_epoch),
87 next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH),
88 )
89 } else {
90 (0, 0)
91 };
92 EpochSchedule {
93 slots_per_epoch,
94 leader_schedule_slot_offset,
95 warmup,
96 first_normal_epoch,
97 first_normal_slot,
98 }
99 }
100
101 pub fn get_slots_in_epoch(&self, epoch: Epoch) -> u64 {
103 if epoch < self.first_normal_epoch {
104 2u64.saturating_pow(
105 (epoch as u32).saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()),
106 )
107 } else {
108 self.slots_per_epoch
109 }
110 }
111
112 pub fn get_leader_schedule_epoch(&self, slot: Slot) -> Epoch {
115 if slot < self.first_normal_slot {
116 self.get_epoch_and_slot_index(slot).0.saturating_add(1)
118 } else {
119 let new_slots_since_first_normal_slot = slot.saturating_sub(self.first_normal_slot);
120 let new_first_normal_leader_schedule_slot =
121 new_slots_since_first_normal_slot.saturating_add(self.leader_schedule_slot_offset);
122 let new_epochs_since_first_normal_leader_schedule =
123 new_first_normal_leader_schedule_slot
124 .checked_div(self.slots_per_epoch)
125 .unwrap_or(0);
126 self.first_normal_epoch
127 .saturating_add(new_epochs_since_first_normal_leader_schedule)
128 }
129 }
130
131 pub fn get_epoch(&self, slot: Slot) -> Epoch {
133 self.get_epoch_and_slot_index(slot).0
134 }
135
136 pub fn get_epoch_and_slot_index(&self, slot: Slot) -> (Epoch, u64) {
138 if slot < self.first_normal_slot {
139 let epoch = slot
140 .saturating_add(MINIMUM_SLOTS_PER_EPOCH)
141 .saturating_add(1)
142 .next_power_of_two()
143 .trailing_zeros()
144 .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros())
145 .saturating_sub(1);
146
147 let epoch_len =
148 2u64.saturating_pow(epoch.saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()));
149
150 (
151 u64::from(epoch),
152 slot.saturating_sub(epoch_len.saturating_sub(MINIMUM_SLOTS_PER_EPOCH)),
153 )
154 } else {
155 let normal_slot_index = slot.saturating_sub(self.first_normal_slot);
156 let normal_epoch_index = normal_slot_index
157 .checked_div(self.slots_per_epoch)
158 .unwrap_or(0);
159 let epoch = self.first_normal_epoch.saturating_add(normal_epoch_index);
160 let slot_index = normal_slot_index
161 .checked_rem(self.slots_per_epoch)
162 .unwrap_or(0);
163 (epoch, slot_index)
164 }
165 }
166
167 pub fn get_first_slot_in_epoch(&self, epoch: Epoch) -> Slot {
168 if epoch <= self.first_normal_epoch {
169 2u64.saturating_pow(epoch as u32)
170 .saturating_sub(1)
171 .saturating_mul(MINIMUM_SLOTS_PER_EPOCH)
172 } else {
173 epoch
174 .saturating_sub(self.first_normal_epoch)
175 .saturating_mul(self.slots_per_epoch)
176 .saturating_add(self.first_normal_slot)
177 }
178 }
179
180 pub fn get_last_slot_in_epoch(&self, epoch: Epoch) -> Slot {
181 self.get_first_slot_in_epoch(epoch)
182 .saturating_add(self.get_slots_in_epoch(epoch))
183 .saturating_sub(1)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_epoch_schedule() {
193 for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 {
198 let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true);
199
200 assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0);
201 assert_eq!(
202 epoch_schedule.get_last_slot_in_epoch(0),
203 MINIMUM_SLOTS_PER_EPOCH - 1
204 );
205
206 let mut last_leader_schedule = 0;
207 let mut last_epoch = 0;
208 let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH;
209 for slot in 0..(2 * slots_per_epoch) {
210 let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot);
214 if leader_schedule != last_leader_schedule {
215 assert_eq!(leader_schedule, last_leader_schedule + 1);
216 last_leader_schedule = leader_schedule;
217 }
218
219 let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot);
220
221 if epoch != last_epoch {
223 assert_eq!(epoch, last_epoch + 1);
224 last_epoch = epoch;
225 assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot);
226 assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1);
227
228 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
232 if slots_in_epoch != last_slots_in_epoch && slots_in_epoch != slots_per_epoch {
233 assert_eq!(slots_in_epoch, last_slots_in_epoch * 2);
234 }
235 last_slots_in_epoch = slots_in_epoch;
236 }
237 assert!(offset < last_slots_in_epoch);
239 }
240
241 assert!(last_leader_schedule != 0); assert!(last_epoch != 0);
244 assert!(last_slots_in_epoch == slots_per_epoch);
246 }
247 }
248
249 #[test]
250 fn test_clone() {
251 let epoch_schedule = EpochSchedule {
252 slots_per_epoch: 1,
253 leader_schedule_slot_offset: 2,
254 warmup: true,
255 first_normal_epoch: 4,
256 first_normal_slot: 5,
257 };
258 #[allow(clippy::clone_on_copy)]
259 let cloned_epoch_schedule = epoch_schedule.clone();
260 assert_eq!(cloned_epoch_schedule, epoch_schedule);
261 }
262}