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#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiExample)]
12struct HashAge {
13 fee_calculator: FeeCalculator,
14 hash_index: u64,
15 timestamp: u64,
16}
17
18#[frozen_abi(digest = "J66ssCYGtWdQu5oyJxFKFeZY86nUjThBdBeXQYuRPDvE")]
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AbiExample)]
21pub struct BlockhashQueue {
22 last_hash_index: u64,
24
25 last_hash: Option<Hash>,
27
28 ages: HashMap<Hash, HashAge>,
29
30 max_age: usize,
32}
33
34impl Default for BlockhashQueue {
35 fn default() -> Self {
36 Self::new(MAX_RECENT_BLOCKHASHES)
37 }
38}
39
40impl BlockhashQueue {
41 pub fn new(max_age: usize) -> Self {
42 Self {
43 ages: HashMap::new(),
44 last_hash_index: 0,
45 last_hash: None,
46 max_age,
47 }
48 }
49
50 pub fn last_hash(&self) -> Hash {
51 self.last_hash.expect("no hash has been set")
52 }
53
54 pub fn get_lamports_per_signature(&self, hash: &Hash) -> Option<u64> {
55 self.ages
56 .get(hash)
57 .map(|hash_age| hash_age.fee_calculator.lamports_per_signature)
58 }
59
60 pub fn is_hash_valid(&self, hash: &Hash) -> bool {
62 self.ages.get(hash).is_some()
63 }
64
65 pub fn is_hash_valid_for_age(&self, hash: &Hash, max_age: usize) -> bool {
67 self.ages
68 .get(hash)
69 .map(|age| Self::is_hash_index_valid(self.last_hash_index, max_age, age.hash_index))
70 .unwrap_or(false)
71 }
72
73 pub fn get_hash_age(&self, hash: &Hash) -> Option<u64> {
74 self.ages
75 .get(hash)
76 .map(|age| self.last_hash_index - age.hash_index)
77 }
78
79 pub fn genesis_hash(&mut self, hash: &Hash, lamports_per_signature: u64) {
80 self.ages.insert(
81 *hash,
82 HashAge {
83 fee_calculator: FeeCalculator::new(lamports_per_signature),
84 hash_index: 0,
85 timestamp: timestamp(),
86 },
87 );
88
89 self.last_hash = Some(*hash);
90 }
91
92 fn is_hash_index_valid(last_hash_index: u64, max_age: usize, hash_index: u64) -> bool {
93 last_hash_index - hash_index <= max_age as u64
94 }
95
96 pub fn register_hash(&mut self, hash: &Hash, lamports_per_signature: u64) {
97 self.last_hash_index += 1;
98 if self.ages.len() >= self.max_age {
99 self.ages.retain(|_, age| {
100 Self::is_hash_index_valid(self.last_hash_index, self.max_age, age.hash_index)
101 });
102 }
103
104 self.ages.insert(
105 *hash,
106 HashAge {
107 fee_calculator: FeeCalculator::new(lamports_per_signature),
108 hash_index: self.last_hash_index,
109 timestamp: timestamp(),
110 },
111 );
112
113 self.last_hash = Some(*hash);
114 }
115
116 #[deprecated(
117 since = "1.9.0",
118 note = "Please do not use, will no longer be available in the future"
119 )]
120 #[allow(deprecated)]
121 pub fn get_recent_blockhashes(&self) -> impl Iterator<Item = recent_blockhashes::IterItem> {
122 (self.ages).iter().map(|(k, v)| {
123 recent_blockhashes::IterItem(v.hash_index, k, v.fee_calculator.lamports_per_signature)
124 })
125 }
126
127 pub(crate) fn get_max_age(&self) -> usize {
128 self.max_age
129 }
130}
131#[cfg(test)]
132mod tests {
133 #[allow(deprecated)]
134 use solana_sdk::sysvar::recent_blockhashes::IterItem;
135 use {
136 super::*,
137 bincode::serialize,
138 solana_sdk::{clock::MAX_RECENT_BLOCKHASHES, hash::hash},
139 };
140
141 #[test]
142 fn test_register_hash() {
143 let last_hash = Hash::default();
144 let mut hash_queue = BlockhashQueue::new(100);
145 assert!(!hash_queue.is_hash_valid(&last_hash));
146 hash_queue.register_hash(&last_hash, 0);
147 assert!(hash_queue.is_hash_valid(&last_hash));
148 assert_eq!(hash_queue.last_hash_index, 1);
149 }
150
151 #[test]
152 fn test_reject_old_last_hash() {
153 let mut hash_queue = BlockhashQueue::new(100);
154 let last_hash = hash(&serialize(&0).unwrap());
155 for i in 0..102 {
156 let last_hash = hash(&serialize(&i).unwrap());
157 hash_queue.register_hash(&last_hash, 0);
158 }
159 assert!(!hash_queue.is_hash_valid(&last_hash));
161 assert!(!hash_queue.is_hash_valid_for_age(&last_hash, 0));
162
163 let last_valid_hash = hash(&serialize(&1).unwrap());
165 assert!(hash_queue.is_hash_valid(&last_valid_hash));
166 assert!(!hash_queue.is_hash_valid_for_age(&last_valid_hash, 0));
167 }
168
169 #[test]
171 fn test_queue_init_blockhash() {
172 let last_hash = Hash::default();
173 let mut hash_queue = BlockhashQueue::new(100);
174 hash_queue.register_hash(&last_hash, 0);
175 assert_eq!(last_hash, hash_queue.last_hash());
176 assert!(hash_queue.is_hash_valid_for_age(&last_hash, 0));
177 }
178
179 #[test]
180 fn test_get_recent_blockhashes() {
181 let mut blockhash_queue = BlockhashQueue::new(MAX_RECENT_BLOCKHASHES);
182 #[allow(deprecated)]
183 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
184 assert_eq!(recent_blockhashes.count(), 0);
186 for i in 0..MAX_RECENT_BLOCKHASHES {
187 let hash = hash(&serialize(&i).unwrap());
188 blockhash_queue.register_hash(&hash, 0);
189 }
190 #[allow(deprecated)]
191 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
192 #[allow(deprecated)]
194 for IterItem(_slot, hash, _lamports_per_signature) in recent_blockhashes {
195 assert!(blockhash_queue.is_hash_valid_for_age(hash, MAX_RECENT_BLOCKHASHES));
196 }
197 }
198
199 #[test]
200 fn test_len() {
201 const MAX_AGE: usize = 10;
202 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
203 assert_eq!(hash_queue.ages.len(), 0);
204
205 for _ in 0..MAX_AGE {
206 hash_queue.register_hash(&Hash::new_unique(), 0);
207 }
208 assert_eq!(hash_queue.ages.len(), MAX_AGE);
209
210 hash_queue.register_hash(&Hash::new_unique(), 0);
214 assert_eq!(hash_queue.ages.len(), MAX_AGE + 1);
215
216 hash_queue.register_hash(&Hash::new_unique(), 0);
218 assert_eq!(hash_queue.ages.len(), MAX_AGE + 1);
219 }
220
221 #[test]
222 fn test_get_hash_age() {
223 const MAX_AGE: usize = 10;
224 let mut hash_list: Vec<Hash> = Vec::new();
225 hash_list.resize_with(MAX_AGE + 1, Hash::new_unique);
226
227 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
228 for hash in &hash_list {
229 assert!(hash_queue.get_hash_age(hash).is_none());
230 }
231
232 for hash in &hash_list {
233 hash_queue.register_hash(hash, 0);
234 }
235
236 for (age, hash) in hash_list.iter().rev().enumerate() {
240 assert_eq!(hash_queue.get_hash_age(hash), Some(age as u64));
241 }
242
243 hash_queue.register_hash(&Hash::new_unique(), 0);
245 assert!(hash_queue.get_hash_age(&hash_list[0]).is_none());
246 }
247
248 #[test]
249 fn test_is_hash_valid_for_age() {
250 const MAX_AGE: usize = 10;
251 let mut hash_list: Vec<Hash> = Vec::new();
252 hash_list.resize_with(MAX_AGE + 1, Hash::new_unique);
253
254 let mut hash_queue = BlockhashQueue::new(MAX_AGE);
255 for hash in &hash_list {
256 assert!(!hash_queue.is_hash_valid_for_age(hash, MAX_AGE));
257 }
258
259 for hash in &hash_list {
260 hash_queue.register_hash(hash, 0);
261 }
262
263 for hash in &hash_list {
267 assert!(hash_queue.is_hash_valid_for_age(hash, MAX_AGE));
268 }
269
270 assert!(hash_queue.is_hash_valid_for_age(&hash_list[MAX_AGE], 0));
272 assert!(!hash_queue.is_hash_valid_for_age(&hash_list[MAX_AGE - 1], 0));
273 }
274}