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
10pub const TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER: usize = 16;
12
13pub const TXPOOL_SUBPOOL_MAX_TXS_DEFAULT: usize = 10_000;
15
16pub const TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT: usize = 20;
18
19pub const DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS: usize = 1;
21
22pub const DEFAULT_PRICE_BUMP: u128 = 10;
24
25pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100;
29
30pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200;
32
33#[derive(Debug, Clone)]
35pub struct PoolConfig {
36 pub pending_limit: SubPoolLimit,
38 pub basefee_limit: SubPoolLimit,
40 pub queued_limit: SubPoolLimit,
42 pub blob_limit: SubPoolLimit,
44 pub max_account_slots: usize,
46 pub price_bumps: PriceBumpConfig,
48 pub minimal_protocol_basefee: u64,
50 pub gas_limit: u64,
52 pub local_transactions_config: LocalTransactionConfig,
55 pub pending_tx_listener_buffer_size: usize,
57 pub new_tx_listener_buffer_size: usize,
59 pub max_new_pending_txs_notifications: usize,
61}
62
63impl PoolConfig {
64 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct SubPoolLimit {
96 pub max_txs: usize,
98 pub max_size: usize,
100}
101
102impl SubPoolLimit {
103 pub const fn new(max_txs: usize, max_size: usize) -> Self {
105 Self { max_txs, max_size }
106 }
107
108 #[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 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#[derive(Debug, Clone, Copy, Eq, PartialEq)]
136pub struct PriceBumpConfig {
137 pub default_price_bump: u128,
139 pub replace_blob_tx_price_bump: u128,
141}
142
143impl PriceBumpConfig {
144 #[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#[derive(Debug, Clone, Eq, PartialEq)]
166pub struct LocalTransactionConfig {
167 pub no_exemptions: bool,
174 pub local_addresses: HashSet<Address>,
176 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 #[inline]
193 pub const fn no_local_exemptions(&self) -> bool {
194 self.no_exemptions
195 }
196
197 #[inline]
199 pub fn contains_local_address(&self, address: &Address) -> bool {
200 self.local_addresses.contains(address)
201 }
202
203 #[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 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 let config = PoolConfig::default();
246 assert!(!config.is_exceeded(pool_size));
247
248 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 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 assert!(config.contains_local_address(&address));
290
291 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 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 assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
319 assert!(config.is_local(TransactionOrigin::Local, &address));
320
321 assert!(config.is_local(TransactionOrigin::External, &address));
323 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}