diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index ea281c4314..36151b5688 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -520,6 +520,9 @@ impl From for RpcPoolError { InvalidPoolTransactionError::InvalidEip4844Blob(err) => { RpcPoolError::InvalidEip4844Blob(err) } + InvalidPoolTransactionError::Eip4844NonceGap => { + RpcPoolError::Invalid(RpcInvalidTransactionError::NonceTooHigh) + } } } } diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index 658f10d324..c1e844e090 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -144,6 +144,14 @@ pub enum InvalidPoolTransactionError { /// Thrown if validating the blob sidecar for the transaction failed. #[error(transparent)] InvalidEip4844Blob(BlobTransactionValidationError), + /// EIP-4844 transactions are only accepted if they're gapless, meaning the previous nonce of + /// the transaction (`tx.nonce -1`) must either be in the pool or match the on chain nonce of + /// the sender. + /// + /// This error is thrown on validation if a valid blob transaction arrives with a nonce that + /// would introduce gap in the nonce sequence. + #[error("Nonce too high.")] + Eip4844NonceGap, /// Any other error that occurred while inserting/validating that is transaction specific #[error("{0:?}")] Other(Box), @@ -210,6 +218,11 @@ impl InvalidPoolTransactionError { // This is only reachable when the blob is invalid true } + InvalidPoolTransactionError::Eip4844NonceGap => { + // it is possible that the pool sees `nonce n` before `nonce n-1` and this is only + // thrown for valid(good) blob transactions + false + } } } } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 7a609f3eec..bd6665ea63 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -1085,7 +1085,7 @@ impl AllTransactions { false } - /// Inserts a new transaction into the pool. + /// Inserts a new _valid_ transaction into the pool. /// /// If the transaction already exists, it will be replaced if not underpriced. /// Returns info to which sub-pool the transaction should be moved. @@ -1104,13 +1104,16 @@ impl AllTransactions { assert!(on_chain_nonce <= transaction.nonce(), "Invalid transaction"); let transaction = Arc::new(self.ensure_valid(transaction)?); - let tx_id = *transaction.id(); + let inserted_tx_id = *transaction.id(); let mut state = TxState::default(); let mut cumulative_cost = U256::ZERO; let mut updates = Vec::new(); - let ancestor = - TransactionId::ancestor(transaction.transaction.nonce(), on_chain_nonce, tx_id.sender); + let ancestor = TransactionId::ancestor( + transaction.transaction.nonce(), + on_chain_nonce, + inserted_tx_id.sender, + ); // If there's no ancestor tx then this is the next transaction. if ancestor.is_none() { @@ -1133,6 +1136,7 @@ impl AllTransactions { state.insert(TxState::NOT_TOO_MUCH_GAS); } + // placeholder for the replaced transaction, if any let mut replaced_tx = None; let pool_tx = PoolInternalTransaction { @@ -1175,6 +1179,7 @@ impl AllTransactions { // The next transaction of this sender let on_chain_id = TransactionId::new(transaction.sender_id(), on_chain_nonce); { + // get all transactions of the sender's account let mut descendants = self.descendant_txs_mut(&on_chain_id).peekable(); // Tracks the next nonce we expect if the transactions are gapless @@ -1189,7 +1194,7 @@ impl AllTransactions { // SAFETY: the transaction was added above so the _inclusive_ descendants iterator // returns at least 1 tx. let (id, tx) = descendants.peek().expect("Includes >= 1; qed."); - if id.nonce < tx_id.nonce { + if id.nonce < inserted_tx_id.nonce { !tx.state.is_pending() } else { true @@ -1232,7 +1237,7 @@ impl AllTransactions { // update the pool based on the state tx.subpool = tx.state.into(); - if tx_id.eq(id) { + if inserted_tx_id.eq(id) { // if it is the new transaction, track the state state = tx.state; } else { @@ -1254,7 +1259,7 @@ impl AllTransactions { // If this wasn't a replacement transaction we need to update the counter. if replaced_tx.is_none() { - self.tx_inc(tx_id.sender); + self.tx_inc(inserted_tx_id.sender); } Ok(InsertOk { transaction, move_to: state.into(), state, replaced_tx, updates })