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