use crate::{
pool::{NEW_TX_LISTENER_BUFFER_SIZE, PENDING_TX_LISTENER_BUFFER_SIZE},
PoolSize, TransactionOrigin,
};
use alloy_consensus::constants::EIP4844_TX_TYPE_ID;
use alloy_eips::eip1559::{ETHEREUM_BLOCK_GAS_LIMIT, MIN_PROTOCOL_BASE_FEE};
use alloy_primitives::Address;
use std::{collections::HashSet, ops::Mul};
pub const TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER: usize = 16;
pub const TXPOOL_SUBPOOL_MAX_TXS_DEFAULT: usize = 10_000;
pub const TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT: usize = 20;
pub const DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS: usize = 1;
pub const DEFAULT_PRICE_BUMP: u128 = 10;
pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100;
pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200;
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub pending_limit: SubPoolLimit,
pub basefee_limit: SubPoolLimit,
pub queued_limit: SubPoolLimit,
pub blob_limit: SubPoolLimit,
pub max_account_slots: usize,
pub price_bumps: PriceBumpConfig,
pub minimal_protocol_basefee: u64,
pub gas_limit: u64,
pub local_transactions_config: LocalTransactionConfig,
pub pending_tx_listener_buffer_size: usize,
pub new_tx_listener_buffer_size: usize,
pub max_new_pending_txs_notifications: usize,
}
impl PoolConfig {
#[inline]
pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool {
self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) ||
self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) ||
self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) ||
self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size)
}
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
pending_limit: Default::default(),
basefee_limit: Default::default(),
queued_limit: Default::default(),
blob_limit: Default::default(),
max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
price_bumps: Default::default(),
minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
gas_limit: ETHEREUM_BLOCK_GAS_LIMIT,
local_transactions_config: Default::default(),
pending_tx_listener_buffer_size: PENDING_TX_LISTENER_BUFFER_SIZE,
new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE,
max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubPoolLimit {
pub max_txs: usize,
pub max_size: usize,
}
impl SubPoolLimit {
pub const fn new(max_txs: usize, max_size: usize) -> Self {
Self { max_txs, max_size }
}
#[inline]
pub const fn is_exceeded(&self, txs: usize, size: usize) -> bool {
self.max_txs < txs || self.max_size < size
}
}
impl Mul<usize> for SubPoolLimit {
type Output = Self;
fn mul(self, rhs: usize) -> Self::Output {
let Self { max_txs, max_size } = self;
Self { max_txs: max_txs * rhs, max_size: max_size * rhs }
}
}
impl Default for SubPoolLimit {
fn default() -> Self {
Self {
max_txs: TXPOOL_SUBPOOL_MAX_TXS_DEFAULT,
max_size: TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT * 1024 * 1024,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PriceBumpConfig {
pub default_price_bump: u128,
pub replace_blob_tx_price_bump: u128,
}
impl PriceBumpConfig {
#[inline]
pub(crate) const fn price_bump(&self, tx_type: u8) -> u128 {
if tx_type == EIP4844_TX_TYPE_ID {
return self.replace_blob_tx_price_bump
}
self.default_price_bump
}
}
impl Default for PriceBumpConfig {
fn default() -> Self {
Self {
default_price_bump: DEFAULT_PRICE_BUMP,
replace_blob_tx_price_bump: REPLACE_BLOB_PRICE_BUMP,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LocalTransactionConfig {
pub no_exemptions: bool,
pub local_addresses: HashSet<Address>,
pub propagate_local_transactions: bool,
}
impl Default for LocalTransactionConfig {
fn default() -> Self {
Self {
no_exemptions: false,
local_addresses: HashSet::default(),
propagate_local_transactions: true,
}
}
}
impl LocalTransactionConfig {
#[inline]
pub const fn no_local_exemptions(&self) -> bool {
self.no_exemptions
}
#[inline]
pub fn contains_local_address(&self, address: &Address) -> bool {
self.local_addresses.contains(address)
}
#[inline]
pub fn is_local(&self, origin: TransactionOrigin, sender: &Address) -> bool {
if self.no_local_exemptions() {
return false
}
origin.is_local() || self.contains_local_address(sender)
}
pub const fn set_propagate_local_transactions(mut self, propagate_local_txs: bool) -> Self {
self.propagate_local_transactions = propagate_local_txs;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_size_sanity() {
let pool_size = PoolSize {
pending: 0,
pending_size: 0,
basefee: 0,
basefee_size: 0,
queued: 0,
queued_size: 0,
blob: 0,
blob_size: 0,
..Default::default()
};
let config = PoolConfig::default();
assert!(!config.is_exceeded(pool_size));
let pool_size = PoolSize {
pending: config.pending_limit.max_txs + 1,
pending_size: config.pending_limit.max_size + 1,
basefee: config.basefee_limit.max_txs + 1,
basefee_size: config.basefee_limit.max_size + 1,
queued: config.queued_limit.max_txs + 1,
queued_size: config.queued_limit.max_size + 1,
blob: config.blob_limit.max_txs + 1,
blob_size: config.blob_limit.max_size + 1,
..Default::default()
};
assert!(config.is_exceeded(pool_size));
}
#[test]
fn test_default_config() {
let config = LocalTransactionConfig::default();
assert!(!config.no_exemptions);
assert!(config.local_addresses.is_empty());
assert!(config.propagate_local_transactions);
}
#[test]
fn test_no_local_exemptions() {
let config = LocalTransactionConfig { no_exemptions: true, ..Default::default() };
assert!(config.no_local_exemptions());
}
#[test]
fn test_contains_local_address() {
let address = Address::new([1; 20]);
let mut local_addresses = HashSet::default();
local_addresses.insert(address);
let config = LocalTransactionConfig { local_addresses, ..Default::default() };
assert!(config.contains_local_address(&address));
assert!(!config.contains_local_address(&Address::new([2; 20])));
}
#[test]
fn test_is_local_with_no_exemptions() {
let address = Address::new([1; 20]);
let config = LocalTransactionConfig {
no_exemptions: true,
local_addresses: HashSet::default(),
..Default::default()
};
assert!(!config.is_local(TransactionOrigin::Local, &address));
}
#[test]
fn test_is_local_without_no_exemptions() {
let address = Address::new([1; 20]);
let mut local_addresses = HashSet::default();
local_addresses.insert(address);
let config =
LocalTransactionConfig { no_exemptions: false, local_addresses, ..Default::default() };
assert!(config.is_local(TransactionOrigin::Local, &Address::new([2; 20])));
assert!(config.is_local(TransactionOrigin::Local, &address));
assert!(config.is_local(TransactionOrigin::External, &address));
assert!(!config.is_local(TransactionOrigin::External, &Address::new([2; 20])));
}
#[test]
fn test_set_propagate_local_transactions() {
let config = LocalTransactionConfig::default();
assert!(config.propagate_local_transactions);
let new_config = config.set_propagate_local_transactions(false);
assert!(!new_config.propagate_local_transactions);
}
#[test]
fn scale_pool_limit() {
let limit = SubPoolLimit::default();
let double = limit * 2;
assert_eq!(
double,
SubPoolLimit { max_txs: limit.max_txs * 2, max_size: limit.max_size * 2 }
)
}
}