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
34pub const DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS: usize = 1;
36
37#[derive(Debug, Clone)]
39pub struct PoolConfig {
40 pub pending_limit: SubPoolLimit,
42 pub basefee_limit: SubPoolLimit,
44 pub queued_limit: SubPoolLimit,
46 pub blob_limit: SubPoolLimit,
48 pub blob_cache_size: Option<u32>,
50 pub max_account_slots: usize,
52 pub price_bumps: PriceBumpConfig,
54 pub minimal_protocol_basefee: u64,
56 pub minimum_priority_fee: Option<u128>,
58 pub gas_limit: u64,
60 pub local_transactions_config: LocalTransactionConfig,
63 pub pending_tx_listener_buffer_size: usize,
65 pub new_tx_listener_buffer_size: usize,
67 pub max_new_pending_txs_notifications: usize,
69 pub max_queued_lifetime: Duration,
71 pub max_inflight_delegated_slot_limit: usize,
75}
76
77impl PoolConfig {
78 pub const fn with_disabled_protocol_base_fee(self) -> Self {
82 self.with_protocol_base_fee(0)
83 }
84
85 pub const fn with_protocol_base_fee(mut self, protocol_base_fee: u64) -> Self {
90 self.minimal_protocol_basefee = protocol_base_fee;
91 self
92 }
93
94 pub const fn with_max_inflight_delegated_slots(
96 mut self,
97 max_inflight_delegation_limit: usize,
98 ) -> Self {
99 self.max_inflight_delegated_slot_limit = max_inflight_delegation_limit;
100 self
101 }
102
103 #[inline]
105 pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool {
106 self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) ||
107 self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) ||
108 self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) ||
109 self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size)
110 }
111}
112
113impl Default for PoolConfig {
114 fn default() -> Self {
115 Self {
116 pending_limit: Default::default(),
117 basefee_limit: Default::default(),
118 queued_limit: Default::default(),
119 blob_limit: Default::default(),
120 blob_cache_size: None,
121 max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
122 price_bumps: Default::default(),
123 minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
124 minimum_priority_fee: None,
125 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
126 local_transactions_config: Default::default(),
127 pending_tx_listener_buffer_size: PENDING_TX_LISTENER_BUFFER_SIZE,
128 new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE,
129 max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS,
130 max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME,
131 max_inflight_delegated_slot_limit: DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct SubPoolLimit {
139 pub max_txs: usize,
141 pub max_size: usize,
143}
144
145impl SubPoolLimit {
146 pub const fn new(max_txs: usize, max_size: usize) -> Self {
148 Self { max_txs, max_size }
149 }
150
151 pub const fn max() -> Self {
153 Self::new(usize::MAX, usize::MAX)
154 }
155
156 #[inline]
158 pub const fn is_exceeded(&self, txs: usize, size: usize) -> bool {
159 self.max_txs < txs || self.max_size < size
160 }
161
162 pub const fn tx_excess(&self, txs: usize) -> Option<usize> {
164 txs.checked_sub(self.max_txs)
165 }
166}
167
168impl Mul<usize> for SubPoolLimit {
169 type Output = Self;
170
171 fn mul(self, rhs: usize) -> Self::Output {
172 let Self { max_txs, max_size } = self;
173 Self { max_txs: max_txs * rhs, max_size: max_size * rhs }
174 }
175}
176
177impl Default for SubPoolLimit {
178 fn default() -> Self {
179 Self {
181 max_txs: TXPOOL_SUBPOOL_MAX_TXS_DEFAULT,
182 max_size: TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT * 1024 * 1024,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, Eq, PartialEq)]
189pub struct PriceBumpConfig {
190 pub default_price_bump: u128,
192 pub replace_blob_tx_price_bump: u128,
194}
195
196impl PriceBumpConfig {
197 #[inline]
199 pub const fn price_bump(&self, tx_type: u8) -> u128 {
200 if tx_type == EIP4844_TX_TYPE_ID {
201 return self.replace_blob_tx_price_bump
202 }
203 self.default_price_bump
204 }
205}
206
207impl Default for PriceBumpConfig {
208 fn default() -> Self {
209 Self {
210 default_price_bump: DEFAULT_PRICE_BUMP,
211 replace_blob_tx_price_bump: REPLACE_BLOB_PRICE_BUMP,
212 }
213 }
214}
215
216#[derive(Debug, Clone, Eq, PartialEq)]
219pub struct LocalTransactionConfig {
220 pub no_exemptions: bool,
227 pub local_addresses: HashSet<Address>,
229 pub propagate_local_transactions: bool,
231}
232
233impl Default for LocalTransactionConfig {
234 fn default() -> Self {
235 Self {
236 no_exemptions: false,
237 local_addresses: HashSet::default(),
238 propagate_local_transactions: true,
239 }
240 }
241}
242
243impl LocalTransactionConfig {
244 #[inline]
246 pub const fn no_local_exemptions(&self) -> bool {
247 self.no_exemptions
248 }
249
250 #[inline]
252 pub fn contains_local_address(&self, address: &Address) -> bool {
253 self.local_addresses.contains(address)
254 }
255
256 #[inline]
260 pub fn is_local(&self, origin: TransactionOrigin, sender: &Address) -> bool {
261 if self.no_local_exemptions() {
262 return false
263 }
264 origin.is_local() || self.contains_local_address(sender)
265 }
266
267 pub const fn set_propagate_local_transactions(mut self, propagate_local_txs: bool) -> Self {
274 self.propagate_local_transactions = propagate_local_txs;
275 self
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_pool_size_sanity() {
285 let pool_size = PoolSize {
286 pending: 0,
287 pending_size: 0,
288 basefee: 0,
289 basefee_size: 0,
290 queued: 0,
291 queued_size: 0,
292 blob: 0,
293 blob_size: 0,
294 ..Default::default()
295 };
296
297 let config = PoolConfig::default();
299 assert!(!config.is_exceeded(pool_size));
300
301 let pool_size = PoolSize {
303 pending: config.pending_limit.max_txs + 1,
304 pending_size: config.pending_limit.max_size + 1,
305 basefee: config.basefee_limit.max_txs + 1,
306 basefee_size: config.basefee_limit.max_size + 1,
307 queued: config.queued_limit.max_txs + 1,
308 queued_size: config.queued_limit.max_size + 1,
309 blob: config.blob_limit.max_txs + 1,
310 blob_size: config.blob_limit.max_size + 1,
311 ..Default::default()
312 };
313
314 assert!(config.is_exceeded(pool_size));
316 }
317
318 #[test]
319 fn test_default_config() {
320 let config = LocalTransactionConfig::default();
321
322 assert!(!config.no_exemptions);
323 assert!(config.local_addresses.is_empty());
324 assert!(config.propagate_local_transactions);
325 }
326
327 #[test]
328 fn test_no_local_exemptions() {
329 let config = LocalTransactionConfig { no_exemptions: true, ..Default::default() };
330 assert!(config.no_local_exemptions());
331 }
332
333 #[test]
334 fn test_contains_local_address() {
335 let address = Address::new([1; 20]);
336 let mut local_addresses = HashSet::default();
337 local_addresses.insert(address);
338
339 let config = LocalTransactionConfig { local_addresses, ..Default::default() };
340
341 assert!(config.contains_local_address(&address));
343
344 assert!(!config.contains_local_address(&Address::new([2; 20])));
346 }
347
348 #[test]
349 fn test_is_local_with_no_exemptions() {
350 let address = Address::new([1; 20]);
351 let config = LocalTransactionConfig {
352 no_exemptions: true,
353 local_addresses: HashSet::default(),
354 ..Default::default()
355 };
356
357 assert!(!config.is_local(TransactionOrigin::Local, &address));
359 }
360
361 #[test]
362 fn test_is_local_without_no_exemptions() {
363 let address = Address::new([1; 20]);
364 let mut local_addresses = HashSet::default();
365 local_addresses.insert(address);
366
367 let config =
368 LocalTransactionConfig { no_exemptions: false, local_addresses, ..Default::default() };
369
370 assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
372 assert!(config.is_local(TransactionOrigin::Local, &address));
373
374 assert!(config.is_local(TransactionOrigin::External, &address));
376 assert!(!config.is_local(TransactionOrigin::External, &Address::new([2; 20])));
378 }
379
380 #[test]
381 fn test_set_propagate_local_transactions() {
382 let config = LocalTransactionConfig::default();
383 assert!(config.propagate_local_transactions);
384
385 let new_config = config.set_propagate_local_transactions(false);
386 assert!(!new_config.propagate_local_transactions);
387 }
388
389 #[test]
390 fn scale_pool_limit() {
391 let limit = SubPoolLimit::default();
392 let double = limit * 2;
393 assert_eq!(
394 double,
395 SubPoolLimit { max_txs: limit.max_txs * 2, max_size: limit.max_size * 2 }
396 )
397 }
398}