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
11pub const TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER: usize = 16;
13
14pub const TXPOOL_SUBPOOL_MAX_TXS_DEFAULT: usize = 10_000;
16
17pub const TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT: usize = 20;
19
20pub const DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS: usize = 1;
22
23pub const DEFAULT_PRICE_BUMP: u128 = 10;
25
26pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100;
30
31pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200;
33
34#[derive(Debug, Clone)]
36pub struct PoolConfig {
37 pub pending_limit: SubPoolLimit,
39 pub basefee_limit: SubPoolLimit,
41 pub queued_limit: SubPoolLimit,
43 pub blob_limit: SubPoolLimit,
45 pub max_account_slots: usize,
47 pub price_bumps: PriceBumpConfig,
49 pub minimal_protocol_basefee: u64,
51 pub gas_limit: u64,
53 pub local_transactions_config: LocalTransactionConfig,
56 pub pending_tx_listener_buffer_size: usize,
58 pub new_tx_listener_buffer_size: usize,
60 pub max_new_pending_txs_notifications: usize,
62 pub max_queued_lifetime: Duration,
64}
65
66impl PoolConfig {
67 #[inline]
69 pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool {
70 self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) ||
71 self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) ||
72 self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) ||
73 self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size)
74 }
75}
76
77impl Default for PoolConfig {
78 fn default() -> Self {
79 Self {
80 pending_limit: Default::default(),
81 basefee_limit: Default::default(),
82 queued_limit: Default::default(),
83 blob_limit: Default::default(),
84 max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
85 price_bumps: Default::default(),
86 minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
87 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
88 local_transactions_config: Default::default(),
89 pending_tx_listener_buffer_size: PENDING_TX_LISTENER_BUFFER_SIZE,
90 new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE,
91 max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS,
92 max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME,
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct SubPoolLimit {
100 pub max_txs: usize,
102 pub max_size: usize,
104}
105
106impl SubPoolLimit {
107 pub const fn new(max_txs: usize, max_size: usize) -> Self {
109 Self { max_txs, max_size }
110 }
111
112 #[inline]
114 pub const fn is_exceeded(&self, txs: usize, size: usize) -> bool {
115 self.max_txs < txs || self.max_size < size
116 }
117}
118
119impl Mul<usize> for SubPoolLimit {
120 type Output = Self;
121
122 fn mul(self, rhs: usize) -> Self::Output {
123 let Self { max_txs, max_size } = self;
124 Self { max_txs: max_txs * rhs, max_size: max_size * rhs }
125 }
126}
127
128impl Default for SubPoolLimit {
129 fn default() -> Self {
130 Self {
132 max_txs: TXPOOL_SUBPOOL_MAX_TXS_DEFAULT,
133 max_size: TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT * 1024 * 1024,
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, Eq, PartialEq)]
140pub struct PriceBumpConfig {
141 pub default_price_bump: u128,
143 pub replace_blob_tx_price_bump: u128,
145}
146
147impl PriceBumpConfig {
148 #[inline]
150 pub(crate) const fn price_bump(&self, tx_type: u8) -> u128 {
151 if tx_type == EIP4844_TX_TYPE_ID {
152 return self.replace_blob_tx_price_bump
153 }
154 self.default_price_bump
155 }
156}
157
158impl Default for PriceBumpConfig {
159 fn default() -> Self {
160 Self {
161 default_price_bump: DEFAULT_PRICE_BUMP,
162 replace_blob_tx_price_bump: REPLACE_BLOB_PRICE_BUMP,
163 }
164 }
165}
166
167#[derive(Debug, Clone, Eq, PartialEq)]
170pub struct LocalTransactionConfig {
171 pub no_exemptions: bool,
178 pub local_addresses: HashSet<Address>,
180 pub propagate_local_transactions: bool,
182}
183
184impl Default for LocalTransactionConfig {
185 fn default() -> Self {
186 Self {
187 no_exemptions: false,
188 local_addresses: HashSet::default(),
189 propagate_local_transactions: true,
190 }
191 }
192}
193
194impl LocalTransactionConfig {
195 #[inline]
197 pub const fn no_local_exemptions(&self) -> bool {
198 self.no_exemptions
199 }
200
201 #[inline]
203 pub fn contains_local_address(&self, address: &Address) -> bool {
204 self.local_addresses.contains(address)
205 }
206
207 #[inline]
211 pub fn is_local(&self, origin: TransactionOrigin, sender: &Address) -> bool {
212 if self.no_local_exemptions() {
213 return false
214 }
215 origin.is_local() || self.contains_local_address(sender)
216 }
217
218 pub const fn set_propagate_local_transactions(mut self, propagate_local_txs: bool) -> Self {
225 self.propagate_local_transactions = propagate_local_txs;
226 self
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_pool_size_sanity() {
236 let pool_size = PoolSize {
237 pending: 0,
238 pending_size: 0,
239 basefee: 0,
240 basefee_size: 0,
241 queued: 0,
242 queued_size: 0,
243 blob: 0,
244 blob_size: 0,
245 ..Default::default()
246 };
247
248 let config = PoolConfig::default();
250 assert!(!config.is_exceeded(pool_size));
251
252 let pool_size = PoolSize {
254 pending: config.pending_limit.max_txs + 1,
255 pending_size: config.pending_limit.max_size + 1,
256 basefee: config.basefee_limit.max_txs + 1,
257 basefee_size: config.basefee_limit.max_size + 1,
258 queued: config.queued_limit.max_txs + 1,
259 queued_size: config.queued_limit.max_size + 1,
260 blob: config.blob_limit.max_txs + 1,
261 blob_size: config.blob_limit.max_size + 1,
262 ..Default::default()
263 };
264
265 assert!(config.is_exceeded(pool_size));
267 }
268
269 #[test]
270 fn test_default_config() {
271 let config = LocalTransactionConfig::default();
272
273 assert!(!config.no_exemptions);
274 assert!(config.local_addresses.is_empty());
275 assert!(config.propagate_local_transactions);
276 }
277
278 #[test]
279 fn test_no_local_exemptions() {
280 let config = LocalTransactionConfig { no_exemptions: true, ..Default::default() };
281 assert!(config.no_local_exemptions());
282 }
283
284 #[test]
285 fn test_contains_local_address() {
286 let address = Address::new([1; 20]);
287 let mut local_addresses = HashSet::default();
288 local_addresses.insert(address);
289
290 let config = LocalTransactionConfig { local_addresses, ..Default::default() };
291
292 assert!(config.contains_local_address(&address));
294
295 assert!(!config.contains_local_address(&Address::new([2; 20])));
297 }
298
299 #[test]
300 fn test_is_local_with_no_exemptions() {
301 let address = Address::new([1; 20]);
302 let config = LocalTransactionConfig {
303 no_exemptions: true,
304 local_addresses: HashSet::default(),
305 ..Default::default()
306 };
307
308 assert!(!config.is_local(TransactionOrigin::Local, &address));
310 }
311
312 #[test]
313 fn test_is_local_without_no_exemptions() {
314 let address = Address::new([1; 20]);
315 let mut local_addresses = HashSet::default();
316 local_addresses.insert(address);
317
318 let config =
319 LocalTransactionConfig { no_exemptions: false, local_addresses, ..Default::default() };
320
321 assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
323 assert!(config.is_local(TransactionOrigin::Local, &address));
324
325 assert!(config.is_local(TransactionOrigin::External, &address));
327 assert!(!config.is_local(TransactionOrigin::External, &Address::new([2; 20])));
329 }
330
331 #[test]
332 fn test_set_propagate_local_transactions() {
333 let config = LocalTransactionConfig::default();
334 assert!(config.propagate_local_transactions);
335
336 let new_config = config.set_propagate_local_transactions(false);
337 assert!(!new_config.propagate_local_transactions);
338 }
339
340 #[test]
341 fn scale_pool_limit() {
342 let limit = SubPoolLimit::default();
343 let double = limit * 2;
344 assert_eq!(
345 double,
346 SubPoolLimit { max_txs: limit.max_txs * 2, max_size: limit.max_size * 2 }
347 )
348 }
349}