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