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 blob_cache_size: Option<u32>,
47 pub max_account_slots: usize,
49 pub price_bumps: PriceBumpConfig,
51 pub minimal_protocol_basefee: u64,
53 pub gas_limit: u64,
55 pub local_transactions_config: LocalTransactionConfig,
58 pub pending_tx_listener_buffer_size: usize,
60 pub new_tx_listener_buffer_size: usize,
62 pub max_new_pending_txs_notifications: usize,
64 pub max_queued_lifetime: Duration,
66}
67
68impl PoolConfig {
69 #[inline]
71 pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool {
72 self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) ||
73 self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) ||
74 self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) ||
75 self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size)
76 }
77}
78
79impl Default for PoolConfig {
80 fn default() -> Self {
81 Self {
82 pending_limit: Default::default(),
83 basefee_limit: Default::default(),
84 queued_limit: Default::default(),
85 blob_limit: Default::default(),
86 blob_cache_size: None,
87 max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
88 price_bumps: Default::default(),
89 minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
90 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
91 local_transactions_config: Default::default(),
92 pending_tx_listener_buffer_size: PENDING_TX_LISTENER_BUFFER_SIZE,
93 new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE,
94 max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS,
95 max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct SubPoolLimit {
103 pub max_txs: usize,
105 pub max_size: usize,
107}
108
109impl SubPoolLimit {
110 pub const fn new(max_txs: usize, max_size: usize) -> Self {
112 Self { max_txs, max_size }
113 }
114
115 #[inline]
117 pub const fn is_exceeded(&self, txs: usize, size: usize) -> bool {
118 self.max_txs < txs || self.max_size < size
119 }
120}
121
122impl Mul<usize> for SubPoolLimit {
123 type Output = Self;
124
125 fn mul(self, rhs: usize) -> Self::Output {
126 let Self { max_txs, max_size } = self;
127 Self { max_txs: max_txs * rhs, max_size: max_size * rhs }
128 }
129}
130
131impl Default for SubPoolLimit {
132 fn default() -> Self {
133 Self {
135 max_txs: TXPOOL_SUBPOOL_MAX_TXS_DEFAULT,
136 max_size: TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT * 1024 * 1024,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, Eq, PartialEq)]
143pub struct PriceBumpConfig {
144 pub default_price_bump: u128,
146 pub replace_blob_tx_price_bump: u128,
148}
149
150impl PriceBumpConfig {
151 #[inline]
153 pub const fn price_bump(&self, tx_type: u8) -> u128 {
154 if tx_type == EIP4844_TX_TYPE_ID {
155 return self.replace_blob_tx_price_bump
156 }
157 self.default_price_bump
158 }
159}
160
161impl Default for PriceBumpConfig {
162 fn default() -> Self {
163 Self {
164 default_price_bump: DEFAULT_PRICE_BUMP,
165 replace_blob_tx_price_bump: REPLACE_BLOB_PRICE_BUMP,
166 }
167 }
168}
169
170#[derive(Debug, Clone, Eq, PartialEq)]
173pub struct LocalTransactionConfig {
174 pub no_exemptions: bool,
181 pub local_addresses: HashSet<Address>,
183 pub propagate_local_transactions: bool,
185}
186
187impl Default for LocalTransactionConfig {
188 fn default() -> Self {
189 Self {
190 no_exemptions: false,
191 local_addresses: HashSet::default(),
192 propagate_local_transactions: true,
193 }
194 }
195}
196
197impl LocalTransactionConfig {
198 #[inline]
200 pub const fn no_local_exemptions(&self) -> bool {
201 self.no_exemptions
202 }
203
204 #[inline]
206 pub fn contains_local_address(&self, address: &Address) -> bool {
207 self.local_addresses.contains(address)
208 }
209
210 #[inline]
214 pub fn is_local(&self, origin: TransactionOrigin, sender: &Address) -> bool {
215 if self.no_local_exemptions() {
216 return false
217 }
218 origin.is_local() || self.contains_local_address(sender)
219 }
220
221 pub const fn set_propagate_local_transactions(mut self, propagate_local_txs: bool) -> Self {
228 self.propagate_local_transactions = propagate_local_txs;
229 self
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_pool_size_sanity() {
239 let pool_size = PoolSize {
240 pending: 0,
241 pending_size: 0,
242 basefee: 0,
243 basefee_size: 0,
244 queued: 0,
245 queued_size: 0,
246 blob: 0,
247 blob_size: 0,
248 ..Default::default()
249 };
250
251 let config = PoolConfig::default();
253 assert!(!config.is_exceeded(pool_size));
254
255 let pool_size = PoolSize {
257 pending: config.pending_limit.max_txs + 1,
258 pending_size: config.pending_limit.max_size + 1,
259 basefee: config.basefee_limit.max_txs + 1,
260 basefee_size: config.basefee_limit.max_size + 1,
261 queued: config.queued_limit.max_txs + 1,
262 queued_size: config.queued_limit.max_size + 1,
263 blob: config.blob_limit.max_txs + 1,
264 blob_size: config.blob_limit.max_size + 1,
265 ..Default::default()
266 };
267
268 assert!(config.is_exceeded(pool_size));
270 }
271
272 #[test]
273 fn test_default_config() {
274 let config = LocalTransactionConfig::default();
275
276 assert!(!config.no_exemptions);
277 assert!(config.local_addresses.is_empty());
278 assert!(config.propagate_local_transactions);
279 }
280
281 #[test]
282 fn test_no_local_exemptions() {
283 let config = LocalTransactionConfig { no_exemptions: true, ..Default::default() };
284 assert!(config.no_local_exemptions());
285 }
286
287 #[test]
288 fn test_contains_local_address() {
289 let address = Address::new([1; 20]);
290 let mut local_addresses = HashSet::default();
291 local_addresses.insert(address);
292
293 let config = LocalTransactionConfig { local_addresses, ..Default::default() };
294
295 assert!(config.contains_local_address(&address));
297
298 assert!(!config.contains_local_address(&Address::new([2; 20])));
300 }
301
302 #[test]
303 fn test_is_local_with_no_exemptions() {
304 let address = Address::new([1; 20]);
305 let config = LocalTransactionConfig {
306 no_exemptions: true,
307 local_addresses: HashSet::default(),
308 ..Default::default()
309 };
310
311 assert!(!config.is_local(TransactionOrigin::Local, &address));
313 }
314
315 #[test]
316 fn test_is_local_without_no_exemptions() {
317 let address = Address::new([1; 20]);
318 let mut local_addresses = HashSet::default();
319 local_addresses.insert(address);
320
321 let config =
322 LocalTransactionConfig { no_exemptions: false, local_addresses, ..Default::default() };
323
324 assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
326 assert!(config.is_local(TransactionOrigin::Local, &address));
327
328 assert!(config.is_local(TransactionOrigin::External, &address));
330 assert!(!config.is_local(TransactionOrigin::External, &Address::new([2; 20])));
332 }
333
334 #[test]
335 fn test_set_propagate_local_transactions() {
336 let config = LocalTransactionConfig::default();
337 assert!(config.propagate_local_transactions);
338
339 let new_config = config.set_propagate_local_transactions(false);
340 assert!(!new_config.propagate_local_transactions);
341 }
342
343 #[test]
344 fn scale_pool_limit() {
345 let limit = SubPoolLimit::default();
346 let double = limit * 2;
347 assert_eq!(
348 double,
349 SubPoolLimit { max_txs: limit.max_txs * 2, max_size: limit.max_size * 2 }
350 )
351 }
352}