core/txpool/legacypool: check pending replace eligibility before eviction in add()#34665
Open
Mayveskii wants to merge 1 commit intoethereum:masterfrom
Open
Conversation
When the transaction pool is full, underpriced transactions are collected via pool.priced.Discard() and then permanently removed from pool.all through the eviction loop (pool.removeTx). The pending-replacement eligibility check that follows (list.Add with PriceBump) runs after this point of no return. If list.Add returns !inserted (price bump not met), the collected transactions are already gone from pool.all with no rollback path, causing a silent loss of legitimate mempool entries. Fix: before the eviction loop, verify the price-bump requirement by reading the existing pending transaction via list.txs.Get(). Discard() only modifies the price heap; pool.all is still intact at this point, so an early return needs only pool.priced.Put() calls to restore the heap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In
(*LegacyPool).add(), when the pool is full the code:pool.priced.Discard()— removes transactions from the price heap only;pool.allis still intact.pool.removeTx) — permanently deletes the collected transactions frompool.all. This is the point of no return.list.Add(tx, pool.config.PriceBump).If
list.Addreturns!inserted(price bump not met), the function returnstxpool.ErrReplaceUnderpriced— but the transactions evicted in step 2 are already gone frompool.allwith no rollback path.This creates a DoS vector: an attacker can craft transactions whose
gasFeeCapexceeds the current underpriced floor (passing theUnderpriced()check) but falls short of the pending-replacement price bump threshold. Each submission permanently evicts legitimate transactions from other senders without adding a new one.Note the contrast with the
isGappedrollback that already exists in the same function: that check runs before the eviction loop, sopool.allis still intact andpool.priced.Put(dropTx)is sufficient to restore state. The pending-replacement case has no equivalent guard.Fix
Before the eviction loop, read the existing pending transaction via
list.txs.Get(tx.Nonce())(read-only) and reproduce the price-bump threshold comparison fromlist.Add(). If the incoming transaction would fail replacement, restore the price heap withpool.priced.Put()(sufficient becausepool.allis untouched byDiscard()) and return early.