diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index fbaa91698..e8c0c0c7a 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -346,6 +346,7 @@ impl From for RpcPoolError { PoolError::DiscardedOnInsert(_) => RpcPoolError::TxPoolOverflow, PoolError::InvalidTransaction(_, err) => err.into(), PoolError::Other(_, err) => RpcPoolError::Other(err), + PoolError::AlreadyImported(_) => RpcPoolError::AlreadyKnown, } } } diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index 82b5832eb..be74f9986 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -8,6 +8,9 @@ pub type PoolResult = Result; /// All errors the Transaction pool can throw. #[derive(Debug, thiserror::Error)] pub enum PoolError { + /// Same transaction already imported + #[error("[{0:?}] Already imported")] + AlreadyImported(TxHash), /// Thrown if a replacement transaction's gas price is below the already imported transaction #[error("[{0:?}]: insufficient gas price to replace existing transaction.")] ReplacementUnderpriced(TxHash), @@ -36,6 +39,7 @@ impl PoolError { /// Returns the hash of the transaction that resulted in this error. pub fn hash(&self) -> &TxHash { match self { + PoolError::AlreadyImported(hash) => hash, PoolError::ReplacementUnderpriced(hash) => hash, PoolError::FeeCapBelowMinimumProtocolFeeCap(hash, _) => hash, PoolError::SpammerExceededCapacity(_, hash) => hash, @@ -60,6 +64,10 @@ impl PoolError { #[inline] pub fn is_bad_transaction(&self) -> bool { match self { + PoolError::AlreadyImported(_) => { + // already imported but not bad + false + } PoolError::ReplacementUnderpriced(_) => { // already imported but not bad false diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index dd65c6f72..8807d5e29 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -279,14 +279,14 @@ where self.pool.pooled_transactions_hashes() } - fn pooled_transactions(&self) -> Vec>> { - self.pool.pooled_transactions() - } - fn pooled_transaction_hashes_max(&self, max: usize) -> Vec { self.pooled_transaction_hashes().into_iter().take(max).collect() } + fn pooled_transactions(&self) -> Vec>> { + self.pool.pooled_transactions() + } + fn pooled_transactions_max( &self, max: usize, diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index ddaafb769..79e87d9b2 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -214,6 +214,10 @@ impl TxPool { on_chain_balance: U256, on_chain_nonce: u64, ) -> PoolResult> { + if self.contains(tx.hash()) { + return Err(PoolError::AlreadyImported(*tx.hash())) + } + // Update sender info with balance and nonce self.sender_info .entry(tx.sender_id()) @@ -1146,7 +1150,7 @@ impl SenderInfo { mod tests { use super::*; use crate::{ - test_utils::{MockTransaction, MockTransactionFactory}, + test_utils::{MockOrdering, MockTransaction, MockTransactionFactory}, traits::TransactionOrigin, }; @@ -1213,6 +1217,21 @@ mod tests { assert!(inserted.state.intersects(expected_state)); } + #[test] + fn insert_already_imported() { + let on_chain_balance = U256::ZERO; + let on_chain_nonce = 0; + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + let tx = MockTransaction::eip1559().inc_price().inc_limit(); + let tx = f.validated(tx); + pool.add_transaction(tx.clone(), on_chain_balance, on_chain_nonce).unwrap(); + match pool.add_transaction(tx, on_chain_balance, on_chain_nonce).unwrap_err() { + PoolError::AlreadyImported(_) => {} + _ => unreachable!(), + } + } + #[test] fn insert_replace() { let on_chain_balance = U256::ZERO;