sc_transaction_pool/graph/
rotator.rs1use parking_lot::RwLock;
25use std::{
26 collections::HashMap,
27 hash, iter,
28 time::{Duration, Instant},
29};
30
31use super::base_pool::Transaction;
32
33const DEFAULT_EXPECTED_SIZE: usize = 2048;
35
36const DEFAULT_BAN_TIME_SECS: u64 = 30 * 60;
38
39pub struct PoolRotator<Hash> {
44 ban_time: Duration,
46 banned_until: RwLock<HashMap<Hash, Instant>>,
48 expected_size: usize,
50}
51
52impl<Hash: Clone> Clone for PoolRotator<Hash> {
53 fn clone(&self) -> Self {
54 Self {
55 ban_time: self.ban_time,
56 banned_until: RwLock::new(self.banned_until.read().clone()),
57 expected_size: self.expected_size,
58 }
59 }
60}
61
62impl<Hash: hash::Hash + Eq> Default for PoolRotator<Hash> {
63 fn default() -> Self {
64 Self {
65 ban_time: Duration::from_secs(DEFAULT_BAN_TIME_SECS),
66 banned_until: Default::default(),
67 expected_size: DEFAULT_EXPECTED_SIZE,
68 }
69 }
70}
71
72impl<Hash: hash::Hash + Eq + Clone> PoolRotator<Hash> {
73 pub fn new(ban_time: Duration) -> Self {
75 Self { ban_time, ..Self::default() }
76 }
77
78 pub fn new_with_expected_size(ban_time: Duration, expected_size: usize) -> Self {
80 Self { expected_size, ..Self::new(ban_time) }
81 }
82
83 pub fn is_banned(&self, hash: &Hash) -> bool {
85 self.banned_until.read().contains_key(hash)
86 }
87
88 pub fn ban(&self, now: &Instant, hashes: impl IntoIterator<Item = Hash>) {
90 let mut banned = self.banned_until.write();
91
92 for hash in hashes {
93 banned.insert(hash, *now + self.ban_time);
94 }
95
96 if banned.len() > 2 * self.expected_size {
97 while banned.len() > self.expected_size {
98 if let Some(key) = banned.keys().next().cloned() {
99 banned.remove(&key);
100 }
101 }
102 }
103 }
104
105 pub fn ban_if_stale<Ex>(
109 &self,
110 now: &Instant,
111 current_block: u64,
112 xt: &Transaction<Hash, Ex>,
113 ) -> bool {
114 if xt.valid_till > current_block {
115 return false
116 }
117
118 self.ban(now, iter::once(xt.hash.clone()));
119 true
120 }
121
122 pub fn clear_timeouts(&self, now: &Instant) {
124 let mut banned = self.banned_until.write();
125
126 banned.retain(|_, &mut v| v >= *now);
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 type Hash = u64;
135 type Ex = ();
136
137 fn rotator() -> PoolRotator<Hash> {
138 PoolRotator { ban_time: Duration::from_millis(10), ..Default::default() }
139 }
140
141 fn tx() -> (Hash, Transaction<Hash, Ex>) {
142 let hash = 5u64;
143 let tx = Transaction {
144 data: (),
145 bytes: 1,
146 hash,
147 priority: 5,
148 valid_till: 1,
149 requires: vec![],
150 provides: vec![],
151 propagate: true,
152 source: crate::TimedTransactionSource::new_external(false),
153 };
154
155 (hash, tx)
156 }
157
158 #[test]
159 fn should_not_ban_if_not_stale() {
160 let (hash, tx) = tx();
162 let rotator = rotator();
163 assert!(!rotator.is_banned(&hash));
164 let now = Instant::now();
165 let past_block = 0;
166
167 assert!(!rotator.ban_if_stale(&now, past_block, &tx));
169
170 assert!(!rotator.is_banned(&hash));
172 }
173
174 #[test]
175 fn should_ban_stale_extrinsic() {
176 let (hash, tx) = tx();
178 let rotator = rotator();
179 assert!(!rotator.is_banned(&hash));
180
181 assert!(rotator.ban_if_stale(&Instant::now(), 1, &tx));
183
184 assert!(rotator.is_banned(&hash));
186 }
187
188 #[test]
189 fn should_clear_banned() {
190 let (hash, tx) = tx();
192 let rotator = rotator();
193 assert!(rotator.ban_if_stale(&Instant::now(), 1, &tx));
194 assert!(rotator.is_banned(&hash));
195
196 let future = Instant::now() + rotator.ban_time + rotator.ban_time;
198 rotator.clear_timeouts(&future);
199
200 assert!(!rotator.is_banned(&hash));
202 }
203
204 #[test]
205 fn should_garbage_collect() {
206 fn tx_with(i: u64, valid_till: u64) -> Transaction<Hash, Ex> {
208 let hash = i;
209 Transaction {
210 data: (),
211 bytes: 2,
212 hash,
213 priority: 5,
214 valid_till,
215 requires: vec![],
216 provides: vec![],
217 propagate: true,
218 source: crate::TimedTransactionSource::new_external(false),
219 }
220 }
221
222 let rotator = rotator();
223
224 let now = Instant::now();
225 let past_block = 0;
226
227 for i in 0..2 * DEFAULT_EXPECTED_SIZE {
229 let tx = tx_with(i as u64, past_block);
230 assert!(rotator.ban_if_stale(&now, past_block, &tx));
231 }
232 assert_eq!(rotator.banned_until.read().len(), 2 * DEFAULT_EXPECTED_SIZE);
233
234 let tx = tx_with(2 * DEFAULT_EXPECTED_SIZE as u64, past_block);
236 assert!(rotator.ban_if_stale(&now, past_block, &tx));
238 assert_eq!(rotator.banned_until.read().len(), DEFAULT_EXPECTED_SIZE);
239 }
240}