reth_transaction_pool/
config.rs

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