solana_sysvar/
slot_hashes.rs1#[cfg(feature = "bytemuck")]
48use bytemuck_derive::{Pod, Zeroable};
49#[cfg(feature = "bincode")]
50use {crate::SysvarSerialize, solana_account_info::AccountInfo};
51use {solana_clock::Slot, solana_hash::Hash};
52
53#[cfg(feature = "bytemuck")]
54const U64_SIZE: usize = std::mem::size_of::<u64>();
55
56pub use {
57 solana_sdk_ids::sysvar::slot_hashes::{check_id, id, ID},
58 solana_slot_hashes::{SlotHashes, SIZE},
59 solana_sysvar_id::SysvarId,
60};
61
62#[cfg(feature = "bincode")]
63impl SysvarSerialize for SlotHashes {
64 fn size_of() -> usize {
66 SIZE
68 }
69 fn from_account_info(
70 _account_info: &AccountInfo,
71 ) -> Result<Self, solana_program_error::ProgramError> {
72 Err(solana_program_error::ProgramError::UnsupportedSysvar)
74 }
75}
76
77#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
79#[derive(Copy, Clone, Default)]
80#[repr(C)]
81pub struct PodSlotHash {
82 pub slot: Slot,
83 pub hash: Hash,
84}
85
86#[cfg(feature = "bytemuck")]
87#[derive(Default)]
92pub struct PodSlotHashes {
93 data: Vec<u8>,
94 slot_hashes_start: usize,
95 slot_hashes_end: usize,
96}
97
98#[cfg(feature = "bytemuck")]
99impl PodSlotHashes {
100 pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
102 let sysvar_len = SIZE;
104 let mut data = vec![0; sysvar_len];
105
106 if data.as_ptr().align_offset(8) != 0 {
108 return Err(solana_program_error::ProgramError::InvalidAccountData);
109 }
110
111 crate::get_sysvar(
114 &mut data,
115 &SlotHashes::id(),
116 0,
117 sysvar_len as u64,
118 )?;
119
120 Self::from_bytes(data)
121 }
122
123 fn from_bytes(data: Vec<u8>) -> Result<Self, solana_program_error::ProgramError> {
124 let length = data
130 .get(..U64_SIZE)
131 .and_then(|bytes| bytes.try_into().ok())
132 .map(u64::from_le_bytes)
133 .and_then(|length| length.checked_mul(std::mem::size_of::<PodSlotHash>() as u64))
134 .ok_or(solana_program_error::ProgramError::InvalidAccountData)?;
135
136 let slot_hashes_start = U64_SIZE;
137 let slot_hashes_end = slot_hashes_start.saturating_add(length as usize);
138
139 Ok(Self {
140 data,
141 slot_hashes_start,
142 slot_hashes_end,
143 })
144 }
145
146 pub fn as_slice(&self) -> Result<&[PodSlotHash], solana_program_error::ProgramError> {
149 self.data
150 .get(self.slot_hashes_start..self.slot_hashes_end)
151 .and_then(|data| bytemuck::try_cast_slice(data).ok())
152 .ok_or(solana_program_error::ProgramError::InvalidAccountData)
153 }
154
155 pub fn get(&self, slot: &Slot) -> Result<Option<Hash>, solana_program_error::ProgramError> {
158 self.as_slice().map(|pod_hashes| {
159 pod_hashes
160 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
161 .map(|idx| pod_hashes[idx].hash)
162 .ok()
163 })
164 }
165
166 pub fn position(
169 &self,
170 slot: &Slot,
171 ) -> Result<Option<usize>, solana_program_error::ProgramError> {
172 self.as_slice().map(|pod_hashes| {
173 pod_hashes
174 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
175 .ok()
176 })
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use {
183 super::*, solana_hash::Hash, solana_sha256_hasher::hash, solana_slot_hashes::MAX_ENTRIES,
184 test_case::test_case,
185 };
186
187 #[test]
188 fn test_size_of() {
189 assert_eq!(
190 SlotHashes::size_of(),
191 bincode::serialized_size(
192 &(0..MAX_ENTRIES)
193 .map(|slot| (slot as Slot, Hash::default()))
194 .collect::<SlotHashes>()
195 )
196 .unwrap() as usize
197 );
198 }
199
200 #[test_case(0)]
201 #[test_case(1)]
202 #[test_case(2)]
203 #[test_case(5)]
204 #[test_case(10)]
205 #[test_case(64)]
206 #[test_case(128)]
207 #[test_case(192)]
208 #[test_case(256)]
209 #[test_case(384)]
210 #[test_case(MAX_ENTRIES)]
211 fn test_pod_slot_hashes(num_entries: usize) {
212 let mut slot_hashes = vec![];
213 for i in 0..num_entries {
214 slot_hashes.push((
215 i as u64,
216 hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
217 ));
218 }
219
220 let check_slot_hashes = SlotHashes::new(&slot_hashes);
221 let pod_slot_hashes =
222 PodSlotHashes::from_bytes(bincode::serialize(&check_slot_hashes).unwrap()).unwrap();
223
224 let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
227 assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
228
229 for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
232 assert_eq!(
234 pod_slot_hashes.get(slot).unwrap().as_ref(),
235 check_slot_hashes.get(slot),
236 );
237 assert_eq!(
239 pod_slot_hashes.position(slot).unwrap(),
240 check_slot_hashes.position(slot),
241 );
242 }
243
244 let not_a_slot = num_entries.saturating_add(1) as u64;
246 assert_eq!(
247 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
248 check_slot_hashes.get(¬_a_slot),
249 );
250 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
251 assert_eq!(
252 pod_slot_hashes.position(¬_a_slot).unwrap(),
253 check_slot_hashes.position(¬_a_slot),
254 );
255 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
256
257 let not_a_slot = num_entries.saturating_add(2) as u64;
258 assert_eq!(
259 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
260 check_slot_hashes.get(¬_a_slot),
261 );
262 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
263 assert_eq!(
264 pod_slot_hashes.position(¬_a_slot).unwrap(),
265 check_slot_hashes.position(¬_a_slot),
266 );
267 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
268 }
269}