Skip to main content

solana_sysvar/
epoch_schedule.rs

1//! Information about epoch duration.
2//!
3//! The _epoch schedule_ sysvar provides access to the [`EpochSchedule`] type,
4//! which includes the number of slots per epoch, timing of leader schedule
5//! selection, and information about epoch warm-up time.
6//!
7//! [`EpochSchedule`] implements [`Sysvar::get`] and can be loaded efficiently without
8//! passing the sysvar account ID to the program.
9//!
10//! See also the Solana [documentation on the epoch schedule sysvar][sdoc].
11//!
12//! [sdoc]: https://docs.solanalabs.com/runtime/sysvars#epochschedule
13//!
14//! # Examples
15//!
16//! Accessing via on-chain program directly:
17//!
18//! ```no_run
19//! # use solana_account_info::AccountInfo;
20//! # use solana_epoch_schedule::EpochSchedule;
21//! # use solana_msg::msg;
22//! # use solana_program_error::{ProgramError, ProgramResult};
23//! # use solana_pubkey::Pubkey;
24//! # use solana_sdk_ids::sysvar::epoch_schedule;
25//! # use solana_sysvar::Sysvar;
26//! fn process_instruction(
27//!     program_id: &Pubkey,
28//!     accounts: &[AccountInfo],
29//!     instruction_data: &[u8],
30//! ) -> ProgramResult {
31//!
32//!     let epoch_schedule = EpochSchedule::get()?;
33//!     msg!("epoch_schedule: {:#?}", epoch_schedule);
34//!
35//!     Ok(())
36//! }
37//! #
38//! # use solana_sysvar_id::SysvarId;
39//! # let p = EpochSchedule::id();
40//! # let l = &mut 1120560;
41//! # let d = &mut vec![0, 32, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
42//! # let a = AccountInfo::new(&p, false, false, l, d, &p, false);
43//! # let accounts = &[a.clone(), a];
44//! # process_instruction(
45//! #     &Pubkey::new_unique(),
46//! #     accounts,
47//! #     &[],
48//! # )?;
49//! # Ok::<(), ProgramError>(())
50//! ```
51//!
52//! Accessing via on-chain program's account parameters:
53//!
54//! ```
55//! # use solana_account_info::{AccountInfo, next_account_info};
56//! # use solana_epoch_schedule::EpochSchedule;
57//! # use solana_msg::msg;
58//! # use solana_program_error::{ProgramError, ProgramResult};
59//! # use solana_pubkey::Pubkey;
60//! # use solana_sdk_ids::sysvar::epoch_schedule;
61//! # use solana_sysvar::{Sysvar, SysvarSerialize};
62//! fn process_instruction(
63//!     program_id: &Pubkey,
64//!     accounts: &[AccountInfo],
65//!     instruction_data: &[u8],
66//! ) -> ProgramResult {
67//!     let account_info_iter = &mut accounts.iter();
68//!     let epoch_schedule_account_info = next_account_info(account_info_iter)?;
69//!
70//!     assert!(epoch_schedule::check_id(epoch_schedule_account_info.key));
71//!
72//!     let epoch_schedule = EpochSchedule::from_account_info(epoch_schedule_account_info)?;
73//!     msg!("epoch_schedule: {:#?}", epoch_schedule);
74//!
75//!     Ok(())
76//! }
77//! #
78//! # use solana_sysvar_id::SysvarId;
79//! # let p = EpochSchedule::id();
80//! # let l = &mut 1120560;
81//! # let d = &mut vec![0, 32, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
82//! # let a = AccountInfo::new(&p, false, false, l, d, &p, false);
83//! # let accounts = &[a.clone(), a];
84//! # process_instruction(
85//! #     &Pubkey::new_unique(),
86//! #     accounts,
87//! #     &[],
88//! # )?;
89//! # Ok::<(), ProgramError>(())
90//! ```
91//!
92//! Accessing via the RPC client:
93//!
94//! ```
95//! # use solana_epoch_schedule::EpochSchedule;
96//! # use solana_example_mocks::solana_account;
97//! # use solana_example_mocks::solana_rpc_client;
98//! # use solana_rpc_client::rpc_client::RpcClient;
99//! # use solana_account::Account;
100//! # use solana_sdk_ids::sysvar::epoch_schedule;
101//! # use anyhow::Result;
102//! #
103//! fn print_sysvar_epoch_schedule(client: &RpcClient) -> Result<()> {
104//! #   client.set_get_account_response(epoch_schedule::ID, Account {
105//! #       lamports: 1120560,
106//! #       data: vec![0, 32, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
107//! #       owner: solana_sdk_ids::system_program::ID,
108//! #       executable: false,
109//! # });
110//! #
111//!     let epoch_schedule = client.get_account(&epoch_schedule::ID)?;
112//!     let data: EpochSchedule = bincode::deserialize(&epoch_schedule.data)?;
113//!
114//!     Ok(())
115//! }
116//! #
117//! # let client = RpcClient::new(String::new());
118//! # print_sysvar_epoch_schedule(&client)?;
119//! #
120//! # Ok::<(), anyhow::Error>(())
121//! ```
122use crate::Sysvar;
123#[cfg(feature = "bincode")]
124use crate::SysvarSerialize;
125pub use {
126    solana_epoch_schedule::EpochSchedule,
127    solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID},
128};
129
130/// Pod (Plain Old Data) representation of [`EpochSchedule`] with no padding.
131///
132/// This type can be safely loaded via `sol_get_sysvar` without undefined behavior.
133/// Provides performant zero-copy accessors as an alternative to the `EpochSchedule` type.
134#[repr(C)]
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub struct PodEpochSchedule {
137    slots_per_epoch: [u8; 8],
138    leader_schedule_slot_offset: [u8; 8],
139    warmup: u8,
140    first_normal_epoch: [u8; 8],
141    first_normal_slot: [u8; 8],
142}
143
144const POD_EPOCH_SCHEDULE_SIZE: usize = 33;
145const _: () = assert!(core::mem::size_of::<PodEpochSchedule>() == POD_EPOCH_SCHEDULE_SIZE);
146
147impl PodEpochSchedule {
148    /// Fetch the sysvar data using the `sol_get_sysvar` syscall.
149    /// This provides an alternative to `EpochSchedule` which provides zero-copy accessors.
150    pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
151        let mut pod = core::mem::MaybeUninit::<Self>::uninit();
152        // Safety: `get_sysvar_unchecked` will initialize `pod` with the sysvar data,
153        // and error if unsuccessful.
154        unsafe {
155            crate::get_sysvar_unchecked(
156                pod.as_mut_ptr() as *mut u8,
157                (&id()) as *const _ as *const u8,
158                0,
159                POD_EPOCH_SCHEDULE_SIZE as u64,
160            )?;
161            Ok(pod.assume_init())
162        }
163    }
164
165    pub fn slots_per_epoch(&self) -> u64 {
166        u64::from_le_bytes(self.slots_per_epoch)
167    }
168
169    pub fn leader_schedule_slot_offset(&self) -> u64 {
170        u64::from_le_bytes(self.leader_schedule_slot_offset)
171    }
172
173    pub fn warmup(&self) -> bool {
174        // SAFETY: upstream invariant: the sysvar data is created exclusively
175        // by the Solana runtime and serializes bool as 0x00 or 0x01.
176        self.warmup > 0
177    }
178
179    pub fn first_normal_epoch(&self) -> u64 {
180        u64::from_le_bytes(self.first_normal_epoch)
181    }
182
183    pub fn first_normal_slot(&self) -> u64 {
184        u64::from_le_bytes(self.first_normal_slot)
185    }
186}
187
188impl From<PodEpochSchedule> for EpochSchedule {
189    fn from(pod: PodEpochSchedule) -> Self {
190        Self {
191            slots_per_epoch: pod.slots_per_epoch(),
192            leader_schedule_slot_offset: pod.leader_schedule_slot_offset(),
193            warmup: pod.warmup(),
194            first_normal_epoch: pod.first_normal_epoch(),
195            first_normal_slot: pod.first_normal_slot(),
196        }
197    }
198}
199
200impl Sysvar for EpochSchedule {
201    fn get() -> Result<Self, solana_program_error::ProgramError> {
202        Ok(PodEpochSchedule::fetch()?.into())
203    }
204}
205
206#[cfg(feature = "bincode")]
207impl SysvarSerialize for EpochSchedule {}
208
209#[cfg(test)]
210mod tests {
211    use {super::*, crate::Sysvar, serial_test::serial};
212
213    #[test]
214    fn test_pod_epoch_schedule_conversion() {
215        let pod = PodEpochSchedule {
216            slots_per_epoch: 432000u64.to_le_bytes(),
217            leader_schedule_slot_offset: 432000u64.to_le_bytes(),
218            warmup: 1,
219            first_normal_epoch: 14u64.to_le_bytes(),
220            first_normal_slot: 524256u64.to_le_bytes(),
221        };
222
223        let epoch_schedule = EpochSchedule::from(pod);
224
225        assert_eq!(epoch_schedule.slots_per_epoch, 432000);
226        assert_eq!(epoch_schedule.leader_schedule_slot_offset, 432000);
227        assert!(epoch_schedule.warmup);
228        assert_eq!(epoch_schedule.first_normal_epoch, 14);
229        assert_eq!(epoch_schedule.first_normal_slot, 524256);
230    }
231
232    #[test]
233    #[serial]
234    #[cfg(feature = "bincode")]
235    fn test_epoch_schedule_get() {
236        let expected = EpochSchedule::custom(1234, 5678, false);
237        let data = bincode::serialize(&expected).unwrap();
238        assert_eq!(data.len(), 33);
239
240        crate::tests::mock_get_sysvar_syscall(&data);
241        let got = EpochSchedule::get().unwrap();
242        assert_eq!(got, expected);
243    }
244}