reth_transaction_pool/
config.rs

1use crate::{
2    maintain::MAX_QUEUED_TRANSACTION_LIFETIME,
3    pool::{NEW_TX_LISTENER_BUFFER_SIZE, PENDING_TX_LISTENER_BUFFER_SIZE},
4    PoolSize, TransactionOrigin,
5};
6use alloy_consensus::constants::EIP4844_TX_TYPE_ID;
7use alloy_eips::eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, MIN_PROTOCOL_BASE_FEE};
8use alloy_primitives::Address;
9use std::{collections::HashSet, ops::Mul, time::Duration};
10
11/// Guarantees max transactions for one sender, compatible with geth/erigon
12pub const TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER: usize = 16;
13
14/// The default maximum allowed number of transactions in the given subpool.
15pub const TXPOOL_SUBPOOL_MAX_TXS_DEFAULT: usize = 10_000;
16
17/// The default maximum allowed size of the given subpool.
18pub const TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT: usize = 20;
19
20/// The default additional validation tasks size.
21pub const DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS: usize = 1;
22
23/// Default price bump (in %) for the transaction pool underpriced check.
24pub const DEFAULT_PRICE_BUMP: u128 = 10;
25
26/// Replace blob price bump (in %) for the transaction pool underpriced check.
27///
28/// This enforces that a blob transaction requires a 100% price bump to be replaced
29pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100;
30
31/// Default maximum new transactions for broadcasting.
32pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200;
33
34/// Configuration options for the Transaction pool.
35#[derive(Debug, Clone)]
36pub struct PoolConfig {
37    /// Max number of transaction in the pending sub-pool
38    pub pending_limit: SubPoolLimit,
39    /// Max number of transaction in the basefee sub-pool
40    pub basefee_limit: SubPoolLimit,
41    /// Max number of transaction in the queued sub-pool
42    pub queued_limit: SubPoolLimit,
43    /// Max number of transactions in the blob sub-pool
44    pub blob_limit: SubPoolLimit,
45    /// Blob cache size
46    pub blob_cache_size: Option<u32>,
47    /// Max number of executable transaction slots guaranteed per account
48    pub max_account_slots: usize,
49    /// Price bump (in %) for the transaction pool underpriced check.
50    pub price_bumps: PriceBumpConfig,
51    /// Minimum base fee required by the protocol.
52    pub minimal_protocol_basefee: u64,
53    /// Minimum priority fee required for transaction acceptance into the pool.
54    pub minimum_priority_fee: Option<u128>,
55    /// The max gas limit for transactions in the pool
56    pub gas_limit: u64,
57    /// How to handle locally received transactions:
58    /// [`TransactionOrigin::Local`](TransactionOrigin).
59    pub local_transactions_config: LocalTransactionConfig,
60    /// Bound on number of pending transactions from `reth_network::TransactionsManager` to buffer.
61    pub pending_tx_listener_buffer_size: usize,
62    /// Bound on number of new transactions from `reth_network::TransactionsManager` to buffer.
63    pub new_tx_listener_buffer_size: usize,
64    /// How many new pending transactions to buffer and send iterators in progress.
65    pub max_new_pending_txs_notifications: usize,
66    /// Maximum lifetime for transactions in the pool
67    pub max_queued_lifetime: Duration,
68}
69
70impl PoolConfig {
71    /// Sets the minimal protocol base fee to 0, effectively disabling checks that enforce that a
72    /// transaction's fee must be higher than the [`MIN_PROTOCOL_BASE_FEE`] which is the lowest
73    /// value the ethereum EIP-1559 base fee can reach.
74    pub const fn with_disabled_protocol_base_fee(self) -> Self {
75        self.with_protocol_base_fee(0)
76    }
77
78    /// Configures the minimal protocol base fee that should be enforced.
79    ///
80    /// Ethereum's EIP-1559 base fee can't drop below [`MIN_PROTOCOL_BASE_FEE`] hence this is
81    /// enforced by default in the pool.
82    pub const fn with_protocol_base_fee(mut self, protocol_base_fee: u64) -> Self {
83        self.minimal_protocol_basefee = protocol_base_fee;
84        self
85    }
86
87    /// Returns whether the size and amount constraints in any sub-pools are exceeded.
88    #[inline]
89    pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool {
90        self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) ||
91            self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) ||
92            self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) ||
93            self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size)
94    }
95}
96
97impl Default for PoolConfig {
98    fn default() -> Self {
99        Self {
100            pending_limit: Default::default(),
101            basefee_limit: Default::default(),
102            queued_limit: Default::default(),
103            blob_limit: Default::default(),
104            blob_cache_size: None,
105            max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
106            price_bumps: Default::default(),
107            minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
108            minimum_priority_fee: None,
109            gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
110            local_transactions_config: Default::default(),
111            pending_tx_listener_buffer_size: PENDING_TX_LISTENER_BUFFER_SIZE,
112            new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE,
113            max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS,
114            max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME,
115        }
116    }
117}
118
119/// Size limits for a sub-pool.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct SubPoolLimit {
122    /// Maximum amount of transaction in the pool.
123    pub max_txs: usize,
124    /// Maximum combined size (in bytes) of transactions in the pool.
125    pub max_size: usize,
126}
127
128impl SubPoolLimit {
129    /// Creates a new instance with the given limits.
130    pub const fn new(max_txs: usize, max_size: usize) -> Self {
131        Self { max_txs, max_size }
132    }
133
134    /// Creates an unlimited [`SubPoolLimit`]
135    pub const fn max() -> Self {
136        Self::new(usize::MAX, usize::MAX)
137    }
138
139    /// Returns whether the size or amount constraint is violated.
140    #[inline]
141    pub const fn is_exceeded(&self, txs: usize, size: usize) -> bool {
142        self.max_txs < txs || self.max_size < size
143    }
144}
145
146impl Mul<usize> for SubPoolLimit {
147    type Output = Self;
148
149    fn mul(self, rhs: usize) -> Self::Output {
150        let Self { max_txs, max_size } = self;
151        Self { max_txs: max_txs * rhs, max_size: max_size * rhs }
152    }
153}
154
155impl Default for SubPoolLimit {
156    fn default() -> Self {
157        // either 10k transactions or 20MB
158        Self {
159            max_txs: TXPOOL_SUBPOOL_MAX_TXS_DEFAULT,
160            max_size: TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT * 1024 * 1024,
161        }
162    }
163}
164
165/// Price bump config (in %) for the transaction pool underpriced check.
166#[derive(Debug, Clone, Copy, Eq, PartialEq)]
167pub struct PriceBumpConfig {
168    /// Default price bump (in %) for the transaction pool underpriced check.
169    pub default_price_bump: u128,
170    /// Replace blob price bump (in %) for the transaction pool underpriced check.
171    pub replace_blob_tx_price_bump: u128,
172}
173
174impl PriceBumpConfig {
175    /// Returns the price bump required to replace the given transaction type.
176    #[inline]
177    pub const fn price_bump(&self, tx_type: u8) -> u128 {
178        if tx_type == EIP4844_TX_TYPE_ID {
179            return self.replace_blob_tx_price_bump
180        }
181        self.default_price_bump
182    }
183}
184
185impl Default for PriceBumpConfig {
186    fn default() -> Self {
187        Self {
188            default_price_bump: DEFAULT_PRICE_BUMP,
189            replace_blob_tx_price_bump: REPLACE_BLOB_PRICE_BUMP,
190        }
191    }
192}
193
194/// Configuration options for the locally received transactions:
195/// [`TransactionOrigin::Local`](TransactionOrigin)
196#[derive(Debug, Clone, Eq, PartialEq)]
197pub struct LocalTransactionConfig {
198    /// Apply no exemptions to the locally received transactions.
199    ///
200    /// This includes:
201    ///   - available slots are limited to the configured `max_account_slots` of [`PoolConfig`]
202    ///   - no price exemptions
203    ///   - no eviction exemptions
204    pub no_exemptions: bool,
205    /// Addresses that will be considered as local. Above exemptions apply.
206    pub local_addresses: HashSet<Address>,
207    /// Flag indicating whether local transactions should be propagated.
208    pub propagate_local_transactions: bool,
209}
210
211impl Default for LocalTransactionConfig {
212    fn default() -> Self {
213        Self {
214            no_exemptions: false,
215            local_addresses: HashSet::default(),
216            propagate_local_transactions: true,
217        }
218    }
219}
220
221impl LocalTransactionConfig {
222    /// Returns whether local transactions are not exempt from the configured limits.
223    #[inline]
224    pub const fn no_local_exemptions(&self) -> bool {
225        self.no_exemptions
226    }
227
228    /// Returns whether the local addresses vector contains the given address.
229    #[inline]
230    pub fn contains_local_address(&self, address: &Address) -> bool {
231        self.local_addresses.contains(address)
232    }
233
234    /// Returns whether the particular transaction should be considered local.
235    ///
236    /// This always returns false if the local exemptions are disabled.
237    #[inline]
238    pub fn is_local(&self, origin: TransactionOrigin, sender: &Address) -> bool {
239        if self.no_local_exemptions() {
240            return false
241        }
242        origin.is_local() || self.contains_local_address(sender)
243    }
244
245    /// Sets toggle to propagate transactions received locally by this client (e.g
246    /// transactions from `eth_sendTransaction` to this nodes' RPC server)
247    ///
248    /// If set to false, only transactions received by network peers (via
249    /// p2p) will be marked as propagated in the local transaction pool and returned on a
250    /// `GetPooledTransactions` p2p request
251    pub const fn set_propagate_local_transactions(mut self, propagate_local_txs: bool) -> Self {
252        self.propagate_local_transactions = propagate_local_txs;
253        self
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_pool_size_sanity() {
263        let pool_size = PoolSize {
264            pending: 0,
265            pending_size: 0,
266            basefee: 0,
267            basefee_size: 0,
268            queued: 0,
269            queued_size: 0,
270            blob: 0,
271            blob_size: 0,
272            ..Default::default()
273        };
274
275        // the current size is zero so this should not exceed any limits
276        let config = PoolConfig::default();
277        assert!(!config.is_exceeded(pool_size));
278
279        // set them to be above the limits
280        let pool_size = PoolSize {
281            pending: config.pending_limit.max_txs + 1,
282            pending_size: config.pending_limit.max_size + 1,
283            basefee: config.basefee_limit.max_txs + 1,
284            basefee_size: config.basefee_limit.max_size + 1,
285            queued: config.queued_limit.max_txs + 1,
286            queued_size: config.queued_limit.max_size + 1,
287            blob: config.blob_limit.max_txs + 1,
288            blob_size: config.blob_limit.max_size + 1,
289            ..Default::default()
290        };
291
292        // now this should be above the limits
293        assert!(config.is_exceeded(pool_size));
294    }
295
296    #[test]
297    fn test_default_config() {
298        let config = LocalTransactionConfig::default();
299
300        assert!(!config.no_exemptions);
301        assert!(config.local_addresses.is_empty());
302        assert!(config.propagate_local_transactions);
303    }
304
305    #[test]
306    fn test_no_local_exemptions() {
307        let config = LocalTransactionConfig { no_exemptions: true, ..Default::default() };
308        assert!(config.no_local_exemptions());
309    }
310
311    #[test]
312    fn test_contains_local_address() {
313        let address = Address::new([1; 20]);
314        let mut local_addresses = HashSet::default();
315        local_addresses.insert(address);
316
317        let config = LocalTransactionConfig { local_addresses, ..Default::default() };
318
319        // Should contain the inserted address
320        assert!(config.contains_local_address(&address));
321
322        // Should not contain another random address
323        assert!(!config.contains_local_address(&Address::new([2; 20])));
324    }
325
326    #[test]
327    fn test_is_local_with_no_exemptions() {
328        let address = Address::new([1; 20]);
329        let config = LocalTransactionConfig {
330            no_exemptions: true,
331            local_addresses: HashSet::default(),
332            ..Default::default()
333        };
334
335        // Should return false as no exemptions is set to true
336        assert!(!config.is_local(TransactionOrigin::Local, &address));
337    }
338
339    #[test]
340    fn test_is_local_without_no_exemptions() {
341        let address = Address::new([1; 20]);
342        let mut local_addresses = HashSet::default();
343        local_addresses.insert(address);
344
345        let config =
346            LocalTransactionConfig { no_exemptions: false, local_addresses, ..Default::default() };
347
348        // Should return true as the transaction origin is local
349        assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
350        assert!(config.is_local(TransactionOrigin::Local, &address));
351
352        // Should return true as the address is in the local_addresses set
353        assert!(config.is_local(TransactionOrigin::External, &address));
354        // Should return false as the address is not in the local_addresses set
355        assert!(!config.is_local(TransactionOrigin::External, &Address::new([2; 20])));
356    }
357
358    #[test]
359    fn test_set_propagate_local_transactions() {
360        let config = LocalTransactionConfig::default();
361        assert!(config.propagate_local_transactions);
362
363        let new_config = config.set_propagate_local_transactions(false);
364        assert!(!new_config.propagate_local_transactions);
365    }
366
367    #[test]
368    fn scale_pool_limit() {
369        let limit = SubPoolLimit::default();
370        let double = limit * 2;
371        assert_eq!(
372            double,
373            SubPoolLimit { max_txs: limit.max_txs * 2, max_size: limit.max_size * 2 }
374        )
375    }
376}