solana_slot_history/
lib.rs1#![allow(clippy::arithmetic_side_effects)]
10#![cfg_attr(docsrs, feature(doc_auto_cfg))]
11
12#[cfg(feature = "sysvar")]
13pub mod sysvar;
14
15use bv::{BitVec, BitsMut};
16
17#[repr(C)]
19#[cfg_attr(
20 feature = "serde",
21 derive(serde_derive::Deserialize, serde_derive::Serialize)
22)]
23#[derive(Clone, PartialEq, Eq)]
24pub struct SlotHistory {
25 pub bits: BitVec<u64>,
26 pub next_slot: u64,
27}
28
29impl Default for SlotHistory {
30 fn default() -> Self {
31 let mut bits = BitVec::new_fill(false, MAX_ENTRIES);
32 bits.set(0, true);
33 Self { bits, next_slot: 1 }
34 }
35}
36
37impl std::fmt::Debug for SlotHistory {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "SlotHistory {{ slot: {} bits:", self.next_slot)?;
40 for i in 0..MAX_ENTRIES {
41 if self.bits.get(i) {
42 write!(f, "1")?;
43 } else {
44 write!(f, "0")?;
45 }
46 }
47 Ok(())
48 }
49}
50
51pub const MAX_ENTRIES: u64 = 1024 * 1024; #[derive(PartialEq, Eq, Debug)]
54pub enum Check {
55 Future,
56 TooOld,
57 Found,
58 NotFound,
59}
60
61impl SlotHistory {
62 pub fn add(&mut self, slot: u64) {
63 if slot > self.next_slot && slot - self.next_slot >= MAX_ENTRIES {
64 let full_blocks = (MAX_ENTRIES as usize) / 64;
67 for i in 0..full_blocks {
68 self.bits.set_block(i, 0);
69 }
70 } else {
71 for skipped in self.next_slot..slot {
72 self.bits.set(skipped % MAX_ENTRIES, false);
73 }
74 }
75 self.bits.set(slot % MAX_ENTRIES, true);
76 self.next_slot = slot + 1;
77 }
78
79 pub fn check(&self, slot: u64) -> Check {
80 if slot > self.newest() {
81 Check::Future
82 } else if slot < self.oldest() {
83 Check::TooOld
84 } else if self.bits.get(slot % MAX_ENTRIES) {
85 Check::Found
86 } else {
87 Check::NotFound
88 }
89 }
90
91 pub fn oldest(&self) -> u64 {
92 self.next_slot.saturating_sub(MAX_ENTRIES)
93 }
94
95 pub fn newest(&self) -> u64 {
96 self.next_slot - 1
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn slot_history_test1() {
106 assert_eq!(MAX_ENTRIES % 64, 0);
108 let mut slot_history = SlotHistory::default();
109 slot_history.add(2);
110 assert_eq!(slot_history.check(0), Check::Found);
111 assert_eq!(slot_history.check(1), Check::NotFound);
112 for i in 3..MAX_ENTRIES {
113 assert_eq!(slot_history.check(i), Check::Future);
114 }
115 slot_history.add(20);
116 slot_history.add(MAX_ENTRIES);
117 assert_eq!(slot_history.check(0), Check::TooOld);
118 assert_eq!(slot_history.check(1), Check::NotFound);
119 for i in &[2, 20, MAX_ENTRIES] {
120 assert_eq!(slot_history.check(*i), Check::Found);
121 }
122 for i in 3..20 {
123 assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}");
124 }
125 for i in 21..MAX_ENTRIES {
126 assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}");
127 }
128 assert_eq!(slot_history.check(MAX_ENTRIES + 1), Check::Future);
129
130 let slot = 3 * MAX_ENTRIES + 3;
131 slot_history.add(slot);
132 for i in &[0, 1, 2, 20, 21, MAX_ENTRIES] {
133 assert_eq!(slot_history.check(*i), Check::TooOld);
134 }
135 let start = slot - MAX_ENTRIES + 1;
136 let end = slot;
137 for i in start..end {
138 assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}");
139 }
140 assert_eq!(slot_history.check(slot), Check::Found);
141 }
142
143 #[test]
144 fn slot_history_test_wrap() {
145 let mut slot_history = SlotHistory::default();
146 slot_history.add(2);
147 assert_eq!(slot_history.check(0), Check::Found);
148 assert_eq!(slot_history.check(1), Check::NotFound);
149 for i in 3..MAX_ENTRIES {
150 assert_eq!(slot_history.check(i), Check::Future);
151 }
152 slot_history.add(20);
153 slot_history.add(MAX_ENTRIES + 19);
154 for i in 0..19 {
155 assert_eq!(slot_history.check(i), Check::TooOld);
156 }
157 assert_eq!(slot_history.check(MAX_ENTRIES), Check::NotFound);
158 assert_eq!(slot_history.check(20), Check::Found);
159 assert_eq!(slot_history.check(MAX_ENTRIES + 19), Check::Found);
160 assert_eq!(slot_history.check(20), Check::Found);
161 for i in 21..MAX_ENTRIES + 19 {
162 assert_eq!(slot_history.check(i), Check::NotFound, "found: {i}");
163 }
164 assert_eq!(slot_history.check(MAX_ENTRIES + 20), Check::Future);
165 }
166
167 #[test]
168 fn slot_history_test_same_index() {
169 let mut slot_history = SlotHistory::default();
170 slot_history.add(3);
171 slot_history.add(4);
172 assert_eq!(slot_history.check(1), Check::NotFound);
173 assert_eq!(slot_history.check(2), Check::NotFound);
174 assert_eq!(slot_history.check(3), Check::Found);
175 assert_eq!(slot_history.check(4), Check::Found);
176 slot_history.add(MAX_ENTRIES + 5);
177 assert_eq!(slot_history.check(5), Check::TooOld);
178 for i in 6..MAX_ENTRIES + 5 {
179 assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}");
180 }
181 assert_eq!(slot_history.check(MAX_ENTRIES + 5), Check::Found);
182 }
183
184 #[test]
185 fn test_older_slot() {
186 let mut slot_history = SlotHistory::default();
187 slot_history.add(10);
188 slot_history.add(5);
189 assert_eq!(slot_history.check(0), Check::Found);
190 assert_eq!(slot_history.check(5), Check::Found);
191 assert_eq!(slot_history.check(10), Check::Future);
193 assert_eq!(slot_history.check(6), Check::Future);
194 assert_eq!(slot_history.check(11), Check::Future);
195 }
196
197 #[test]
198 fn test_oldest() {
199 let mut slot_history = SlotHistory::default();
200 assert_eq!(slot_history.oldest(), 0);
201 slot_history.add(10);
202 assert_eq!(slot_history.oldest(), 0);
203 slot_history.add(MAX_ENTRIES - 1);
204 assert_eq!(slot_history.oldest(), 0);
205 slot_history.add(MAX_ENTRIES);
206 assert_eq!(slot_history.oldest(), 1);
207 }
208}