1#[allow(deprecated)]
2use solana_sysvar::recent_blockhashes;
3use {
4 serde::{Deserialize, Serialize},
5 solana_clock::MAX_RECENT_BLOCKHASHES,
6 solana_fee_calculator::FeeCalculator,
7 solana_hash::Hash,
8 solana_time_utils::timestamp,
9 std::collections::HashMap,
10};
11
12#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
13#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
14pub struct HashInfo {
15 fee_calculator: FeeCalculator,
16 hash_index: u64,
17 timestamp: u64,
18}
19
20impl HashInfo {
21 pub fn lamports_per_signature(&self) -> u64 {
22 self.fee_calculator.lamports_per_signature
23 }
24}
25
26#[cfg_attr(
28 feature = "frozen-abi",
29 derive(AbiExample),
30 frozen_abi(digest = "DZVVXt4saSgH1CWGrzBcX2sq5yswCuRqGx1Y1ZehtWT6")
31)]
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33pub struct BlockhashQueue {
34 last_hash_index: u64,
36
37 last_hash: Option<Hash>,
39
40 hashes: HashMap<Hash, HashInfo, ahash::RandomState>,
41
42 max_age: usize,
44}
45
46impl Default for BlockhashQueue {
47 fn default() -> Self {
48 Self::new(MAX_RECENT_BLOCKHASHES)
49 }
50}
51
52impl BlockhashQueue {
53 pub fn new(max_age: usize) -> Self {
54 Self {
55 hashes: HashMap::default(),
56 last_hash_index: 0,
57 last_hash: None,
58 max_age,
59 }
60 }
61
62 pub fn last_hash(&self) -> Hash {
63 self.last_hash.expect("no hash has been set")
64 }
65
66 pub fn get_lamports_per_signature(&self, hash: &Hash) -> Option<u64> {
67 self.hashes
68 .get(hash)
69 .map(|hash_age| hash_age.fee_calculator.lamports_per_signature)
70 }
71
72 pub fn is_hash_valid_for_age(&self, hash: &Hash, max_age: usize) -> bool {
74 self.get_hash_info_if_valid(hash, max_age).is_some()
75 }
76
77 pub fn get_hash_info_if_valid(&self, hash: &Hash, max_age: usize) -> Option<&HashInfo> {
80 self.hashes.get(hash).filter(|info| {
81 Self::is_hash_index_valid(self.last_hash_index, max_age, info.hash_index)
82 })
83 }
84
85 pub fn get_hash_age(&self, hash: &Hash) -> Option<u64> {
86 self.hashes
87 .get(hash)
88 .map(|info| self.last_hash_index - info.hash_index)
89 }
90
91 pub fn genesis_hash(&mut self, hash: &Hash, lamports_per_signature: u64) {
92 self.hashes.insert(
93 *hash,
94 HashInfo {
95 fee_calculator: FeeCalculator::new(lamports_per_signature),
96 hash_index: 0,
97 timestamp: timestamp(),
98 },
99 );
100
101 self.last_hash = Some(*hash);
102 }
103
104 fn is_hash_index_valid(last_hash_index: u64, max_age: usize, hash_index: u64) -> bool {
105 last_hash_index - hash_index <= max_age as u64
106 }
107
108 pub fn register_hash(&mut self, hash: &Hash, lamports_per_signature: u64) {
109 self.last_hash_index += 1;
110 if self.hashes.len() >= self.max_age {
111 self.hashes.retain(|_, info| {
112 Self::is_hash_index_valid(self.last_hash_index, self.max_age, info.hash_index)
113 });
114 }
115
116 self.hashes.insert(
117 *hash,
118 HashInfo {
119 fee_calculator: FeeCalculator::new(lamports_per_signature),
120 hash_index: self.last_hash_index,
121 timestamp: timestamp(),
122 },
123 );
124
125 self.last_hash = Some(*hash);
126 }
127
128 #[deprecated(
129 since = "1.9.0",
130 note = "Please do not use, will no longer be available in the future"
131 )]
132 #[allow(deprecated)]
133 pub fn get_recent_blockhashes(&self) -> impl Iterator<Item = recent_blockhashes::IterItem<'_>> {
134 (self.hashes).iter().map(|(k, v)| {
135 recent_blockhashes::IterItem(v.hash_index, k, v.fee_calculator.lamports_per_signature)
136 })
137 }
138
139 #[deprecated(
140 since = "2.0.0",
141 note = "Please use `solana_clock::MAX_PROCESSING_AGE`"
142 )]
143 pub fn get_max_age(&self) -> usize {
144 #[allow(deprecated)]
145 self.max_age
146 }
147}
148#[cfg(test)]
149mod tests {
150 #[allow(deprecated)]
151 use solana_sysvar::recent_blockhashes::IterItem;
152 use {
153 super::*, bincode::serialize, solana_clock::MAX_RECENT_BLOCKHASHES,
154 solana_sha256_hasher::hash,
155 };
156
157 #[test]
158 fn test_register_hash() {
159 let last_hash = Hash::default();
160 let max_age = 100;
161 let mut hash_queue = BlockhashQueue::new(max_age);
162 assert!(!hash_queue.is_hash_valid_for_age(&last_hash, max_age));
163 hash_queue.register_hash(&last_hash, 0);
164 assert!(hash_queue.is_hash_valid_for_age(&last_hash, max_age));
165 assert_eq!(hash_queue.last_hash_index, 1);
166 }
167
168 #[test]
169 fn test_reject_old_last_hash() {
170 let max_age = 100;
171 let mut hash_queue = BlockhashQueue::new(max_age);
172 let last_hash = hash(&serialize(&0).unwrap());
173 for i in 0..102 {
174 let last_hash = hash(&serialize(&i).unwrap());
175 hash_queue.register_hash(&last_hash, 0);
176 }
177 assert!(!hash_queue.is_hash_valid_for_age(&last_hash, max_age));
179 assert!(!hash_queue.is_hash_valid_for_age(&last_hash, 0));
180
181 let last_valid_hash = hash(&serialize(&1).unwrap());
183 assert!(hash_queue.is_hash_valid_for_age(&last_valid_hash, max_age));
184 assert!(!hash_queue.is_hash_valid_for_age(&last_valid_hash, 0));
185 }
186
187 #[test]
189 fn test_queue_init_blockhash() {
190 let last_hash = Hash::default();
191 let mut hash_queue = BlockhashQueue::new(100);
192 hash_queue.register_hash(&last_hash, 0);
193 assert_eq!(last_hash, hash_queue.last_hash());
194 assert!(hash_queue.is_hash_valid_for_age(&last_hash, 0));
195 }
196
197 #[test]
198 fn test_get_recent_blockhashes() {
199 let mut blockhash_queue = BlockhashQueue::new(MAX_RECENT_BLOCKHASHES);
200 #[allow(deprecated)]
201 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
202 assert_eq!(recent_blockhashes.count(), 0);
204 for i in 0..MAX_RECENT_BLOCKHASHES {
205 let hash = hash(&serialize(&i).unwrap());
206 blockhash_queue.register_hash(&hash, 0);
207 }
208 #[allow(deprecated)]
209 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
210 #[allow(deprecated)]
212 for IterItem(_slot, hash, _lamports_per_signature) in recent_blockhashes {
213 assert!(blockhash_queue.is_hash_valid_for_age(hash, MAX_RECENT_BLOCKHASHES));
214 }
215 }
216
217 #[test]
218 fn test_len() {
219 const MAX_AGE: usize = 10;
220 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
221 assert_eq!(hash_queue.hashes.len(), 0);
222
223 for _ in 0..MAX_AGE {
224 hash_queue.register_hash(&Hash::new_unique(), 0);
225 }
226 assert_eq!(hash_queue.hashes.len(), MAX_AGE);
227
228 hash_queue.register_hash(&Hash::new_unique(), 0);
232 assert_eq!(hash_queue.hashes.len(), MAX_AGE + 1);
233
234 hash_queue.register_hash(&Hash::new_unique(), 0);
236 assert_eq!(hash_queue.hashes.len(), MAX_AGE + 1);
237 }
238
239 #[test]
240 fn test_get_hash_age() {
241 const MAX_AGE: usize = 10;
242 let mut hash_list: Vec<Hash> = Vec::new();
243 hash_list.resize_with(MAX_AGE + 1, Hash::new_unique);
244
245 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
246 for hash in &hash_list {
247 assert!(hash_queue.get_hash_age(hash).is_none());
248 }
249
250 for hash in &hash_list {
251 hash_queue.register_hash(hash, 0);
252 }
253
254 for (age, hash) in hash_list.iter().rev().enumerate() {
258 assert_eq!(hash_queue.get_hash_age(hash), Some(age as u64));
259 }
260
261 hash_queue.register_hash(&Hash::new_unique(), 0);
263 assert!(hash_queue.get_hash_age(&hash_list[0]).is_none());
264 }
265
266 #[test]
267 fn test_is_hash_valid_for_age() {
268 const MAX_AGE: usize = 10;
269 let mut hash_list: Vec<Hash> = Vec::new();
270 hash_list.resize_with(MAX_AGE + 1, Hash::new_unique);
271
272 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
273 for hash in &hash_list {
274 assert!(!hash_queue.is_hash_valid_for_age(hash, MAX_AGE));
275 }
276
277 for hash in &hash_list {
278 hash_queue.register_hash(hash, 0);
279 }
280
281 for hash in &hash_list {
285 assert!(hash_queue.is_hash_valid_for_age(hash, MAX_AGE));
286 }
287
288 assert!(hash_queue.is_hash_valid_for_age(&hash_list[MAX_AGE], 0));
290 assert!(!hash_queue.is_hash_valid_for_age(&hash_list[MAX_AGE - 1], 0));
291 }
292
293 #[test]
294 fn test_get_hash_info_if_valid() {
295 const MAX_AGE: usize = 10;
296 let mut hash_list: Vec<Hash> = Vec::new();
297 hash_list.resize_with(MAX_AGE + 1, Hash::new_unique);
298
299 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
300 for hash in &hash_list {
301 assert!(hash_queue.get_hash_info_if_valid(hash, MAX_AGE).is_none());
302 }
303
304 for hash in &hash_list {
305 hash_queue.register_hash(hash, 0);
306 }
307
308 for hash in &hash_list {
312 assert_eq!(
313 hash_queue.get_hash_info_if_valid(hash, MAX_AGE),
314 Some(hash_queue.hashes.get(hash).unwrap())
315 );
316 }
317
318 let most_recent_hash = &hash_list[MAX_AGE];
320 assert_eq!(
321 hash_queue.get_hash_info_if_valid(most_recent_hash, 0),
322 Some(hash_queue.hashes.get(most_recent_hash).unwrap())
323 );
324 assert!(hash_queue
325 .get_hash_info_if_valid(&hash_list[MAX_AGE - 1], 0)
326 .is_none());
327 }
328}