1#[cfg(feature = "bytemuck")]
50use bytemuck_derive::{Pod, Zeroable};
51#[cfg(feature = "bincode")]
52use {crate::Sysvar, solana_account_info::AccountInfo};
53use {solana_clock::Slot, solana_hash::Hash};
54
55#[cfg(feature = "bytemuck")]
56const U64_SIZE: usize = std::mem::size_of::<u64>();
57
58#[cfg(any(feature = "bytemuck", feature = "bincode"))]
59const SYSVAR_LEN: usize = 20_488; pub use {
62 solana_sdk_ids::sysvar::slot_hashes::{check_id, id, ID},
63 solana_slot_hashes::SlotHashes,
64 solana_sysvar_id::SysvarId,
65};
66
67#[cfg(feature = "bincode")]
68impl Sysvar for SlotHashes {
69 fn size_of() -> usize {
71 SYSVAR_LEN
73 }
74 fn from_account_info(
75 _account_info: &AccountInfo,
76 ) -> Result<Self, solana_program_error::ProgramError> {
77 Err(solana_program_error::ProgramError::UnsupportedSysvar)
79 }
80}
81
82#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
84#[derive(Copy, Clone, Default)]
85#[repr(C)]
86pub struct PodSlotHash {
87 pub slot: Slot,
88 pub hash: Hash,
89}
90
91#[cfg(feature = "bytemuck")]
92#[derive(Default)]
97pub struct PodSlotHashes {
98 data: Vec<u8>,
99 slot_hashes_start: usize,
100 slot_hashes_end: usize,
101}
102
103#[cfg(feature = "bytemuck")]
104impl PodSlotHashes {
105 pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
107 let sysvar_len = SYSVAR_LEN;
109 let mut data = vec![0; sysvar_len];
110
111 if data.as_ptr().align_offset(8) != 0 {
113 return Err(solana_program_error::ProgramError::InvalidAccountData);
114 }
115
116 crate::get_sysvar(
119 &mut data,
120 &SlotHashes::id(),
121 0,
122 sysvar_len as u64,
123 )?;
124
125 let length = data
131 .get(..U64_SIZE)
132 .and_then(|bytes| bytes.try_into().ok())
133 .map(u64::from_le_bytes)
134 .and_then(|length| length.checked_mul(std::mem::size_of::<PodSlotHash>() as u64))
135 .ok_or(solana_program_error::ProgramError::InvalidAccountData)?;
136
137 let slot_hashes_start = U64_SIZE;
138 let slot_hashes_end = slot_hashes_start.saturating_add(length as usize);
139
140 Ok(Self {
141 data,
142 slot_hashes_start,
143 slot_hashes_end,
144 })
145 }
146
147 pub fn as_slice(&self) -> Result<&[PodSlotHash], solana_program_error::ProgramError> {
150 self.data
151 .get(self.slot_hashes_start..self.slot_hashes_end)
152 .and_then(|data| bytemuck::try_cast_slice(data).ok())
153 .ok_or(solana_program_error::ProgramError::InvalidAccountData)
154 }
155
156 pub fn get(&self, slot: &Slot) -> Result<Option<Hash>, solana_program_error::ProgramError> {
159 self.as_slice().map(|pod_hashes| {
160 pod_hashes
161 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
162 .map(|idx| pod_hashes[idx].hash)
163 .ok()
164 })
165 }
166
167 pub fn position(
170 &self,
171 slot: &Slot,
172 ) -> Result<Option<usize>, solana_program_error::ProgramError> {
173 self.as_slice().map(|pod_hashes| {
174 pod_hashes
175 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
176 .ok()
177 })
178 }
179}
180
181#[deprecated(since = "2.1.0", note = "Please use `PodSlotHashes` instead")]
183pub struct SlotHashesSysvar;
184
185#[allow(deprecated)]
186impl SlotHashesSysvar {
187 #[cfg(feature = "bytemuck")]
188 pub fn get(slot: &Slot) -> Result<Option<Hash>, solana_program_error::ProgramError> {
191 get_pod_slot_hashes().map(|pod_hashes| {
192 pod_hashes
193 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
194 .map(|idx| pod_hashes[idx].hash)
195 .ok()
196 })
197 }
198
199 #[cfg(feature = "bytemuck")]
200 pub fn position(slot: &Slot) -> Result<Option<usize>, solana_program_error::ProgramError> {
203 get_pod_slot_hashes().map(|pod_hashes| {
204 pod_hashes
205 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
206 .ok()
207 })
208 }
209}
210
211#[cfg(feature = "bytemuck")]
212fn get_pod_slot_hashes() -> Result<Vec<PodSlotHash>, solana_program_error::ProgramError> {
213 let mut pod_hashes = vec![PodSlotHash::default(); solana_slot_hashes::MAX_ENTRIES];
214 {
215 let data = bytemuck::try_cast_slice_mut::<PodSlotHash, u8>(&mut pod_hashes)
216 .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?;
217
218 if data.as_ptr().align_offset(8) != 0 {
220 return Err(solana_program_error::ProgramError::InvalidAccountData);
221 }
222
223 let offset = 8; let length = (SYSVAR_LEN as u64).saturating_sub(offset);
225 crate::get_sysvar(data, &SlotHashes::id(), offset, length)?;
226 }
227 Ok(pod_hashes)
228}
229
230#[cfg(test)]
231mod tests {
232 use {
233 super::*, crate::tests::mock_get_sysvar_syscall, serial_test::serial, solana_hash::Hash,
234 solana_sha256_hasher::hash, solana_slot_hashes::MAX_ENTRIES, test_case::test_case,
235 };
236
237 #[test]
238 fn test_size_of() {
239 assert_eq!(
240 SlotHashes::size_of(),
241 bincode::serialized_size(
242 &(0..MAX_ENTRIES)
243 .map(|slot| (slot as Slot, Hash::default()))
244 .collect::<SlotHashes>()
245 )
246 .unwrap() as usize
247 );
248 }
249
250 fn mock_slot_hashes(slot_hashes: &SlotHashes) {
251 let mut data = vec![0; SlotHashes::size_of()];
253 bincode::serialize_into(&mut data[..], slot_hashes).unwrap();
254 mock_get_sysvar_syscall(&data);
255 }
256
257 #[test_case(0)]
258 #[test_case(1)]
259 #[test_case(2)]
260 #[test_case(5)]
261 #[test_case(10)]
262 #[test_case(64)]
263 #[test_case(128)]
264 #[test_case(192)]
265 #[test_case(256)]
266 #[test_case(384)]
267 #[test_case(MAX_ENTRIES)]
268 #[serial]
269 fn test_pod_slot_hashes(num_entries: usize) {
270 let mut slot_hashes = vec![];
271 for i in 0..num_entries {
272 slot_hashes.push((
273 i as u64,
274 hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
275 ));
276 }
277
278 let check_slot_hashes = SlotHashes::new(&slot_hashes);
279 mock_slot_hashes(&check_slot_hashes);
280
281 let pod_slot_hashes = PodSlotHashes::fetch().unwrap();
282
283 let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
286 assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
287
288 for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
291 assert_eq!(
293 pod_slot_hashes.get(slot).unwrap().as_ref(),
294 check_slot_hashes.get(slot),
295 );
296 assert_eq!(
298 pod_slot_hashes.position(slot).unwrap(),
299 check_slot_hashes.position(slot),
300 );
301 }
302
303 let not_a_slot = num_entries.saturating_add(1) as u64;
305 assert_eq!(
306 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
307 check_slot_hashes.get(¬_a_slot),
308 );
309 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
310 assert_eq!(
311 pod_slot_hashes.position(¬_a_slot).unwrap(),
312 check_slot_hashes.position(¬_a_slot),
313 );
314 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
315
316 let not_a_slot = num_entries.saturating_add(2) as u64;
317 assert_eq!(
318 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
319 check_slot_hashes.get(¬_a_slot),
320 );
321 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
322 assert_eq!(
323 pod_slot_hashes.position(¬_a_slot).unwrap(),
324 check_slot_hashes.position(¬_a_slot),
325 );
326 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
327 }
328
329 #[allow(deprecated)]
330 #[serial]
331 #[test]
332 fn test_slot_hashes_sysvar() {
333 let mut slot_hashes = vec![];
334 for i in 0..MAX_ENTRIES {
335 slot_hashes.push((
336 i as u64,
337 hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
338 ));
339 }
340
341 let check_slot_hashes = SlotHashes::new(&slot_hashes);
342 mock_get_sysvar_syscall(&bincode::serialize(&check_slot_hashes).unwrap());
343
344 assert_eq!(
346 SlotHashesSysvar::get(&0).unwrap().as_ref(),
347 check_slot_hashes.get(&0),
348 );
349 assert_eq!(
350 SlotHashesSysvar::get(&256).unwrap().as_ref(),
351 check_slot_hashes.get(&256),
352 );
353 assert_eq!(
354 SlotHashesSysvar::get(&511).unwrap().as_ref(),
355 check_slot_hashes.get(&511),
356 );
357 assert_eq!(
359 SlotHashesSysvar::get(&600).unwrap().as_ref(),
360 check_slot_hashes.get(&600),
361 );
362
363 assert_eq!(
365 SlotHashesSysvar::position(&0).unwrap(),
366 check_slot_hashes.position(&0),
367 );
368 assert_eq!(
369 SlotHashesSysvar::position(&256).unwrap(),
370 check_slot_hashes.position(&256),
371 );
372 assert_eq!(
373 SlotHashesSysvar::position(&511).unwrap(),
374 check_slot_hashes.position(&511),
375 );
376 assert_eq!(
378 SlotHashesSysvar::position(&600).unwrap(),
379 check_slot_hashes.position(&600),
380 );
381 }
382}