solana_program/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//! [`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::sysvar::slot_hashes::{self, SlotHashes};
25//! # use anyhow::Result;
26//! #
27//! fn print_sysvar_slot_hashes(client: &RpcClient) -> Result<()> {
28//! #   client.set_get_account_response(slot_hashes::ID, Account {
29//! #       lamports: 1009200,
30//! #       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],
31//! #       owner: solana_sdk::system_program::ID,
32//! #       executable: false,
33//! #       rent_epoch: 307,
34//! # });
35//! #
36//!     let slot_hashes = client.get_account(&slot_hashes::ID)?;
37//!     let data: SlotHashes = bincode::deserialize(&slot_hashes.data)?;
38//!
39//!     Ok(())
40//! }
41//! #
42//! # let client = RpcClient::new(String::new());
43//! # print_sysvar_slot_hashes(&client)?;
44//! #
45//! # Ok::<(), anyhow::Error>(())
46//! ```
47
48use {
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    // override
72    fn size_of() -> usize {
73        // hard-coded so that we don't have to construct an empty
74        20_488 // golden, update if MAX_ENTRIES changes
75    }
76    fn from_account_info(_account_info: &AccountInfo) -> Result<Self, ProgramError> {
77        // This sysvar is too large to bincode::deserialize in-program
78        Err(ProgramError::UnsupportedSysvar)
79    }
80}
81
82/// A bytemuck-compatible (plain old data) version of `SlotHash`.
83#[derive(Copy, Clone, Default, Pod, Zeroable)]
84#[repr(C)]
85pub struct PodSlotHash {
86    pub slot: Slot,
87    pub hash: Hash,
88}
89
90/// API for querying of the `SlotHashes` sysvar by on-chain programs.
91///
92/// Hangs onto the allocated raw buffer from the account data, which can be
93/// queried or accessed directly as a slice of `PodSlotHash`.
94#[derive(Default)]
95pub struct PodSlotHashes {
96    data: Vec<u8>,
97    slot_hashes_start: usize,
98    slot_hashes_end: usize,
99}
100
101impl PodSlotHashes {
102    /// Fetch all of the raw sysvar data using the `sol_get_sysvar` syscall.
103    pub fn fetch() -> Result<Self, ProgramError> {
104        // Allocate an uninitialized buffer for the raw sysvar data.
105        let sysvar_len = SlotHashes::size_of();
106        let mut data = vec![0; sysvar_len];
107
108        // Ensure the created buffer is aligned to 8.
109        if data.as_ptr().align_offset(8) != 0 {
110            return Err(ProgramError::InvalidAccountData);
111        }
112
113        // Populate the buffer by fetching all sysvar data using the
114        // `sol_get_sysvar` syscall.
115        get_sysvar(
116            &mut data,
117            &SlotHashes::id(),
118            /* offset */ 0,
119            /* length */ sysvar_len as u64,
120        )?;
121
122        // Get the number of slot hashes present in the data by reading the
123        // `u64` length at the beginning of the data, then use that count to
124        // calculate the length of the slot hashes data.
125        //
126        // The rest of the buffer is uninitialized and should not be accessed.
127        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    /// Return the `SlotHashes` sysvar data as a slice of `PodSlotHash`.
145    /// Returns a slice of only the initialized sysvar data.
146    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    /// Given a slot, get its corresponding hash in the `SlotHashes` sysvar
154    /// data. Returns `None` if the slot is not found.
155    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    /// Given a slot, get its position in the `SlotHashes` sysvar data. Returns
165    /// `None` if the slot is not found.
166    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/// API for querying the `SlotHashes` sysvar.
176#[deprecated(since = "2.1.0", note = "Please use `PodSlotHashes` instead")]
177pub struct SlotHashesSysvar;
178
179#[allow(deprecated)]
180impl SlotHashesSysvar {
181    /// Get a value from the sysvar entries by its key.
182    /// Returns `None` if the key is not found.
183    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    /// Get the position of an entry in the sysvar by its key.
193    /// Returns `None` if the key is not found.
194    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        // Ensure the created buffer is aligned to 8.
210        if data.as_ptr().align_offset(8) != 0 {
211            return Err(ProgramError::InvalidAccountData);
212        }
213
214        let offset = 8; // Vector length as `u64`.
215        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        // The data is always `SlotHashes::size_of()`.
249        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        // Assert the slice of `PodSlotHash` has the same length as
281        // `SlotHashes`.
282        let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
283        assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
284
285        // Assert `PodSlotHashes` and `SlotHashes` contain the same slot hashes
286        // in the same order.
287        for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
288            // `get`:
289            assert_eq!(
290                pod_slot_hashes.get(slot).unwrap().as_ref(),
291                check_slot_hashes.get(slot),
292            );
293            // `position`:
294            assert_eq!(
295                pod_slot_hashes.position(slot).unwrap(),
296                check_slot_hashes.position(slot),
297            );
298        }
299
300        // Check a few `None` values.
301        let not_a_slot = num_entries.saturating_add(1) as u64;
302        assert_eq!(
303            pod_slot_hashes.get(&not_a_slot).unwrap().as_ref(),
304            check_slot_hashes.get(&not_a_slot),
305        );
306        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
307        assert_eq!(
308            pod_slot_hashes.position(&not_a_slot).unwrap(),
309            check_slot_hashes.position(&not_a_slot),
310        );
311        assert_eq!(pod_slot_hashes.position(&not_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(&not_a_slot).unwrap().as_ref(),
316            check_slot_hashes.get(&not_a_slot),
317        );
318        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
319        assert_eq!(
320            pod_slot_hashes.position(&not_a_slot).unwrap(),
321            check_slot_hashes.position(&not_a_slot),
322        );
323        assert_eq!(pod_slot_hashes.position(&not_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        // `get`:
342        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        // `None`.
355        assert_eq!(
356            SlotHashesSysvar::get(&600).unwrap().as_ref(),
357            check_slot_hashes.get(&600),
358        );
359
360        // `position`:
361        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        // `None`.
374        assert_eq!(
375            SlotHashesSysvar::position(&600).unwrap(),
376            check_slot_hashes.position(&600),
377        );
378    }
379}