Skip to main content

solana_slot_hashes/
lib.rs

1//! A type to hold data for the [`SlotHashes` sysvar][sv].
2//!
3//! [sv]: https://docs.solanalabs.com/runtime/sysvars#slothashes
4//!
5//! The sysvar ID is declared in [`solana_program::sysvar::slot_hashes`].
6//!
7//! [`solana_program::sysvar::slot_hashes`]: https://docs.rs/solana-program/latest/solana_program/sysvar/slot_hashes/index.html
8#![cfg_attr(docsrs, feature(doc_cfg))]
9
10#[cfg(feature = "sysvar")]
11pub mod sysvar;
12
13use {
14    solana_hash::Hash,
15    std::{
16        iter::FromIterator,
17        ops::Deref,
18        sync::atomic::{AtomicUsize, Ordering},
19    },
20};
21
22pub const MAX_ENTRIES: usize = 512; // about 2.5 minutes to get your vote in
23
24// This is to allow tests with custom slot hash expiry to avoid having to generate
25// 512 blocks for such tests.
26static NUM_ENTRIES: AtomicUsize = AtomicUsize::new(MAX_ENTRIES);
27
28pub fn get_entries() -> usize {
29    NUM_ENTRIES.load(Ordering::Relaxed)
30}
31
32pub fn set_entries_for_tests_only(entries: usize) {
33    NUM_ENTRIES.store(entries, Ordering::Relaxed);
34}
35
36const LEN_PREFIX: usize = size_of::<u64>();
37const SLOT_HASH_SERIALIZED_SIZE: usize = size_of::<u64>() + size_of::<Hash>();
38
39/// Serialized size of the `SlotHashes` sysvar account.
40pub const SIZE: usize = LEN_PREFIX + MAX_ENTRIES * SLOT_HASH_SERIALIZED_SIZE;
41const _: () = assert!(SIZE == 20_488);
42
43/// A single entry of the [`SlotHashes`] sysvar.
44///
45/// `#[repr(C)]` and padding-free so wincode decodes the whole sysvar in one copy.
46#[repr(C)]
47#[cfg_attr(
48    feature = "serde",
49    derive(serde_derive::Deserialize, serde_derive::Serialize)
50)]
51#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
52// Big-endian targets encode integers byte-swapped, so they never qualify as zero-copy.
53#[cfg_attr(
54    all(feature = "wincode", target_endian = "little"),
55    wincode(assert_zero_copy)
56)]
57#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
58pub struct SlotHash {
59    pub slot: u64,
60    pub hash: Hash,
61}
62
63// Unlike `assert_zero_copy`, this also holds on big-endian targets.
64const _: () = assert!(size_of::<SlotHash>() == SLOT_HASH_SERIALIZED_SIZE);
65
66impl SlotHash {
67    pub const fn new(slot: u64, hash: Hash) -> Self {
68        Self { slot, hash }
69    }
70}
71
72impl From<(u64, Hash)> for SlotHash {
73    fn from((slot, hash): (u64, Hash)) -> Self {
74        Self { slot, hash }
75    }
76}
77
78impl From<SlotHash> for (u64, Hash) {
79    fn from(SlotHash { slot, hash }: SlotHash) -> Self {
80        (slot, hash)
81    }
82}
83
84#[repr(C)]
85#[cfg_attr(
86    feature = "serde",
87    derive(serde_derive::Deserialize, serde_derive::Serialize)
88)]
89#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
90#[derive(PartialEq, Eq, Debug, Default)]
91pub struct SlotHashes(Vec<SlotHash>);
92
93impl SlotHashes {
94    pub fn add(&mut self, slot: u64, hash: Hash) {
95        let entry = SlotHash { slot, hash };
96        match self.binary_search_by(|probe| slot.cmp(&probe.slot)) {
97            Ok(index) => (self.0)[index] = entry,
98            Err(index) => (self.0).insert(index, entry),
99        }
100        (self.0).truncate(get_entries());
101    }
102    pub fn position(&self, slot: &u64) -> Option<usize> {
103        self.binary_search_by(|probe| slot.cmp(&probe.slot)).ok()
104    }
105    #[allow(clippy::trivially_copy_pass_by_ref)]
106    pub fn get(&self, slot: &u64) -> Option<&Hash> {
107        self.binary_search_by(|probe| slot.cmp(&probe.slot))
108            .ok()
109            .map(|index| &self[index].hash)
110    }
111    pub fn new(slot_hashes: &[SlotHash]) -> Self {
112        let mut slot_hashes = slot_hashes.to_vec();
113        slot_hashes.sort_by_key(|entry| std::cmp::Reverse(entry.slot));
114        Self(slot_hashes)
115    }
116    pub fn slot_hashes(&self) -> &[SlotHash] {
117        &self.0
118    }
119}
120
121impl FromIterator<SlotHash> for SlotHashes {
122    fn from_iter<I: IntoIterator<Item = SlotHash>>(iter: I) -> Self {
123        Self(iter.into_iter().collect())
124    }
125}
126
127impl FromIterator<(u64, Hash)> for SlotHashes {
128    fn from_iter<I: IntoIterator<Item = (u64, Hash)>>(iter: I) -> Self {
129        Self(iter.into_iter().map(SlotHash::from).collect())
130    }
131}
132
133impl Deref for SlotHashes {
134    type Target = Vec<SlotHash>;
135    fn deref(&self) -> &Self::Target {
136        &self.0
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use {super::*, solana_sha256_hasher::hash};
143
144    fn entry(slot: u64) -> SlotHash {
145        SlotHash::new(slot, hash(&slot.to_le_bytes()))
146    }
147
148    #[test]
149    fn test_size_of() {
150        let slot_hashes = SlotHashes(vec![SlotHash::default(); MAX_ENTRIES]);
151        assert_eq!(
152            wincode::serialized_size(&slot_hashes).unwrap() as usize,
153            SIZE,
154        );
155    }
156
157    #[test]
158    fn test() {
159        let mut slot_hashes = SlotHashes::new(&[entry(1), entry(3)]);
160        slot_hashes.add(2, hash(&2u64.to_le_bytes()));
161        assert_eq!(slot_hashes, SlotHashes(vec![entry(3), entry(2), entry(1)]));
162
163        let mut slot_hashes = SlotHashes::new(&[]);
164        for i in 0..MAX_ENTRIES + 1 {
165            slot_hashes.add(
166                i as u64,
167                hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
168            );
169        }
170        for i in 0..MAX_ENTRIES {
171            assert_eq!(slot_hashes[i].slot, (MAX_ENTRIES - i) as u64);
172        }
173
174        assert_eq!(slot_hashes.len(), MAX_ENTRIES);
175    }
176
177    /// Deployed accounts fix the layout to a length prefix followed by packed
178    /// slot/hash pairs. Checked against the equivalent tuple encoding.
179    #[test]
180    fn test_wire_compat() {
181        let entries: Vec<SlotHash> = (0..MAX_ENTRIES as u64).rev().map(entry).collect();
182        let tuples: Vec<(u64, Hash)> = entries.iter().cloned().map(Into::into).collect();
183        let slot_hashes = SlotHashes::new(&entries);
184
185        let expected = wincode::serialize(&tuples).unwrap();
186        assert_eq!(expected.len(), SIZE);
187        assert_eq!(wincode::serialize(&slot_hashes).unwrap(), expected);
188        assert_eq!(
189            wincode::deserialize::<SlotHashes>(&expected).unwrap(),
190            slot_hashes
191        );
192    }
193}