gemachain_runtime/
blockhash_queue.rs1use serde::{Deserialize, Serialize};
2#[allow(deprecated)]
3use gemachain_sdk::sysvar::recent_blockhashes;
4use gemachain_sdk::{fee_calculator::FeeCalculator, hash::Hash, timing::timestamp};
5use std::collections::HashMap;
6
7#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiExample)]
8struct HashAge {
9 fee_calculator: FeeCalculator,
10 hash_height: u64,
11 timestamp: u64,
12}
13
14#[frozen_abi(digest = "J1fGiMHyiKEBcWE6mfm7grAEGJgYEaVLzcrNZvd37iA2")]
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AbiExample)]
17pub struct BlockhashQueue {
18 hash_height: u64,
20
21 last_hash: Option<Hash>,
23
24 ages: HashMap<Hash, HashAge>,
25
26 max_age: usize,
28}
29
30impl BlockhashQueue {
31 pub fn new(max_age: usize) -> Self {
32 Self {
33 ages: HashMap::new(),
34 hash_height: 0,
35 last_hash: None,
36 max_age,
37 }
38 }
39
40 #[allow(dead_code)]
41 pub fn hash_height(&self) -> u64 {
42 self.hash_height
43 }
44
45 pub fn last_hash(&self) -> Hash {
46 self.last_hash.expect("no hash has been set")
47 }
48
49 #[deprecated(
50 since = "1.8.0",
51 note = "Please do not use, will no longer be available in the future"
52 )]
53 pub fn get_fee_calculator(&self, hash: &Hash) -> Option<&FeeCalculator> {
54 self.ages.get(hash).map(|hash_age| &hash_age.fee_calculator)
55 }
56
57 pub fn check_hash_age(&self, hash: &Hash, max_age: usize) -> Option<bool> {
61 self.ages
62 .get(hash)
63 .map(|age| self.hash_height - age.hash_height <= max_age as u64)
64 }
65
66 pub fn get_hash_age(&self, hash: &Hash) -> Option<u64> {
67 self.ages
68 .get(hash)
69 .map(|age| self.hash_height - age.hash_height)
70 }
71
72 pub fn check_hash(&self, hash: &Hash) -> bool {
74 self.ages.get(hash).is_some()
75 }
76
77 pub fn genesis_hash(&mut self, hash: &Hash, fee_calculator: &FeeCalculator) {
78 self.ages.insert(
79 *hash,
80 HashAge {
81 fee_calculator: fee_calculator.clone(),
82 hash_height: 0,
83 timestamp: timestamp(),
84 },
85 );
86
87 self.last_hash = Some(*hash);
88 }
89
90 fn check_age(hash_height: u64, max_age: usize, age: &HashAge) -> bool {
91 hash_height - age.hash_height <= max_age as u64
92 }
93
94 pub fn register_hash(&mut self, hash: &Hash, fee_calculator: &FeeCalculator) {
95 self.hash_height += 1;
96 let hash_height = self.hash_height;
97
98 let max_age = self.max_age;
101 if self.ages.len() >= max_age {
102 self.ages
103 .retain(|_, age| Self::check_age(hash_height, max_age, age));
104 }
105 self.ages.insert(
106 *hash,
107 HashAge {
108 fee_calculator: fee_calculator.clone(),
109 hash_height,
110 timestamp: timestamp(),
111 },
112 );
113
114 self.last_hash = Some(*hash);
115 }
116
117 pub fn hash_height_to_timestamp(&self, hash_height: u64) -> Option<u64> {
119 for age in self.ages.values() {
120 if age.hash_height == hash_height {
121 return Some(age.timestamp);
122 }
123 }
124 None
125 }
126
127 #[deprecated(
128 since = "1.8.0",
129 note = "Please do not use, will no longer be available in the future"
130 )]
131 #[allow(deprecated)]
132 pub fn get_recent_blockhashes(&self) -> impl Iterator<Item = recent_blockhashes::IterItem> {
133 (&self.ages)
134 .iter()
135 .map(|(k, v)| recent_blockhashes::IterItem(v.hash_height, k, &v.fee_calculator))
136 }
137
138 pub(crate) fn len(&self) -> usize {
139 self.max_age
140 }
141}
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use bincode::serialize;
146 #[allow(deprecated)]
147 use gemachain_sdk::sysvar::recent_blockhashes::IterItem;
148 use gemachain_sdk::{clock::MAX_RECENT_BLOCKHASHES, hash::hash};
149
150 #[test]
151 fn test_register_hash() {
152 let last_hash = Hash::default();
153 let mut hash_queue = BlockhashQueue::new(100);
154 assert!(!hash_queue.check_hash(&last_hash));
155 hash_queue.register_hash(&last_hash, &FeeCalculator::default());
156 assert!(hash_queue.check_hash(&last_hash));
157 assert_eq!(hash_queue.hash_height(), 1);
158 }
159
160 #[test]
161 fn test_reject_old_last_hash() {
162 let mut hash_queue = BlockhashQueue::new(100);
163 let last_hash = hash(&serialize(&0).unwrap());
164 for i in 0..102 {
165 let last_hash = hash(&serialize(&i).unwrap());
166 hash_queue.register_hash(&last_hash, &FeeCalculator::default());
167 }
168 assert!(!hash_queue.check_hash(&last_hash));
170 assert_eq!(None, hash_queue.check_hash_age(&last_hash, 0));
171
172 let last_valid_hash = hash(&serialize(&1).unwrap());
174 assert!(hash_queue.check_hash(&last_valid_hash));
175 assert_eq!(Some(false), hash_queue.check_hash_age(&last_valid_hash, 0));
176 }
177
178 #[test]
180 fn test_queue_init_blockhash() {
181 let last_hash = Hash::default();
182 let mut hash_queue = BlockhashQueue::new(100);
183 hash_queue.register_hash(&last_hash, &FeeCalculator::default());
184 assert_eq!(last_hash, hash_queue.last_hash());
185 assert_eq!(Some(true), hash_queue.check_hash_age(&last_hash, 0));
186 }
187
188 #[test]
189 fn test_get_recent_blockhashes() {
190 let mut blockhash_queue = BlockhashQueue::new(MAX_RECENT_BLOCKHASHES);
191 #[allow(deprecated)]
192 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
193 assert_eq!(recent_blockhashes.count(), 0);
195 for i in 0..MAX_RECENT_BLOCKHASHES {
196 let hash = hash(&serialize(&i).unwrap());
197 blockhash_queue.register_hash(&hash, &FeeCalculator::default());
198 }
199 #[allow(deprecated)]
200 let recent_blockhashes = blockhash_queue.get_recent_blockhashes();
201 #[allow(deprecated)]
203 for IterItem(_slot, hash, _fee_calc) in recent_blockhashes {
204 assert_eq!(
205 Some(true),
206 blockhash_queue.check_hash_age(hash, MAX_RECENT_BLOCKHASHES)
207 );
208 }
209 }
210}