1bitflags::bitflags! {
2/// Marker to represents the current state of a transaction in the pool and from which the corresponding sub-pool is derived, depending on what bits are set.
3 ///
4 /// This mirrors [erigon's ephemeral state field](https://github.com/ledgerwatch/erigon/wiki/Transaction-Pool-Design#ordering-function).
5 ///
6 /// The [SubPool] the transaction belongs to is derived from its state and determined by the following sequential checks:
7 ///
8 /// - If it satisfies the [TxState::PENDING_POOL_BITS] it belongs in the pending sub-pool: [SubPool::Pending].
9 /// - If it is an EIP-4844 blob transaction it belongs in the blob sub-pool: [SubPool::Blob].
10 /// - If it satisfies the [TxState::BASE_FEE_POOL_BITS] it belongs in the base fee sub-pool: [SubPool::BaseFee].
11 ///
12 /// Otherwise, it belongs in the queued sub-pool: [SubPool::Queued].
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
14pub(crate) struct TxState: u8 {
15/// Set to `1` if all ancestor transactions are pending.
16const NO_PARKED_ANCESTORS = 0b10000000;
17/// Set to `1` of the transaction is either the next transaction of the sender (on chain nonce == tx.nonce) or all prior transactions are also present in the pool.
18const NO_NONCE_GAPS = 0b01000000;
19/// Bit derived from the sender's balance.
20 ///
21 /// Set to `1` if the sender's balance can cover the maximum cost for this transaction (`feeCap * gasLimit + value`).
22 /// This includes cumulative costs of prior transactions, which ensures that the sender has enough funds for all max cost of prior transactions.
23const ENOUGH_BALANCE = 0b00100000;
24/// Bit set to true if the transaction has a lower gas limit than the block's gas limit.
25const NOT_TOO_MUCH_GAS = 0b00010000;
26/// Covers the Dynamic fee requirement.
27 ///
28 /// Set to 1 if `maxFeePerGas` of the transaction meets the requirement of the pending block.
29const ENOUGH_FEE_CAP_BLOCK = 0b00001000;
30/// Covers the dynamic blob fee requirement, only relevant for EIP-4844 blob transactions.
31 ///
32 /// Set to 1 if `maxBlobFeePerGas` of the transaction meets the requirement of the pending block.
33const ENOUGH_BLOB_FEE_CAP_BLOCK = 0b00000100;
34/// Marks whether the transaction is a blob transaction.
35 ///
36 /// We track this as part of the state for simplicity, since blob transactions are handled differently and are mutually exclusive with normal transactions.
37const BLOB_TRANSACTION = 0b00000010;
3839const 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();
4041const BASE_FEE_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits();
4243const QUEUED_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits();
4445const BLOB_POOL_BITS = Self::BLOB_TRANSACTION.bits();
46 }
47}
4849impl TxState {
50/// The state of a transaction is considered `pending`, if the transaction has:
51 /// - _No_ parked ancestors
52 /// - enough balance
53 /// - enough fee cap
54 /// - enough blob fee cap
55#[inline]
56pub(crate) const fn is_pending(&self) -> bool {
57self.bits() >= Self::PENDING_POOL_BITS.bits()
58 }
5960/// Whether this transaction is a blob transaction.
61#[inline]
62pub(crate) const fn is_blob(&self) -> bool {
63self.contains(Self::BLOB_TRANSACTION)
64 }
6566/// Returns `true` if the transaction has a nonce gap.
67#[inline]
68pub(crate) const fn has_nonce_gap(&self) -> bool {
69 !self.intersects(Self::NO_NONCE_GAPS)
70 }
71}
7273/// Identifier for the transaction Sub-pool
74#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
75#[repr(u8)]
76pub enum SubPool {
77/// The queued sub-pool contains transactions that are not ready to be included in the next
78 /// block because they have missing or queued ancestors or the sender the lacks funds to
79 /// execute this transaction.
80Queued = 0,
81/// The base-fee sub-pool contains transactions that are not ready to be included in the next
82 /// block because they don't meet the base fee requirement.
83BaseFee,
84/// The blob sub-pool contains all blob transactions that are __not__ pending.
85Blob,
86/// The pending sub-pool contains transactions that are ready to be included in the next block.
87Pending,
88}
8990impl SubPool {
91/// Whether this transaction is to be moved to the pending sub-pool.
92#[inline]
93pub const fn is_pending(&self) -> bool {
94matches!(self, Self::Pending)
95 }
9697/// Whether this transaction is in the queued pool.
98#[inline]
99pub const fn is_queued(&self) -> bool {
100matches!(self, Self::Queued)
101 }
102103/// Whether this transaction is in the base fee pool.
104#[inline]
105pub const fn is_base_fee(&self) -> bool {
106matches!(self, Self::BaseFee)
107 }
108109/// Whether this transaction is in the blob pool.
110#[inline]
111pub const fn is_blob(&self) -> bool {
112matches!(self, Self::Blob)
113 }
114115/// Returns whether this is a promotion depending on the current sub-pool location.
116#[inline]
117pub fn is_promoted(&self, other: Self) -> bool {
118self> &other119 }
120}
121122impl From<TxState> for SubPool {
123fn from(value: TxState) -> Self {
124if value.is_pending() {
125Self::Pending
126 } else if value.is_blob() {
127// all _non-pending_ blob transactions are in the blob sub-pool
128Self::Blob
129 } else if value.bits() < TxState::BASE_FEE_POOL_BITS.bits() {
130Self::Queued
131 } else {
132Self::BaseFee
133 }
134 }
135}
136137#[cfg(test)]
138mod tests {
139use super::*;
140141#[test]
142fn test_promoted() {
143assert!(SubPool::BaseFee.is_promoted(SubPool::Queued));
144assert!(SubPool::Pending.is_promoted(SubPool::BaseFee));
145assert!(SubPool::Pending.is_promoted(SubPool::Queued));
146assert!(SubPool::Pending.is_promoted(SubPool::Blob));
147assert!(!SubPool::BaseFee.is_promoted(SubPool::Pending));
148assert!(!SubPool::Queued.is_promoted(SubPool::BaseFee));
149 }
150151#[test]
152fn test_tx_state() {
153let mut state = TxState::default();
154 state |= TxState::NO_NONCE_GAPS;
155assert!(state.intersects(TxState::NO_NONCE_GAPS))
156 }
157158#[test]
159fn test_tx_queued() {
160let state = TxState::default();
161assert_eq!(SubPool::Queued, state.into());
162163let state = TxState::NO_PARKED_ANCESTORS |
164 TxState::NO_NONCE_GAPS |
165 TxState::NOT_TOO_MUCH_GAS |
166 TxState::ENOUGH_FEE_CAP_BLOCK;
167assert_eq!(SubPool::Queued, state.into());
168 }
169170#[test]
171fn test_tx_pending() {
172let state = TxState::PENDING_POOL_BITS;
173assert_eq!(SubPool::Pending, state.into());
174assert!(state.is_pending());
175176let bits = 0b11111100;
177let state = TxState::from_bits(bits).unwrap();
178assert_eq!(SubPool::Pending, state.into());
179assert!(state.is_pending());
180181let bits = 0b11111110;
182let state = TxState::from_bits(bits).unwrap();
183assert_eq!(SubPool::Pending, state.into());
184assert!(state.is_pending());
185 }
186187#[test]
188fn test_blob() {
189let mut state = TxState::PENDING_POOL_BITS;
190 state.insert(TxState::BLOB_TRANSACTION);
191assert!(state.is_pending());
192193 state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
194assert!(state.is_blob());
195assert!(!state.is_pending());
196197 state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
198 state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
199assert!(state.is_blob());
200assert!(!state.is_pending());
201 }
202203#[test]
204fn test_tx_state_no_nonce_gap() {
205let mut state = TxState::default();
206 state |= TxState::NO_NONCE_GAPS;
207assert!(!state.has_nonce_gap());
208 }
209210#[test]
211fn test_tx_state_with_nonce_gap() {
212let state = TxState::default();
213assert!(state.has_nonce_gap());
214 }
215216#[test]
217fn test_tx_state_enough_balance() {
218let mut state = TxState::default();
219 state.insert(TxState::ENOUGH_BALANCE);
220assert!(state.contains(TxState::ENOUGH_BALANCE));
221 }
222223#[test]
224fn test_tx_state_not_too_much_gas() {
225let mut state = TxState::default();
226 state.insert(TxState::NOT_TOO_MUCH_GAS);
227assert!(state.contains(TxState::NOT_TOO_MUCH_GAS));
228 }
229230#[test]
231fn test_tx_state_enough_fee_cap_block() {
232let mut state = TxState::default();
233 state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
234assert!(state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
235 }
236237#[test]
238fn test_tx_base_fee() {
239let state = TxState::BASE_FEE_POOL_BITS;
240assert_eq!(SubPool::BaseFee, state.into());
241 }
242243#[test]
244fn test_blob_transaction_only() {
245let state = TxState::BLOB_TRANSACTION;
246assert_eq!(SubPool::Blob, state.into());
247assert!(state.is_blob());
248assert!(!state.is_pending());
249 }
250251#[test]
252fn test_blob_transaction_with_base_fee_bits() {
253let mut state = TxState::BASE_FEE_POOL_BITS;
254 state.insert(TxState::BLOB_TRANSACTION);
255assert_eq!(SubPool::Blob, state.into());
256assert!(state.is_blob());
257assert!(!state.is_pending());
258 }
259}