solana_sysvar/
slot_hashes.rs1#[cfg(feature = "bytemuck")]
47use bytemuck_derive::{Pod, Zeroable};
48#[cfg(feature = "bincode")]
49#[allow(deprecated)]
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")]
63#[allow(deprecated)]
64impl SysvarSerialize for SlotHashes {
65 fn size_of() -> usize {
67 SIZE
69 }
70 fn from_account_info(
71 _account_info: &AccountInfo,
72 ) -> Result<Self, solana_program_error::ProgramError> {
73 Err(solana_program_error::ProgramError::UnsupportedSysvar)
75 }
76}
77
78#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
80#[derive(Copy, Clone, Default)]
81#[repr(C)]
82pub struct PodSlotHash {
83 pub slot: Slot,
84 pub hash: Hash,
85}
86
87#[cfg(feature = "bytemuck")]
88#[derive(Default)]
93pub struct PodSlotHashes {
94 data: Vec<u8>,
95 slot_hashes_start: usize,
96 slot_hashes_end: usize,
97}
98
99#[cfg(feature = "bytemuck")]
100impl PodSlotHashes {
101 pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
103 let sysvar_len = SIZE;
105 let mut data = vec![0; sysvar_len];
106
107 if data.as_ptr().align_offset(8) != 0 {
109 return Err(solana_program_error::ProgramError::InvalidAccountData);
110 }
111
112 crate::get_sysvar(
115 &mut data,
116 &SlotHashes::id(),
117 0,
118 sysvar_len as u64,
119 )?;
120
121 Self::from_bytes(data)
122 }
123
124 fn from_bytes(data: Vec<u8>) -> Result<Self, solana_program_error::ProgramError> {
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#[cfg(test)]
182mod tests {
183 use {
184 super::*, solana_hash::Hash, solana_sha256_hasher::hash, solana_slot_hashes::MAX_ENTRIES,
185 test_case::test_case,
186 };
187
188 #[test]
189 #[allow(deprecated)]
190 fn test_size_of() {
191 assert_eq!(
192 SlotHashes::size_of(),
193 bincode::serialized_size(
194 &(0..MAX_ENTRIES)
195 .map(|slot| (slot as Slot, Hash::default()))
196 .collect::<SlotHashes>()
197 )
198 .unwrap() as usize
199 );
200 }
201
202 #[test_case(0)]
203 #[test_case(1)]
204 #[test_case(2)]
205 #[test_case(5)]
206 #[test_case(10)]
207 #[test_case(64)]
208 #[test_case(128)]
209 #[test_case(192)]
210 #[test_case(256)]
211 #[test_case(384)]
212 #[test_case(MAX_ENTRIES)]
213 fn test_pod_slot_hashes(num_entries: usize) {
214 let mut slot_hashes = vec![];
215 for i in 0..num_entries {
216 slot_hashes.push((
217 i as u64,
218 hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
219 ));
220 }
221
222 let check_slot_hashes = SlotHashes::new(&slot_hashes);
223 let pod_slot_hashes =
224 PodSlotHashes::from_bytes(bincode::serialize(&check_slot_hashes).unwrap()).unwrap();
225
226 let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
229 assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
230
231 for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
234 assert_eq!(
236 pod_slot_hashes.get(slot).unwrap().as_ref(),
237 check_slot_hashes.get(slot),
238 );
239 assert_eq!(
241 pod_slot_hashes.position(slot).unwrap(),
242 check_slot_hashes.position(slot),
243 );
244 }
245
246 let not_a_slot = num_entries.saturating_add(1) as u64;
248 assert_eq!(
249 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
250 check_slot_hashes.get(¬_a_slot),
251 );
252 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
253 assert_eq!(
254 pod_slot_hashes.position(¬_a_slot).unwrap(),
255 check_slot_hashes.position(¬_a_slot),
256 );
257 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
258
259 let not_a_slot = num_entries.saturating_add(2) as u64;
260 assert_eq!(
261 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
262 check_slot_hashes.get(¬_a_slot),
263 );
264 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
265 assert_eq!(
266 pod_slot_hashes.position(¬_a_slot).unwrap(),
267 check_slot_hashes.position(¬_a_slot),
268 );
269 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
270 }
271}