diff --git a/crates/mempool/src/mempool.rs b/crates/mempool/src/mempool.rs index 85be89de49..9a866ad2d6 100644 --- a/crates/mempool/src/mempool.rs +++ b/crates/mempool/src/mempool.rs @@ -148,24 +148,38 @@ impl Mempool { } fn validate_input(&self, input: &MempoolInput) -> MempoolResult<()> { + let MempoolInput { + tx: ThinTransaction { sender_address, nonce: tx_nonce, .. }, + account: Account { state: AccountState { nonce: account_nonce }, .. }, + } = input; + let duplicate_nonce_error = + MempoolError::DuplicateNonce { address: *sender_address, nonce: *tx_nonce }; + + // Stateless checks. + + // Check the input: transaction nonce against given account state. + if account_nonce > tx_nonce { + return Err(duplicate_nonce_error); + } + + // Stateful checks. + // Check nonce against mempool state. - let MempoolInput { tx, account } = input; - if let Some(AccountState { nonce }) = self.mempool_state.get(&tx.sender_address) { - if nonce >= &tx.nonce { - return Err(MempoolError::DuplicateNonce { - address: tx.sender_address, - nonce: tx.nonce, - }); + if let Some(AccountState { nonce: mempool_state_nonce }) = + self.mempool_state.get(sender_address) + { + if mempool_state_nonce >= tx_nonce { + return Err(duplicate_nonce_error); } } - // Check nonce against given account state. - let Account { state: AccountState { nonce }, .. } = account; - if nonce > &tx.nonce { - return Err(MempoolError::DuplicateNonce { - address: tx.sender_address, - nonce: tx.nonce, - }); + // Check nonce against the queue. + if self + .tx_queue + .get_nonce(*sender_address) + .is_some_and(|queued_nonce| queued_nonce > *tx_nonce) + { + return Err(duplicate_nonce_error); } Ok(()) diff --git a/crates/mempool/src/mempool_test.rs b/crates/mempool/src/mempool_test.rs index 0e8b36ca79..0ac9936f35 100644 --- a/crates/mempool/src/mempool_test.rs +++ b/crates/mempool/src/mempool_test.rs @@ -427,6 +427,24 @@ fn test_add_tx_with_duplicate_tx(mut mempool: Mempool) { expected_mempool_state.assert_eq_pool_state(&mempool); } +#[rstest] +fn test_add_tx_lower_than_queued_nonce() { + // Setup. + let valid_input = + add_tx_input!(tx_hash: 1, sender_address: "0x0", tx_nonce: 1_u8, account_nonce: 1_u8); + let lower_nonce_input = + add_tx_input!(tx_hash: 2, sender_address: "0x0", tx_nonce: 0_u8, account_nonce: 0_u8); + + let queue_txs = [TransactionReference::new(&valid_input.tx)]; + let expected_mempool_state = MempoolState::with_queue(queue_txs); + let pool_txs = [valid_input.tx]; + let mut mempool: Mempool = MempoolState::new(pool_txs, queue_txs).into(); + + // Test and assert the original transaction remains. + assert_matches!(mempool.add_tx(lower_nonce_input), Err(MempoolError::DuplicateNonce { .. })); + expected_mempool_state.assert_eq_queue_state(&mempool); +} + #[rstest] fn test_add_tx_with_identical_tip_succeeds(mut mempool: Mempool) { // Setup.