From c70be0f91413e30f555efe3cd863d502804fa757 Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Sat, 28 Sep 2024 23:46:01 +0400 Subject: [PATCH 1/6] extract epoch-schedule crate --- Cargo.lock | 14 ++ Cargo.toml | 2 + programs/sbf/Cargo.lock | 10 ++ sdk/epoch-schedule/Cargo.toml | 31 ++++ sdk/epoch-schedule/src/lib.rs | 276 ++++++++++++++++++++++++++++++ sdk/program/Cargo.toml | 2 + sdk/program/src/epoch_schedule.rs | 267 +---------------------------- 7 files changed, 339 insertions(+), 263 deletions(-) create mode 100644 sdk/epoch-schedule/Cargo.toml create mode 100644 sdk/epoch-schedule/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a8558aa59a0aa1..26c8c5f6d510ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6594,6 +6594,19 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-epoch-schedule" +version = "2.1.0" +dependencies = [ + "serde", + "serde_derive", + "solana-clock", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "static_assertions", +] + [[package]] name = "solana-faucet" version = "2.1.0" @@ -7255,6 +7268,7 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-define-syscall", + "solana-epoch-schedule", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-hash", diff --git a/Cargo.toml b/Cargo.toml index 921996e4a27749..dd30379e452dca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ members = [ "sdk/clock", "sdk/decode-error", "sdk/derivation-path", + "sdk/epoch-schedule", "sdk/feature-set", "sdk/gen-headers", "sdk/hash", @@ -400,6 +401,7 @@ solana-define-syscall = { path = "define-syscall", version = "=2.1.0" } solana-derivation-path = { path = "sdk/derivation-path", version = "=2.1.0" } solana-download-utils = { path = "download-utils", version = "=2.1.0" } solana-entry = { path = "entry", version = "=2.1.0" } +solana-epoch-schedule = { path = "sdk/epoch-schedule", version = "=2.1.0" } solana-faucet = { path = "faucet", version = "=2.1.0" } solana-feature-set = { path = "sdk/feature-set", version = "=2.1.0" } solana-fee = { path = "fee", version = "=2.1.0" } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 70d1b420f6297b..30da9fd84fcb20 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5240,6 +5240,15 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-epoch-schedule" +version = "2.1.0" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-macro", +] + [[package]] name = "solana-faucet" version = "2.1.0" @@ -5656,6 +5665,7 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-define-syscall", + "solana-epoch-schedule", "solana-hash", "solana-instruction", "solana-msg", diff --git a/sdk/epoch-schedule/Cargo.toml b/sdk/epoch-schedule/Cargo.toml new file mode 100644 index 00000000000000..c89e764d9911b2 --- /dev/null +++ b/sdk/epoch-schedule/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "solana-epoch-schedule" +description = "Configuration for Solana epochs and slots." +documentation = "https://docs.rs/solana-epoch-schedule" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-frozen-abi = { workspace = true, optional = true } +solana-frozen-abi-macro = { workspace = true, optional = true } +solana-sdk-macro = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +solana-clock = { workspace = true } +static_assertions = { workspace = true } + +[features] +frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro"] +serde = ["dep:serde", "dep:serde_derive"] + +[lints] +workspace = true diff --git a/sdk/epoch-schedule/src/lib.rs b/sdk/epoch-schedule/src/lib.rs new file mode 100644 index 00000000000000..e8f21c9f2b2bad --- /dev/null +++ b/sdk/epoch-schedule/src/lib.rs @@ -0,0 +1,276 @@ +//! Configuration for epochs and slots. +//! +//! Epochs mark a period of time composed of _slots_, for which a particular +//! [leader schedule][ls] is in effect. The epoch schedule determines the length +//! of epochs, and the timing of the next leader-schedule selection. +//! +//! [ls]: https://docs.solanalabs.com/consensus/leader-rotation#leader-schedule-rotation +//! +//! The epoch schedule does not change during the life of a blockchain, +//! though the length of an epoch does — during the initial launch of +//! the chain there is a "warmup" period, where epochs are short, with subsequent +//! epochs increasing in slots until they last for [`DEFAULT_SLOTS_PER_EPOCH`]. +#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] + +#[cfg(feature = "serde")] +use serde_derive::{Deserialize, Serialize}; +use solana_sdk_macro::CloneZeroed; + +// inlined to avoid solana_clock dep +const DEFAULT_SLOTS_PER_EPOCH: u64 = 432_000; +#[cfg(test)] +static_assertions::const_assert_eq!( + DEFAULT_SLOTS_PER_EPOCH, + solana_clock::DEFAULT_SLOTS_PER_EPOCH +); +/// The default number of slots before an epoch starts to calculate the leader schedule. +pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH; + +/// The maximum number of slots before an epoch starts to calculate the leader schedule. +/// +/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at +/// the beginning of epoch X - 1. +pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3; + +/// The minimum number of slots per epoch during the warmup period. +/// +/// Based on `MAX_LOCKOUT_HISTORY` from `vote_program`. +pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32; + +#[repr(C)] +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(rename_all = "camelCase") +)] +#[derive(Debug, CloneZeroed, PartialEq, Eq)] +pub struct EpochSchedule { + /// The maximum number of slots in each epoch. + pub slots_per_epoch: u64, + + /// A number of slots before beginning of an epoch to calculate + /// a leader schedule for that epoch. + pub leader_schedule_slot_offset: u64, + + /// Whether epochs start short and grow. + pub warmup: bool, + + /// The first epoch after the warmup period. + /// + /// Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. + pub first_normal_epoch: u64, + + /// The first slot after the warmup period. + /// + /// Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. + pub first_normal_slot: u64, +} + +impl Default for EpochSchedule { + fn default() -> Self { + Self::custom( + DEFAULT_SLOTS_PER_EPOCH, + DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET, + true, + ) + } +} + +impl EpochSchedule { + pub fn new(slots_per_epoch: u64) -> Self { + Self::custom(slots_per_epoch, slots_per_epoch, true) + } + pub fn without_warmup() -> Self { + Self::custom( + DEFAULT_SLOTS_PER_EPOCH, + DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET, + false, + ) + } + pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self { + assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH); + let (first_normal_epoch, first_normal_slot) = if warmup { + let next_power_of_two = slots_per_epoch.next_power_of_two(); + let log2_slots_per_epoch = next_power_of_two + .trailing_zeros() + .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()); + + ( + u64::from(log2_slots_per_epoch), + next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH), + ) + } else { + (0, 0) + }; + EpochSchedule { + slots_per_epoch, + leader_schedule_slot_offset, + warmup, + first_normal_epoch, + first_normal_slot, + } + } + + /// get the length of the given epoch (in slots) + pub fn get_slots_in_epoch(&self, epoch: u64) -> u64 { + if epoch < self.first_normal_epoch { + 2u64.saturating_pow( + (epoch as u32).saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()), + ) + } else { + self.slots_per_epoch + } + } + + /// get the epoch for which the given slot should save off + /// information about stakers + pub fn get_leader_schedule_epoch(&self, slot: u64) -> u64 { + if slot < self.first_normal_slot { + // until we get to normal slots, behave as if leader_schedule_slot_offset == slots_per_epoch + self.get_epoch_and_slot_index(slot).0.saturating_add(1) + } else { + let new_slots_since_first_normal_slot = slot.saturating_sub(self.first_normal_slot); + let new_first_normal_leader_schedule_slot = + new_slots_since_first_normal_slot.saturating_add(self.leader_schedule_slot_offset); + let new_epochs_since_first_normal_leader_schedule = + new_first_normal_leader_schedule_slot + .checked_div(self.slots_per_epoch) + .unwrap_or(0); + self.first_normal_epoch + .saturating_add(new_epochs_since_first_normal_leader_schedule) + } + } + + /// get epoch for the given slot + pub fn get_epoch(&self, slot: u64) -> u64 { + self.get_epoch_and_slot_index(slot).0 + } + + /// get epoch and offset into the epoch for the given slot + pub fn get_epoch_and_slot_index(&self, slot: u64) -> (u64, u64) { + if slot < self.first_normal_slot { + let epoch = slot + .saturating_add(MINIMUM_SLOTS_PER_EPOCH) + .saturating_add(1) + .next_power_of_two() + .trailing_zeros() + .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()) + .saturating_sub(1); + + let epoch_len = + 2u64.saturating_pow(epoch.saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros())); + + ( + u64::from(epoch), + slot.saturating_sub(epoch_len.saturating_sub(MINIMUM_SLOTS_PER_EPOCH)), + ) + } else { + let normal_slot_index = slot.saturating_sub(self.first_normal_slot); + let normal_epoch_index = normal_slot_index + .checked_div(self.slots_per_epoch) + .unwrap_or(0); + let epoch = self.first_normal_epoch.saturating_add(normal_epoch_index); + let slot_index = normal_slot_index + .checked_rem(self.slots_per_epoch) + .unwrap_or(0); + (epoch, slot_index) + } + } + + pub fn get_first_slot_in_epoch(&self, epoch: u64) -> u64 { + if epoch <= self.first_normal_epoch { + 2u64.saturating_pow(epoch as u32) + .saturating_sub(1) + .saturating_mul(MINIMUM_SLOTS_PER_EPOCH) + } else { + epoch + .saturating_sub(self.first_normal_epoch) + .saturating_mul(self.slots_per_epoch) + .saturating_add(self.first_normal_slot) + } + } + + pub fn get_last_slot_in_epoch(&self, epoch: u64) -> u64 { + self.get_first_slot_in_epoch(epoch) + .saturating_add(self.get_slots_in_epoch(epoch)) + .saturating_sub(1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_epoch_schedule() { + // one week of slots at 8 ticks/slot, 10 ticks/sec is + // (1 * 7 * 24 * 4500u64).next_power_of_two(); + + // test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix + for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 { + let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true); + + assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0); + assert_eq!( + epoch_schedule.get_last_slot_in_epoch(0), + MINIMUM_SLOTS_PER_EPOCH - 1 + ); + + let mut last_leader_schedule = 0; + let mut last_epoch = 0; + let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH; + for slot in 0..(2 * slots_per_epoch) { + // verify that leader_schedule_epoch is continuous over the warmup + // and into the first normal epoch + + let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot); + if leader_schedule != last_leader_schedule { + assert_eq!(leader_schedule, last_leader_schedule + 1); + last_leader_schedule = leader_schedule; + } + + let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot); + + // verify that epoch increases continuously + if epoch != last_epoch { + assert_eq!(epoch, last_epoch + 1); + last_epoch = epoch; + assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot); + assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1); + + // verify that slots in an epoch double continuously + // until they reach slots_per_epoch + + let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch); + if slots_in_epoch != last_slots_in_epoch && slots_in_epoch != slots_per_epoch { + assert_eq!(slots_in_epoch, last_slots_in_epoch * 2); + } + last_slots_in_epoch = slots_in_epoch; + } + // verify that the slot offset is less than slots_in_epoch + assert!(offset < last_slots_in_epoch); + } + + // assert that these changed ;) + assert!(last_leader_schedule != 0); // t + assert!(last_epoch != 0); + // assert that we got to "normal" mode + assert!(last_slots_in_epoch == slots_per_epoch); + } + } + + #[test] + fn test_clone() { + let epoch_schedule = EpochSchedule { + slots_per_epoch: 1, + leader_schedule_slot_offset: 2, + warmup: true, + first_normal_epoch: 4, + first_normal_slot: 5, + }; + #[allow(clippy::clone_on_copy)] + let cloned_epoch_schedule = epoch_schedule.clone(); + assert_eq!(cloned_epoch_schedule, epoch_schedule); + } +} diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index f7bc8ba8aeaa96..d78f83134f8db4 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -35,6 +35,7 @@ solana-account-info = { workspace = true, features = ["bincode"] } solana-atomic-u64 = { workspace = true } solana-clock = { workspace = true, features = ["serde"] } solana-decode-error = { workspace = true } +solana-epoch-schedule = { workspace = true, features = ["serde"] } solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] } solana-frozen-abi-macro = { workspace = true, optional = true, features = ["frozen-abi"] } solana-hash = { workspace = true, features = [ @@ -126,6 +127,7 @@ dev-context-only-utils = ["dep:qualifier_attr"] frozen-abi = [ "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", + "solana-epoch-schedule/frozen-abi", "solana-hash/frozen-abi", "solana-instruction/frozen-abi", "solana-pubkey/frozen-abi", diff --git a/sdk/program/src/epoch_schedule.rs b/sdk/program/src/epoch_schedule.rs index d36f34aacf64ab..bd2dbdee4a7136 100644 --- a/sdk/program/src/epoch_schedule.rs +++ b/sdk/program/src/epoch_schedule.rs @@ -1,263 +1,4 @@ -//! Configuration for epochs and slots. -//! -//! Epochs mark a period of time composed of _slots_, for which a particular -//! [leader schedule][ls] is in effect. The epoch schedule determines the length -//! of epochs, and the timing of the next leader-schedule selection. -//! -//! [ls]: https://docs.solanalabs.com/consensus/leader-rotation#leader-schedule-rotation -//! -//! The epoch schedule does not change during the life of a blockchain, -//! though the length of an epoch does — during the initial launch of -//! the chain there is a "warmup" period, where epochs are short, with subsequent -//! epochs increasing in slots until they last for [`DEFAULT_SLOTS_PER_EPOCH`]. - -pub use solana_clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}; -use solana_sdk_macro::CloneZeroed; - -/// The default number of slots before an epoch starts to calculate the leader schedule. -pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH; - -/// The maximum number of slots before an epoch starts to calculate the leader schedule. -/// -/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at -/// the beginning of epoch X - 1. -pub const MAX_LEADER_SCHEDULE_EPOCH_OFFSET: u64 = 3; - -/// The minimum number of slots per epoch during the warmup period. -/// -/// Based on `MAX_LOCKOUT_HISTORY` from `vote_program`. -pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32; - -#[repr(C)] -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, CloneZeroed, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EpochSchedule { - /// The maximum number of slots in each epoch. - pub slots_per_epoch: u64, - - /// A number of slots before beginning of an epoch to calculate - /// a leader schedule for that epoch. - pub leader_schedule_slot_offset: u64, - - /// Whether epochs start short and grow. - pub warmup: bool, - - /// The first epoch after the warmup period. - /// - /// Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. - pub first_normal_epoch: Epoch, - - /// The first slot after the warmup period. - /// - /// Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. - pub first_normal_slot: Slot, -} - -impl Default for EpochSchedule { - fn default() -> Self { - Self::custom( - DEFAULT_SLOTS_PER_EPOCH, - DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET, - true, - ) - } -} - -impl EpochSchedule { - pub fn new(slots_per_epoch: u64) -> Self { - Self::custom(slots_per_epoch, slots_per_epoch, true) - } - pub fn without_warmup() -> Self { - Self::custom( - DEFAULT_SLOTS_PER_EPOCH, - DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET, - false, - ) - } - pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self { - assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH); - let (first_normal_epoch, first_normal_slot) = if warmup { - let next_power_of_two = slots_per_epoch.next_power_of_two(); - let log2_slots_per_epoch = next_power_of_two - .trailing_zeros() - .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()); - - ( - u64::from(log2_slots_per_epoch), - next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH), - ) - } else { - (0, 0) - }; - EpochSchedule { - slots_per_epoch, - leader_schedule_slot_offset, - warmup, - first_normal_epoch, - first_normal_slot, - } - } - - /// get the length of the given epoch (in slots) - pub fn get_slots_in_epoch(&self, epoch: Epoch) -> u64 { - if epoch < self.first_normal_epoch { - 2u64.saturating_pow( - (epoch as u32).saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()), - ) - } else { - self.slots_per_epoch - } - } - - /// get the epoch for which the given slot should save off - /// information about stakers - pub fn get_leader_schedule_epoch(&self, slot: Slot) -> Epoch { - if slot < self.first_normal_slot { - // until we get to normal slots, behave as if leader_schedule_slot_offset == slots_per_epoch - self.get_epoch_and_slot_index(slot).0.saturating_add(1) - } else { - let new_slots_since_first_normal_slot = slot.saturating_sub(self.first_normal_slot); - let new_first_normal_leader_schedule_slot = - new_slots_since_first_normal_slot.saturating_add(self.leader_schedule_slot_offset); - let new_epochs_since_first_normal_leader_schedule = - new_first_normal_leader_schedule_slot - .checked_div(self.slots_per_epoch) - .unwrap_or(0); - self.first_normal_epoch - .saturating_add(new_epochs_since_first_normal_leader_schedule) - } - } - - /// get epoch for the given slot - pub fn get_epoch(&self, slot: Slot) -> Epoch { - self.get_epoch_and_slot_index(slot).0 - } - - /// get epoch and offset into the epoch for the given slot - pub fn get_epoch_and_slot_index(&self, slot: Slot) -> (Epoch, u64) { - if slot < self.first_normal_slot { - let epoch = slot - .saturating_add(MINIMUM_SLOTS_PER_EPOCH) - .saturating_add(1) - .next_power_of_two() - .trailing_zeros() - .saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()) - .saturating_sub(1); - - let epoch_len = - 2u64.saturating_pow(epoch.saturating_add(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros())); - - ( - u64::from(epoch), - slot.saturating_sub(epoch_len.saturating_sub(MINIMUM_SLOTS_PER_EPOCH)), - ) - } else { - let normal_slot_index = slot.saturating_sub(self.first_normal_slot); - let normal_epoch_index = normal_slot_index - .checked_div(self.slots_per_epoch) - .unwrap_or(0); - let epoch = self.first_normal_epoch.saturating_add(normal_epoch_index); - let slot_index = normal_slot_index - .checked_rem(self.slots_per_epoch) - .unwrap_or(0); - (epoch, slot_index) - } - } - - pub fn get_first_slot_in_epoch(&self, epoch: Epoch) -> Slot { - if epoch <= self.first_normal_epoch { - 2u64.saturating_pow(epoch as u32) - .saturating_sub(1) - .saturating_mul(MINIMUM_SLOTS_PER_EPOCH) - } else { - epoch - .saturating_sub(self.first_normal_epoch) - .saturating_mul(self.slots_per_epoch) - .saturating_add(self.first_normal_slot) - } - } - - pub fn get_last_slot_in_epoch(&self, epoch: Epoch) -> Slot { - self.get_first_slot_in_epoch(epoch) - .saturating_add(self.get_slots_in_epoch(epoch)) - .saturating_sub(1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_epoch_schedule() { - // one week of slots at 8 ticks/slot, 10 ticks/sec is - // (1 * 7 * 24 * 4500u64).next_power_of_two(); - - // test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix - for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 { - let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true); - - assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0); - assert_eq!( - epoch_schedule.get_last_slot_in_epoch(0), - MINIMUM_SLOTS_PER_EPOCH - 1 - ); - - let mut last_leader_schedule = 0; - let mut last_epoch = 0; - let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH; - for slot in 0..(2 * slots_per_epoch) { - // verify that leader_schedule_epoch is continuous over the warmup - // and into the first normal epoch - - let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot); - if leader_schedule != last_leader_schedule { - assert_eq!(leader_schedule, last_leader_schedule + 1); - last_leader_schedule = leader_schedule; - } - - let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot); - - // verify that epoch increases continuously - if epoch != last_epoch { - assert_eq!(epoch, last_epoch + 1); - last_epoch = epoch; - assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot); - assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1); - - // verify that slots in an epoch double continuously - // until they reach slots_per_epoch - - let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch); - if slots_in_epoch != last_slots_in_epoch && slots_in_epoch != slots_per_epoch { - assert_eq!(slots_in_epoch, last_slots_in_epoch * 2); - } - last_slots_in_epoch = slots_in_epoch; - } - // verify that the slot offset is less than slots_in_epoch - assert!(offset < last_slots_in_epoch); - } - - // assert that these changed ;) - assert!(last_leader_schedule != 0); // t - assert!(last_epoch != 0); - // assert that we got to "normal" mode - assert!(last_slots_in_epoch == slots_per_epoch); - } - } - - #[test] - fn test_clone() { - let epoch_schedule = EpochSchedule { - slots_per_epoch: 1, - leader_schedule_slot_offset: 2, - warmup: true, - first_normal_epoch: 4, - first_normal_slot: 5, - }; - #[allow(clippy::clone_on_copy)] - let cloned_epoch_schedule = epoch_schedule.clone(); - assert_eq!(cloned_epoch_schedule, epoch_schedule); - } -} +pub use { + solana_clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}, + solana_epoch_schedule::*, +}; From 9078ab9f14aec34d0c5ea3ac6e725b3fe86828fd Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Sat, 28 Sep 2024 23:54:10 +0400 Subject: [PATCH 2/6] make epoch-schedule crate no-std --- sdk/epoch-schedule/Cargo.toml | 3 ++- sdk/epoch-schedule/src/lib.rs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/epoch-schedule/Cargo.toml b/sdk/epoch-schedule/Cargo.toml index c89e764d9911b2..d902f67a8107cd 100644 --- a/sdk/epoch-schedule/Cargo.toml +++ b/sdk/epoch-schedule/Cargo.toml @@ -24,8 +24,9 @@ solana-clock = { workspace = true } static_assertions = { workspace = true } [features] -frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro"] +frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", "std"] serde = ["dep:serde", "dep:serde_derive"] +std = [] [lints] workspace = true diff --git a/sdk/epoch-schedule/src/lib.rs b/sdk/epoch-schedule/src/lib.rs index e8f21c9f2b2bad..f4a6a2496034aa 100644 --- a/sdk/epoch-schedule/src/lib.rs +++ b/sdk/epoch-schedule/src/lib.rs @@ -11,6 +11,9 @@ //! the chain there is a "warmup" period, where epochs are short, with subsequent //! epochs increasing in slots until they last for [`DEFAULT_SLOTS_PER_EPOCH`]. #![cfg_attr(feature = "frozen-abi", feature(min_specialization))] +#![no_std] +#[cfg(feature = "std")] +extern crate std; #[cfg(feature = "serde")] use serde_derive::{Deserialize, Serialize}; From 3a78a84937f11892fc0f792a373159fdb63edcc3 Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Sun, 29 Sep 2024 00:12:33 +0400 Subject: [PATCH 3/6] simplify features --- sdk/epoch-schedule/Cargo.toml | 3 +-- sdk/epoch-schedule/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/epoch-schedule/Cargo.toml b/sdk/epoch-schedule/Cargo.toml index d902f67a8107cd..c89e764d9911b2 100644 --- a/sdk/epoch-schedule/Cargo.toml +++ b/sdk/epoch-schedule/Cargo.toml @@ -24,9 +24,8 @@ solana-clock = { workspace = true } static_assertions = { workspace = true } [features] -frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", "std"] +frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro"] serde = ["dep:serde", "dep:serde_derive"] -std = [] [lints] workspace = true diff --git a/sdk/epoch-schedule/src/lib.rs b/sdk/epoch-schedule/src/lib.rs index f4a6a2496034aa..e0d9d1c24e3b30 100644 --- a/sdk/epoch-schedule/src/lib.rs +++ b/sdk/epoch-schedule/src/lib.rs @@ -12,7 +12,7 @@ //! epochs increasing in slots until they last for [`DEFAULT_SLOTS_PER_EPOCH`]. #![cfg_attr(feature = "frozen-abi", feature(min_specialization))] #![no_std] -#[cfg(feature = "std")] +#[cfg(feature = "frozen-abi")] extern crate std; #[cfg(feature = "serde")] From 1ad1c2394f5575f0a667754364f191deb4e03fc5 Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Sun, 29 Sep 2024 02:00:12 +0400 Subject: [PATCH 4/6] update digest --- sdk/src/genesis_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 24208a76363dbf..6b48f8fd1e7645 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -87,7 +87,7 @@ impl FromStr for ClusterType { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "2eGYc5mpKqDsS8sZfNS4mVq4qPptXYa9hSid2Hpv4DkQ") + frozen_abi(digest = "7kinRF6sWtJWxz9Wt8Zu4CB4SxaiFsNW2y9wnZH1FkNM") )] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct GenesisConfig { From 88d1d4b9e59316ee6940b6f570889e0a8e3768b4 Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Tue, 8 Oct 2024 01:49:17 +0400 Subject: [PATCH 5/6] update digest --- runtime/src/bank/serde_snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index eeacfb3f556015..a088979e7bf429 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -546,7 +546,7 @@ mod tests { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "7xkyjhBmj1xk3ykcbufPCnBKKkcpQ3AjKFUmH1r8MRnu") + frozen_abi(digest = "D7zx9HfzJa1cqhNariHYufgyLUVLR64iPoMFzRYqs8rZ") )] #[derive(Serialize)] pub struct BankAbiTestWrapper { From c5ef32ff9c0dfaaa617c8cb3b4b1b30803b00dd4 Mon Sep 17 00:00:00 2001 From: kevinheavey Date: Wed, 9 Oct 2024 18:45:18 +0400 Subject: [PATCH 6/6] add deprecation to re-exports --- sdk/program/src/epoch_schedule.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/program/src/epoch_schedule.rs b/sdk/program/src/epoch_schedule.rs index bd2dbdee4a7136..56addac3218069 100644 --- a/sdk/program/src/epoch_schedule.rs +++ b/sdk/program/src/epoch_schedule.rs @@ -1,3 +1,7 @@ +#[deprecated( + since = "2.1.0", + note = "Use solana-clock and solana-epoch-schedule crates instead." +)] pub use { solana_clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}, solana_epoch_schedule::*,