solana_sysvar/
slot_hashes.rs

1//! The most recent hashes of a slot's parent banks.
2//!
3//! The _slot hashes sysvar_ provides access to the [`SlotHashes`] type.
4//!
5//! The [`Sysvar::from_account_info`] and [`Sysvar::get`] methods always return
6//! [`solana_program_error::ProgramError::UnsupportedSysvar`] because this sysvar account is too large
7//! to process on-chain. Thus this sysvar cannot be accessed on chain, though
8//! one can still use the [`SysvarId::id`], [`SysvarId::check_id`] and
9//! [`Sysvar::size_of`] methods in an on-chain program, and it can be accessed
10//! off-chain through RPC.
11//!
12//! [`SysvarId::id`]: https://docs.rs/solana-sysvar-id/latest/solana_sysvar_id/trait.SysvarId.html#tymethod.id
13//! [`SysvarId::check_id`]: https://docs.rs/solana-sysvar-id/latest/solana_sysvar_id/trait.SysvarId.html#tymethod.check_id
14//!
15//! # Examples
16//!
17//! Calling via the RPC client:
18//!
19//! ```
20//! # use solana_program::example_mocks::solana_sdk;
21//! # use solana_program::example_mocks::solana_rpc_client;
22//! # use solana_sdk::account::Account;
23//! # use solana_rpc_client::rpc_client::RpcClient;
24//! # use solana_sdk_ids::sysvar::slot_hashes;
25//! # use solana_slot_hashes::SlotHashes;
26//! # use anyhow::Result;
27//! #
28//! fn print_sysvar_slot_hashes(client: &RpcClient) -> Result<()> {
29//! #   client.set_get_account_response(slot_hashes::ID, Account {
30//! #       lamports: 1009200,
31//! #       data: vec![1, 0, 0, 0, 0, 0, 0, 0, 86, 190, 235, 7, 0, 0, 0, 0, 133, 242, 94, 158, 223, 253, 207, 184, 227, 194, 235, 27, 176, 98, 73, 3, 175, 201, 224, 111, 21, 65, 73, 27, 137, 73, 229, 19, 255, 192, 193, 126],
32//! #       owner: solana_sdk_ids::system_program::ID,
33//! #       executable: false,
34//! #       rent_epoch: 307,
35//! # });
36//! #
37//!     let slot_hashes = client.get_account(&slot_hashes::ID)?;
38//!     let data: SlotHashes = bincode::deserialize(&slot_hashes.data)?;
39//!
40//!     Ok(())
41//! }
42//! #
43//! # let client = RpcClient::new(String::new());
44//! # print_sysvar_slot_hashes(&client)?;
45//! #
46//! # Ok::<(), anyhow::Error>(())
47//! ```
48
49#[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; // golden, update if MAX_ENTRIES changes
60
61pub 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    // override
70    fn size_of() -> usize {
71        // hard-coded so that we don't have to construct an empty
72        SYSVAR_LEN
73    }
74    fn from_account_info(
75        _account_info: &AccountInfo,
76    ) -> Result<Self, solana_program_error::ProgramError> {
77        // This sysvar is too large to bincode::deserialize in-program
78        Err(solana_program_error::ProgramError::UnsupportedSysvar)
79    }
80}
81
82/// A bytemuck-compatible (plain old data) version of `SlotHash`.
83#[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/// API for querying of the `SlotHashes` sysvar by on-chain programs.
93///
94/// Hangs onto the allocated raw buffer from the account data, which can be
95/// queried or accessed directly as a slice of `PodSlotHash`.
96#[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    /// Fetch all of the raw sysvar data using the `sol_get_sysvar` syscall.
106    pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
107        // Allocate an uninitialized buffer for the raw sysvar data.
108        let sysvar_len = SYSVAR_LEN;
109        let mut data = vec![0; sysvar_len];
110
111        // Ensure the created buffer is aligned to 8.
112        if data.as_ptr().align_offset(8) != 0 {
113            return Err(solana_program_error::ProgramError::InvalidAccountData);
114        }
115
116        // Populate the buffer by fetching all sysvar data using the
117        // `sol_get_sysvar` syscall.
118        crate::get_sysvar(
119            &mut data,
120            &SlotHashes::id(),
121            /* offset */ 0,
122            /* length */ sysvar_len as u64,
123        )?;
124
125        // Get the number of slot hashes present in the data by reading the
126        // `u64` length at the beginning of the data, then use that count to
127        // calculate the length of the slot hashes data.
128        //
129        // The rest of the buffer is uninitialized and should not be accessed.
130        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    /// Return the `SlotHashes` sysvar data as a slice of `PodSlotHash`.
148    /// Returns a slice of only the initialized sysvar data.
149    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    /// Given a slot, get its corresponding hash in the `SlotHashes` sysvar
157    /// data. Returns `None` if the slot is not found.
158    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    /// Given a slot, get its position in the `SlotHashes` sysvar data. Returns
168    /// `None` if the slot is not found.
169    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/// API for querying the `SlotHashes` sysvar.
182#[deprecated(since = "2.1.0", note = "Please use `PodSlotHashes` instead")]
183pub struct SlotHashesSysvar;
184
185#[allow(deprecated)]
186impl SlotHashesSysvar {
187    #[cfg(feature = "bytemuck")]
188    /// Get a value from the sysvar entries by its key.
189    /// Returns `None` if the key is not found.
190    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    /// Get the position of an entry in the sysvar by its key.
201    /// Returns `None` if the key is not found.
202    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        // Ensure the created buffer is aligned to 8.
219        if data.as_ptr().align_offset(8) != 0 {
220            return Err(solana_program_error::ProgramError::InvalidAccountData);
221        }
222
223        let offset = 8; // Vector length as `u64`.
224        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        // The data is always `SlotHashes::size_of()`.
252        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        // Assert the slice of `PodSlotHash` has the same length as
284        // `SlotHashes`.
285        let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
286        assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
287
288        // Assert `PodSlotHashes` and `SlotHashes` contain the same slot hashes
289        // in the same order.
290        for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
291            // `get`:
292            assert_eq!(
293                pod_slot_hashes.get(slot).unwrap().as_ref(),
294                check_slot_hashes.get(slot),
295            );
296            // `position`:
297            assert_eq!(
298                pod_slot_hashes.position(slot).unwrap(),
299                check_slot_hashes.position(slot),
300            );
301        }
302
303        // Check a few `None` values.
304        let not_a_slot = num_entries.saturating_add(1) as u64;
305        assert_eq!(
306            pod_slot_hashes.get(&not_a_slot).unwrap().as_ref(),
307            check_slot_hashes.get(&not_a_slot),
308        );
309        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
310        assert_eq!(
311            pod_slot_hashes.position(&not_a_slot).unwrap(),
312            check_slot_hashes.position(&not_a_slot),
313        );
314        assert_eq!(pod_slot_hashes.position(&not_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(&not_a_slot).unwrap().as_ref(),
319            check_slot_hashes.get(&not_a_slot),
320        );
321        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
322        assert_eq!(
323            pod_slot_hashes.position(&not_a_slot).unwrap(),
324            check_slot_hashes.position(&not_a_slot),
325        );
326        assert_eq!(pod_slot_hashes.position(&not_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        // `get`:
345        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        // `None`.
358        assert_eq!(
359            SlotHashesSysvar::get(&600).unwrap().as_ref(),
360            check_slot_hashes.get(&600),
361        );
362
363        // `position`:
364        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        // `None`.
377        assert_eq!(
378            SlotHashesSysvar::position(&600).unwrap(),
379            check_slot_hashes.position(&600),
380        );
381    }
382}