diff --git a/crates/node/core/src/args/txpool.rs b/crates/node/core/src/args/txpool.rs index 03eb5d6c0aa..2ab604be168 100644 --- a/crates/node/core/src/args/txpool.rs +++ b/crates/node/core/src/args/txpool.rs @@ -230,6 +230,7 @@ impl RethTransactionPoolConfig for TxPoolArgs { new_tx_listener_buffer_size: self.new_tx_listener_buffer_size, max_new_pending_txs_notifications: self.max_new_pending_txs_notifications, max_queued_lifetime: self.max_queued_lifetime, + ..Default::default() } } diff --git a/crates/transaction-pool/src/config.rs b/crates/transaction-pool/src/config.rs index 558666988db..c6fb4ecc88b 100644 --- a/crates/transaction-pool/src/config.rs +++ b/crates/transaction-pool/src/config.rs @@ -31,6 +31,9 @@ pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100; /// Default maximum new transactions for broadcasting. pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200; +/// Default maximum allowed in flight delegated transactions per account. +pub const DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS: usize = 1; + /// Configuration options for the Transaction pool. #[derive(Debug, Clone)] pub struct PoolConfig { @@ -65,6 +68,10 @@ pub struct PoolConfig { pub max_new_pending_txs_notifications: usize, /// Maximum lifetime for transactions in the pool pub max_queued_lifetime: Duration, + /// The maximum allowed inflight transactions a delegated sender can have. + /// + /// This restricts how many executable transaction a delegated sender can stack. + pub max_inflight_delegated_slot_limit: usize, } impl PoolConfig { @@ -84,6 +91,15 @@ impl PoolConfig { self } + /// Configures how many slots are available for a delegated sender. + pub const fn with_max_inflight_delegated_slots( + mut self, + max_inflight_delegation_limit: usize, + ) -> Self { + self.max_inflight_delegated_slot_limit = max_inflight_delegation_limit; + self + } + /// Returns whether the size and amount constraints in any sub-pools are exceeded. #[inline] pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool { @@ -112,6 +128,7 @@ impl Default for PoolConfig { new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE, max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS, max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME, + max_inflight_delegated_slot_limit: DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS, } } } diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 2485ae97706..5220a03c347 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -274,7 +274,8 @@ pub use crate::{ batcher::{BatchTxProcessor, BatchTxRequest}, blobstore::{BlobStore, BlobStoreError}, config::{ - LocalTransactionConfig, PoolConfig, PriceBumpConfig, SubPoolLimit, DEFAULT_PRICE_BUMP, + LocalTransactionConfig, PoolConfig, PriceBumpConfig, SubPoolLimit, + DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS, DEFAULT_PRICE_BUMP, DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS, MAX_NEW_PENDING_TXS_NOTIFICATIONS, REPLACE_BLOB_PRICE_BUMP, TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER, TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT, TXPOOL_SUBPOOL_MAX_TXS_DEFAULT, diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 5c89f73949e..d21e4f72ddb 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -771,8 +771,8 @@ impl TxPool { } /// Determines if the tx sender is delegated or has a pending delegation, and if so, ensures - /// they have at most one in-flight **executable** transaction, e.g. disallow stacked and - /// nonce-gapped transactions from the account. + /// they have at most one configured amount of in-flight **executable** transactions (default at + /// most one), e.g. disallow stacked and nonce-gapped transactions from the account. fn check_delegation_limit( &self, transaction: &ValidPoolTransaction, @@ -802,8 +802,17 @@ impl TxPool { return Ok(()) } - if txs_by_sender.any(|id| id == &transaction.transaction_id) { - // Transaction replacement is supported + let mut count = 0; + for id in txs_by_sender { + if id == &transaction.transaction_id { + // Transaction replacement is supported + return Ok(()) + } + count += 1; + } + + if count < self.config.max_inflight_delegated_slot_limit { + // account still has an available slot return Ok(()) } @@ -818,8 +827,9 @@ impl TxPool { /// This verifies that the transaction complies with code authorization /// restrictions brought by EIP-7702 transaction type: /// 1. Any account with a deployed delegation or an in-flight authorization to deploy a - /// delegation will only be allowed a single transaction slot instead of the standard limit. - /// This is due to the possibility of the account being sweeped by an unrelated account. + /// delegation will only be allowed a certain amount of transaction slots (default 1) instead + /// of the standard limit. This is due to the possibility of the account being sweeped by an + /// unrelated account. /// 2. In case the pool is tracking a pending / queued transaction from a specific account, at /// most one in-flight transaction is allowed; any additional delegated transactions from /// that account will be rejected. @@ -829,12 +839,12 @@ impl TxPool { on_chain_nonce: u64, on_chain_code_hash: Option, ) -> Result<(), PoolError> { - // Allow at most one in-flight tx for delegated accounts or those with a - // pending authorization. + // Ensure in-flight limit for delegated accounts or those with a pending authorization. self.check_delegation_limit(transaction, on_chain_nonce, on_chain_code_hash)?; if let Some(authority_list) = &transaction.authority_ids { for sender_id in authority_list { + // Ensure authority has at most 1 inflight transaction. if self.all_transactions.txs_iter(*sender_id).nth(1).is_some() { return Err(PoolError::new( *transaction.hash(),