evm: write status=0 receipt for state-transition errors (CON-256)#3383
Open
wen-coding wants to merge 7 commits intomainfrom
Open
evm: write status=0 receipt for state-transition errors (CON-256)#3383wen-coding wants to merge 7 commits intomainfrom
wen-coding wants to merge 7 commits intomainfrom
Conversation
Tx that's included in a block must produce an EVM receipt. The msg_server err-not-VM-error branch (V2: x/evm/keeper/msg_server.go; Giga: app/app.go executeEVMTxWithGigaExecutor) used to drop the receipt and log only — leaving eth_getTransactionByHash and eth_getTransactionReceipt returning null forever for included-but-failed txs. Write a status=0 receipt with gasUsed=gasLimit instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3383 +/- ##
=======================================
Coverage 59.08% 59.08%
=======================================
Files 2100 2099 -1
Lines 173007 172989 -18
=======================================
- Hits 102218 102209 -9
+ Misses 61926 61915 -11
- Partials 8863 8865 +2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Adds TestGiga_FailedExecution_ProducesReceipt — the Giga-path counterpart to TestEVMTransactionStateTransitionErrorProducesReceipt. Triggers the EIP-7623 floor-data-gas check inside go-ethereum's Execute() (intrinsic gas passes EvmStatelessChecks but floor data gas fails inside Execute()), then asserts the transient receipt store contains a status=0 receipt with gasUsed=gasLimit and a populated VmError. Without the app.go fix, the receipt is dropped and the assertion fails with "receipt not found"; with the fix it passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
Mirror the success-branch evmMsg construction (PR #3384): use effectiveGasPrice (computed at line 1866) for receipt's EffectiveGasPrice field instead of ethTx.GasPrice() which returns GasFeeCap for dynamic-fee txs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LegacyTx had GasPrice == GasFeeCap == GasTipCap, so the EffectiveGasPrice assertion would pass equally whether the receipt stored maxFee or the EIP-1559 effective price. Switch to a DynamicFeeTx where tip < cap so the assertion discriminates: if anyone reverts the err-branch evmMsg to hand-roll with ethTx.GasPrice() (returns GasFeeCap for dynamic-fee txs), the assertion catches it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Both V2 (
x/evm/keeper/msg_server.go) and Giga (app/app.go executeEVMTxWithGigaExecutor) had anerr != nilbranch — for state-transition errors that aren't VM errors — that logged the failure and returned without writing a receipt. The receipt store stays empty for that tx hash, soeth_getTransactionByHashandeth_getTransactionReceiptreturn null forever for an included tx. Clients that poll for the receipt (e.g. ethers, hardhat-ethers) hang.This patch writes a status=0 receipt with
gasUsed=gasLimitin both branches before returning.Why this branch existed
Original V2 msg_server (#1083, Oct 2023) treated this branch as defensively unreachable: the antehandler validated everything that could fail before execution (signature, nonce, balance, intrinsic gas), so by the time msg_server ran
Execute()could only return VM errors (whichWriteReceiptalready handles viavmError). Dropping the receipt in this branch was harmless then because nothing reached it.Pectra/EIP-7623 added a floor-data-gas check inside go-ethereum's
Execute(). The Sei antehandler doesn't replicate it (and shouldn't — the chain follows go-ethereum semantics). So a tx withintrinsic ≤ gasLimit < floornow passes the antehandler and fails insideExecute(), surfacing this previously-unreachable branch in normal operation.The Giga path (#2654, Jan 2026; #2889, Feb 2026) was deliberately ported to match V2's behavior here and inherited the same gap.
Why this surfaced first under Autobahn
Under V2+CometBFT the Cosmos tx indexer records every tx in the block by hash, and evmrpc falls back to it when the EVM receipt store has no entry.
eth_getTransactionByHashresolves quickly via the fallback even with no EVM receipt, so the missing-receipt bug stayed latent. Autobahn intentionally has no Cosmos tx indexer (it's a deliberate design choice — Cosmos-specific indexing isn't being pulled forward), so the fallback isn't there. Same bug, different visibility.The general principle
Beyond the specific EIP-7623 case: any tx included in a block should produce a receipt. Silently dropping receipts for "this shouldn't happen" cases is a fragile contract — the next protocol upgrade or antehandler change can move a check across that boundary and make the case observable. Always producing a receipt (with status=0 + a meaningful
vmError) gives clients deterministic feedback and removes a class of "tx vanished" bugs by construction.Things done
x/evm/keeper/msg_server.go): write receipt inerr != nilbranchapp/app.go): write receipt inexecErr != nilbranch, usingeffectiveGasPricefor EIP-1559 correctnessx/evm/keeper/msg_server_test.go— uses a dynamic-fee tx wheretip < capso theEffectiveGasPriceassertion discriminates between effective price and maxFeegofmt -sclean🤖 Generated with Claude Code