feat(scheduler): opt-in deterministic skip of tx-level-invalid txns#110
Open
Richard1048576 wants to merge 1 commit into
Open
feat(scheduler): opt-in deterministic skip of tx-level-invalid txns#110Richard1048576 wants to merge 1 commit into
Richard1048576 wants to merge 1 commit into
Conversation
9d1d939 to
0f91a66
Compare
Add `Scheduler::with_skip_invalid_txn` (off by default; existing callers unaffected). When enabled, a transaction that fails revm tx-level validation (`EVMError::Transaction`, e.g. NonceTooLow) is deterministically SKIPPED — no state change, a phantom 0-gas revert keeps `results` aligned 1:1 with the block's txs — instead of aborting the whole block. Motivation: order-then-execute chains (Gravity) hand grevm a consensus-ordered block whose per-tx validity cannot be fully pre-screened (execution-induced nonce changes, e.g. a CREATE by a 7702-delegated authority, are not modelable without executing). Today any such tx makes `execute()` return `Err`, which the caller turns into a panic -> whole-network halt. This lets the executor skip the offending tx and keep producing blocks. Determinism: the skip runs in `fallback_sequential` (sequential execution against the final pre-state), so the decision is not a speculative false-positive; and because the parallel committed prefix is sequential-equivalent (Block-STM), the final results/state root are identical on every node regardless of where parallel execution aborted. Non-tx-level errors (Header/Database) stay fatal. gravity-audit#823.
0f91a66 to
c7e92be
Compare
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.
What
Adds
Scheduler::with_skip_invalid_txn(bool)(off by default). When enabled, a transactionthat fails revm's tx-level validation (
EVMError::Transaction, e.g.NonceTooLow,RejectCallerWithCode) is deterministically skipped — no state change, and a phantom0-gas
Revertresult keepsresultsaligned 1:1 with the block's txs — instead of abortingthe whole block. Non-tx-level errors (
Header/Database) stay fatal.Non-breaking: the flag defaults to
false, so existing callers are unchanged (the abortpath and
GrevmErrorare preserved verbatim).Why
grevm is the executor for the Gravity order-then-execute L1: consensus commits an ordered
block whose per-tx validity cannot be fully pre-screened, because some invalidity is
execution-induced and unmodelable without executing — e.g. a
CREATE/CREATE2run by anEIP-7702-delegated authority bumps that account's nonce a second time, so a same-block
follow-up tx becomes
NonceTooLowonly at execution. Today any such tx makesexecute()return
Err, which the caller turns intopanic!→ whole-network halt (gravity-audit#838).This lets the executor skip the offending tx and keep producing blocks — the durable close-out
of that halt class (gravity-audit#823).
Determinism (why this is safe for consensus)
The skip runs in
fallback_sequential, i.e. sequential execution against the finalpre-state, so:
stale optimistic read is re-checked against the committed pre-state); and
tail completes the remainder, the final
results/ state root are identical on every noderegardless of where parallel execution aborted.
Verification (single-node + 4-validator devnet, Prague)
Built into
gravity_nodeand driven with the EIP-7702 authority-nonce halt pair (aSetCode(n)whose authorization double-bumps the sender, plus a staleplain(n+1)co-locatedin the same block; the pre-#385 filter admits the stale tx so it reaches the executor):
ERROR ... skipping tx-level-invalid tx ... txid=1 err=NonceTooLow{tx:1,state:2}; chain keeps producing (head 174→331), stale tx gets a phantom receiptCaller opt-in
The caller enables it explicitly, e.g. in the reth
ParallelExecutor:That reth-side change is a separate PR and should be gated with the coordinated
state-transition-function upgrade (a bad tx goes from halt to skip; mixed versions cause a
liveness stall, not a state fork — only the skip result exists).
Receipt semantics — resolved (gravity-audit#823)
The phantom 0-gas
Revert(keepsresults1:1, commits no state) is the intended shape forthis path. gravity-audit#823 settled it in favor of a deterministic status-0 revert receipt
— every committed tx yields exactly one receipt (tooling totality) — with no state effect:
for a tx-level-invalid tx (e.g.
NonceTooLow) the executor charges no gas and does notbump the nonce. That is deliberately different from a normal execution revert (which does
charge gas / bump nonce): for a
NonceTooLowtx the account nonce is already ahead (nothing toadvance), and charging gas for a tx that only reaches execution via a faulty proposer would be a
replay-griefing vector. Full rationale + the Monad comparison it's grounded in are in
gravity-audit#823.
So the remaining action is on the receipt builder (greth side, not grevm): ensure the receipt
derived from the phantom
Revert(zero gas, empty logs, empty output — revm 40:ExecutionResult::Revert { gas: ResultGas::new(0,0,0), logs: vec![], output: Bytes::new() })is well-formed —
status = 0, emptylogs/logsBloom, nullcontractAddress,cumulativeGasUsedcarried monotonically (this txadds 0).
output: [](no return data) is correct.Update — parallel-path skip (was bypassed)
A review found the original skip only engaged on the sequential path. On the parallel path
(
block_size >= MIN_PARALLEL_TXS, default 64), workers run withdisable_nonce_check=true, so aNonceTooLowis detected by the committer, andparallel_execute'scommit_result()short-circuit returned
Errbeforepost_execute()'s skip — so the caller stillpanic!ed→ halt. My earlier devnet test only exercised small (sequential-path) blocks and missed this.
Fixed by routing a commit-detected tx-level
EVMError::Transactiontofallback_sequentialtoo(truncating
resultsto the failingtxidso it re-executes and skips it). Verified on both revmversions with a 102-tx block: skip ON →
Ok(skip engaged); skip OFF →Err(NonceTooLow)(non-breaking).