Skip to main content

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 [`crate::Sysvar::get`] method always returns
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 [`SIZE`]
9//! in an on-chain program, and it can be accessed off-chain through RPC.
10//!
11//! [`SysvarId::id`]: https://docs.rs/solana-sysvar-id/latest/solana_sysvar_id/trait.SysvarId.html#tymethod.id
12//! [`SysvarId::check_id`]: https://docs.rs/solana-sysvar-id/latest/solana_sysvar_id/trait.SysvarId.html#tymethod.check_id
13//!
14//! # Examples
15//!
16//! Calling via the RPC client:
17//!
18//! ```
19//! # use solana_example_mocks::solana_account;
20//! # use solana_example_mocks::solana_rpc_client;
21//! # use solana_account::Account;
22//! # use solana_rpc_client::rpc_client::RpcClient;
23//! # use solana_sdk_ids::sysvar::slot_hashes;
24//! # use solana_slot_hashes::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_ids::system_program::ID,
32//! #       executable: false,
33//! # });
34//! #
35//!     let slot_hashes = client.get_account(&slot_hashes::ID)?;
36//!     let data: SlotHashes = bincode::deserialize(&slot_hashes.data)?;
37//!
38//!     Ok(())
39//! }
40//! #
41//! # let client = RpcClient::new(String::new());
42//! # print_sysvar_slot_hashes(&client)?;
43//! #
44//! # Ok::<(), anyhow::Error>(())
45//! ```
46#[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    // override
66    fn size_of() -> usize {
67        // hard-coded so that we don't have to construct an empty
68        SIZE
69    }
70    fn from_account_info(
71        _account_info: &AccountInfo,
72    ) -> Result<Self, solana_program_error::ProgramError> {
73        // This sysvar is too large to bincode::deserialize in-program
74        Err(solana_program_error::ProgramError::UnsupportedSysvar)
75    }
76}
77
78/// A bytemuck-compatible (plain old data) version of `SlotHash`.
79#[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/// API for querying of the `SlotHashes` sysvar by on-chain programs.
89///
90/// Hangs onto the allocated raw buffer from the account data, which can be
91/// queried or accessed directly as a slice of `PodSlotHash`.
92#[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    /// Fetch all of the raw sysvar data using the `sol_get_sysvar` syscall.
102    pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
103        // Allocate an uninitialized buffer for the raw sysvar data.
104        let sysvar_len = SIZE;
105        let mut data = vec![0; sysvar_len];
106
107        // Ensure the created buffer is aligned to 8.
108        if data.as_ptr().align_offset(8) != 0 {
109            return Err(solana_program_error::ProgramError::InvalidAccountData);
110        }
111
112        // Populate the buffer by fetching all sysvar data using the
113        // `sol_get_sysvar` syscall.
114        crate::get_sysvar(
115            &mut data,
116            &SlotHashes::id(),
117            /* offset */ 0,
118            /* length */ 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        // 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#[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        // Assert the slice of `PodSlotHash` has the same length as
227        // `SlotHashes`.
228        let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
229        assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
230
231        // Assert `PodSlotHashes` and `SlotHashes` contain the same slot hashes
232        // in the same order.
233        for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
234            // `get`:
235            assert_eq!(
236                pod_slot_hashes.get(slot).unwrap().as_ref(),
237                check_slot_hashes.get(slot),
238            );
239            // `position`:
240            assert_eq!(
241                pod_slot_hashes.position(slot).unwrap(),
242                check_slot_hashes.position(slot),
243            );
244        }
245
246        // Check a few `None` values.
247        let not_a_slot = num_entries.saturating_add(1) as u64;
248        assert_eq!(
249            pod_slot_hashes.get(&not_a_slot).unwrap().as_ref(),
250            check_slot_hashes.get(&not_a_slot),
251        );
252        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
253        assert_eq!(
254            pod_slot_hashes.position(&not_a_slot).unwrap(),
255            check_slot_hashes.position(&not_a_slot),
256        );
257        assert_eq!(pod_slot_hashes.position(&not_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(&not_a_slot).unwrap().as_ref(),
262            check_slot_hashes.get(&not_a_slot),
263        );
264        assert_eq!(pod_slot_hashes.get(&not_a_slot).unwrap(), None);
265        assert_eq!(
266            pod_slot_hashes.position(&not_a_slot).unwrap(),
267            check_slot_hashes.position(&not_a_slot),
268        );
269        assert_eq!(pod_slot_hashes.position(&not_a_slot).unwrap(), None);
270    }
271}