Skip to content

fix: keep tx in pool when pre-simulation hits a non-transaction error#540

Open
mooncitydev wants to merge 1 commit into
flashbots:mainfrom
mooncitydev:fix/presim-fail-open-on-sim-error
Open

fix: keep tx in pool when pre-simulation hits a non-transaction error#540
mooncitydev wants to merge 1 commit into
flashbots:mainfrom
mooncitydev:fix/presim-fail-open-on-sim-error

Conversation

@mooncitydev

Copy link
Copy Markdown
Contributor

hey, its moondev. was reading through the pre-simulation path and i think this one is a real bug rather than a nitpick.

what happens

in crates/op-rbuilder/src/pool/presim.rs, run_simulation runs a revert-protected tx through the evm against the pinned tip state and decides whether to keep it in the pool:

  • Ok(..) => keep only if it didn't revert
  • Err(..) => keep only if it's a NonceTooLow/NonceTooHigh (the pool can still order those), otherwise drop it

the problem is that last "otherwise drop it" branch. evm.transact doesn't only fail because the tx is invalid, it also fails when the state read itself fails. the tip state is pinned to a specific block (state_by_block_hash(tip.hash_slow())), and by the time the background sim task actually runs that block can be reorged out or pruned. that surfaces as EVMError::Database(..), which isn't a nonce error, so the current code treats it like a revert and evicts the tx.

so a transient state hiccup silently drops a valid, revert-protected tx. and in the override path (pool/overrides.rs) the sender already got AddedTransactionOutcome { state: Pending } back, so from their side the tx just disappears with no error. the only trace is a debug!("evicting reverting tx") line, which is misleading because it never reverted.

alloy's EvmError trait actually spells this out in its docs: "if caller occurs a error different from EvmError::InvalidTransaction, it should most likely be treated as fatal error flagging some EVM misconfiguration." ie a non-InvalidTransaction error is not a verdict on the tx.

the fix

only evict on a genuine InvalidTransaction (keeping the existing nonce-gap pass-through), and fail open on everything else with a warning so the failure is visible. behavior for real reverts and real validity errors (insufficient funds etc.) is unchanged. pulled the decision into a small keep_tx_on_simulation_error helper and added unit tests for the db/custom (kept), nonce (kept) and fund (evicted) cases.

notes

i know the contributing guide likes an issue first, happy to open one if you'd prefer. also happy to adjust naming or add a metric for the fail-open case. cheers

the top-of-block pre-simulator evicts a tx whenever evm.transact returns
an error that isn't a nonce gap. but transact also errors when the state
read itself fails, e.g. the pinned tip state was reorged out or pruned
before the background sim task ran. that comes back as EVMError::Database,
which isn't a nonce error, so a valid revert-protected tx gets silently
dropped from the pool (and in the override path the sender already got a
Pending response back, so from their side it just vanishes).

only InvalidTransaction errors say anything about the tx, so evict on
those and fail open on everything else, matching how nonce gaps are
already handled. pulled the decision into a small helper and added unit
tests for the db/custom, nonce and fund cases.

@julio4 julio4 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good thanks! Could you keep comments slightly more concise?
Also, be sure to sign your commits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants