reth_transaction_pool/pool/
state.rs1use crate::pool::QueuedReason;
2
3bitflags::bitflags! {
4 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
16 pub(crate) struct TxState: u8 {
17 const NO_PARKED_ANCESTORS = 0b10000000;
19 const NO_NONCE_GAPS = 0b01000000;
21 const ENOUGH_BALANCE = 0b00100000;
26 const NOT_TOO_MUCH_GAS = 0b00010000;
28 const ENOUGH_FEE_CAP_BLOCK = 0b00001000;
32 const ENOUGH_BLOB_FEE_CAP_BLOCK = 0b00000100;
36 const BLOB_TRANSACTION = 0b00000010;
40
41 const PENDING_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits() | Self::ENOUGH_FEE_CAP_BLOCK.bits() | Self::ENOUGH_BLOB_FEE_CAP_BLOCK.bits();
42
43 const BASE_FEE_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits();
44
45 const QUEUED_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits();
46
47 const BLOB_POOL_BITS = Self::BLOB_TRANSACTION.bits();
48 }
49}
50
51impl TxState {
52 #[inline]
58 pub(crate) const fn is_pending(&self) -> bool {
59 self.bits() >= Self::PENDING_POOL_BITS.bits()
60 }
61
62 #[inline]
64 pub(crate) const fn is_blob(&self) -> bool {
65 self.contains(Self::BLOB_TRANSACTION)
66 }
67
68 #[inline]
70 pub(crate) const fn has_nonce_gap(&self) -> bool {
71 !self.intersects(Self::NO_NONCE_GAPS)
72 }
73
74 pub(crate) const fn determine_queued_reason(&self, subpool: SubPool) -> Option<QueuedReason> {
102 match subpool {
103 SubPool::Pending => None, SubPool::Queued => {
105 if !self.contains(Self::NO_NONCE_GAPS) {
107 Some(QueuedReason::NonceGap)
108 } else if !self.contains(Self::ENOUGH_BALANCE) {
109 Some(QueuedReason::InsufficientBalance)
110 } else if !self.contains(Self::NO_PARKED_ANCESTORS) {
111 Some(QueuedReason::ParkedAncestors)
112 } else if !self.contains(Self::NOT_TOO_MUCH_GAS) {
113 Some(QueuedReason::TooMuchGas)
114 } else {
115 Some(QueuedReason::NonceGap)
117 }
118 }
119 SubPool::BaseFee => Some(QueuedReason::InsufficientBaseFee),
120 SubPool::Blob => {
121 if !self.contains(Self::NO_NONCE_GAPS) {
123 Some(QueuedReason::NonceGap)
124 } else if !self.contains(Self::ENOUGH_BALANCE) {
125 Some(QueuedReason::InsufficientBalance)
126 } else if !self.contains(Self::NO_PARKED_ANCESTORS) {
127 Some(QueuedReason::ParkedAncestors)
128 } else if !self.contains(Self::NOT_TOO_MUCH_GAS) {
129 Some(QueuedReason::TooMuchGas)
130 } else if !self.contains(Self::ENOUGH_FEE_CAP_BLOCK) {
131 Some(QueuedReason::InsufficientBaseFee)
132 } else if !self.contains(Self::ENOUGH_BLOB_FEE_CAP_BLOCK) {
133 Some(QueuedReason::InsufficientBlobFee)
134 } else {
135 Some(QueuedReason::InsufficientBlobFee)
137 }
138 }
139 }
140 }
141}
142
143#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
145#[repr(u8)]
146pub enum SubPool {
147 Queued = 0,
151 BaseFee,
154 Blob,
156 Pending,
158}
159
160impl SubPool {
161 #[inline]
163 pub const fn is_pending(&self) -> bool {
164 matches!(self, Self::Pending)
165 }
166
167 #[inline]
169 pub const fn is_queued(&self) -> bool {
170 matches!(self, Self::Queued)
171 }
172
173 #[inline]
175 pub const fn is_base_fee(&self) -> bool {
176 matches!(self, Self::BaseFee)
177 }
178
179 #[inline]
181 pub const fn is_blob(&self) -> bool {
182 matches!(self, Self::Blob)
183 }
184
185 #[inline]
187 pub fn is_promoted(&self, other: Self) -> bool {
188 self > &other
189 }
190}
191
192impl From<TxState> for SubPool {
193 fn from(value: TxState) -> Self {
194 if value.is_pending() {
195 Self::Pending
196 } else if value.is_blob() {
197 Self::Blob
199 } else if value.bits() < TxState::BASE_FEE_POOL_BITS.bits() {
200 Self::Queued
201 } else {
202 Self::BaseFee
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_promoted() {
213 assert!(SubPool::BaseFee.is_promoted(SubPool::Queued));
214 assert!(SubPool::Pending.is_promoted(SubPool::BaseFee));
215 assert!(SubPool::Pending.is_promoted(SubPool::Queued));
216 assert!(SubPool::Pending.is_promoted(SubPool::Blob));
217 assert!(!SubPool::BaseFee.is_promoted(SubPool::Pending));
218 assert!(!SubPool::Queued.is_promoted(SubPool::BaseFee));
219 }
220
221 #[test]
222 fn test_tx_state() {
223 let mut state = TxState::default();
224 state |= TxState::NO_NONCE_GAPS;
225 assert!(state.intersects(TxState::NO_NONCE_GAPS))
226 }
227
228 #[test]
229 fn test_tx_queued() {
230 let state = TxState::default();
231 assert_eq!(SubPool::Queued, state.into());
232
233 let state = TxState::NO_PARKED_ANCESTORS |
234 TxState::NO_NONCE_GAPS |
235 TxState::NOT_TOO_MUCH_GAS |
236 TxState::ENOUGH_FEE_CAP_BLOCK;
237 assert_eq!(SubPool::Queued, state.into());
238 }
239
240 #[test]
241 fn test_tx_pending() {
242 let state = TxState::PENDING_POOL_BITS;
243 assert_eq!(SubPool::Pending, state.into());
244 assert!(state.is_pending());
245
246 let bits = 0b11111100;
247 let state = TxState::from_bits(bits).unwrap();
248 assert_eq!(SubPool::Pending, state.into());
249 assert!(state.is_pending());
250
251 let bits = 0b11111110;
252 let state = TxState::from_bits(bits).unwrap();
253 assert_eq!(SubPool::Pending, state.into());
254 assert!(state.is_pending());
255 }
256
257 #[test]
258 fn test_blob() {
259 let mut state = TxState::PENDING_POOL_BITS;
260 state.insert(TxState::BLOB_TRANSACTION);
261 assert!(state.is_pending());
262
263 state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
264 assert!(state.is_blob());
265 assert!(!state.is_pending());
266
267 state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
268 state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
269 assert!(state.is_blob());
270 assert!(!state.is_pending());
271 }
272
273 #[test]
274 fn test_tx_state_no_nonce_gap() {
275 let mut state = TxState::default();
276 state |= TxState::NO_NONCE_GAPS;
277 assert!(!state.has_nonce_gap());
278 }
279
280 #[test]
281 fn test_tx_state_with_nonce_gap() {
282 let state = TxState::default();
283 assert!(state.has_nonce_gap());
284 }
285
286 #[test]
287 fn test_tx_state_enough_balance() {
288 let mut state = TxState::default();
289 state.insert(TxState::ENOUGH_BALANCE);
290 assert!(state.contains(TxState::ENOUGH_BALANCE));
291 }
292
293 #[test]
294 fn test_tx_state_not_too_much_gas() {
295 let mut state = TxState::default();
296 state.insert(TxState::NOT_TOO_MUCH_GAS);
297 assert!(state.contains(TxState::NOT_TOO_MUCH_GAS));
298 }
299
300 #[test]
301 fn test_tx_state_enough_fee_cap_block() {
302 let mut state = TxState::default();
303 state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
304 assert!(state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
305 }
306
307 #[test]
308 fn test_tx_base_fee() {
309 let state = TxState::BASE_FEE_POOL_BITS;
310 assert_eq!(SubPool::BaseFee, state.into());
311 }
312
313 #[test]
314 fn test_blob_transaction_only() {
315 let state = TxState::BLOB_TRANSACTION;
316 assert_eq!(SubPool::Blob, state.into());
317 assert!(state.is_blob());
318 assert!(!state.is_pending());
319 }
320
321 #[test]
322 fn test_blob_transaction_with_base_fee_bits() {
323 let mut state = TxState::BASE_FEE_POOL_BITS;
324 state.insert(TxState::BLOB_TRANSACTION);
325 assert_eq!(SubPool::Blob, state.into());
326 assert!(state.is_blob());
327 assert!(!state.is_pending());
328 }
329
330 #[test]
331 fn test_blob_reason_insufficient_base_fee() {
332 let state = TxState::NO_PARKED_ANCESTORS |
334 TxState::NO_NONCE_GAPS |
335 TxState::ENOUGH_BALANCE |
336 TxState::NOT_TOO_MUCH_GAS |
337 TxState::ENOUGH_BLOB_FEE_CAP_BLOCK |
338 TxState::BLOB_TRANSACTION;
339 let subpool: SubPool = state.into();
341 assert_eq!(subpool, SubPool::Blob);
342 let reason = state.determine_queued_reason(subpool);
343 assert_eq!(reason, Some(QueuedReason::InsufficientBaseFee));
344 }
345
346 #[test]
347 fn test_blob_reason_insufficient_blob_fee() {
348 let state = TxState::NO_PARKED_ANCESTORS |
350 TxState::NO_NONCE_GAPS |
351 TxState::ENOUGH_BALANCE |
352 TxState::NOT_TOO_MUCH_GAS |
353 TxState::ENOUGH_FEE_CAP_BLOCK |
354 TxState::BLOB_TRANSACTION;
355 let subpool: SubPool = state.into();
357 assert_eq!(subpool, SubPool::Blob);
358 let reason = state.determine_queued_reason(subpool);
359 assert_eq!(reason, Some(QueuedReason::InsufficientBlobFee));
360 }
361
362 #[test]
363 fn test_blob_reason_nonce_gap() {
364 let mut state = TxState::NO_PARKED_ANCESTORS |
366 TxState::ENOUGH_BALANCE |
367 TxState::NOT_TOO_MUCH_GAS |
368 TxState::ENOUGH_FEE_CAP_BLOCK |
369 TxState::ENOUGH_BLOB_FEE_CAP_BLOCK |
370 TxState::BLOB_TRANSACTION;
371 state.remove(TxState::NO_NONCE_GAPS);
372 let subpool: SubPool = state.into();
373 assert_eq!(subpool, SubPool::Blob);
374 let reason = state.determine_queued_reason(subpool);
375 assert_eq!(reason, Some(QueuedReason::NonceGap));
376 }
377
378 #[test]
379 fn test_blob_reason_insufficient_balance() {
380 let state = TxState::NO_PARKED_ANCESTORS |
382 TxState::NO_NONCE_GAPS |
383 TxState::NOT_TOO_MUCH_GAS |
384 TxState::ENOUGH_FEE_CAP_BLOCK |
385 TxState::ENOUGH_BLOB_FEE_CAP_BLOCK |
386 TxState::BLOB_TRANSACTION;
387 let subpool: SubPool = state.into();
389 assert_eq!(subpool, SubPool::Blob);
390 let reason = state.determine_queued_reason(subpool);
391 assert_eq!(reason, Some(QueuedReason::InsufficientBalance));
392 }
393
394 #[test]
395 fn test_blob_reason_too_much_gas() {
396 let mut state = TxState::NO_PARKED_ANCESTORS |
398 TxState::NO_NONCE_GAPS |
399 TxState::ENOUGH_BALANCE |
400 TxState::ENOUGH_FEE_CAP_BLOCK |
401 TxState::ENOUGH_BLOB_FEE_CAP_BLOCK |
402 TxState::BLOB_TRANSACTION;
403 state.remove(TxState::NOT_TOO_MUCH_GAS);
404 let subpool: SubPool = state.into();
405 assert_eq!(subpool, SubPool::Blob);
406 let reason = state.determine_queued_reason(subpool);
407 assert_eq!(reason, Some(QueuedReason::TooMuchGas));
408 }
409}