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}