Skip to content

fix(evm): populate fields on V2 synthetic receipts for state-transition errors#3448

Closed
wen-coding wants to merge 1 commit into
mainfrom
wen/v2-synthetic-receipt-fields
Closed

fix(evm): populate fields on V2 synthetic receipts for state-transition errors#3448
wen-coding wants to merge 1 commit into
mainfrom
wen/v2-synthetic-receipt-fields

Conversation

@wen-coding
Copy link
Copy Markdown
Contributor

Why

Follow-up to #3383, which fixed the Giga path's receipt-iff-nonce-bumped invariant for EIP-7623 floor-data-gas failures. That PR's Giga code writes the receipt with GasUsed = ethTx.Gas() and EIP-1559 EffectiveGasPrice. The V2 path, which catches the same failure shape via the EndBlock synthetic-receipt loop, was emitting only TxHashHex, TransactionIndex, VmError, and BlockNumber — everything else defaulted to zero/empty.

So an RPC client polling eth_getTransactionReceipt for a floor-data-gas-failed tx on V2 saw GasUsed=0, EffectiveGasPrice=0, From="", TxType=0. The sender did pay gasLimit * effectiveGasPrice in the ante handler with no refund, so this misrepresents what was actually charged.

The closest in-spec analog for this failure shape is OOG, which go-ethereum reports as GasUsed = gasLimit. (Mainnet go-ethereum would reject these txs before block inclusion entirely — but Sei's ante/Execute split means they can land in a block, and we have to pick a convention. OOG is the right one.)

What

In x/evm/keeper/abci.go:

  • Capture GetBaseFee(ctx) before AdjustDynamicBaseFeePerGas runs — that call overwrites the stored value with the next block's base fee, so reading it later in the loop would return the wrong price.
  • Factor synthetic-receipt construction into a buildSyntheticReceipt helper that populates GasUsed=etx.Gas(), Status=Failed, TxType, From, To/ContractAddress, and EIP-1559 EffectiveGasPrice from the original message and captured base fee.
  • Defensive fallbacks for missing msg, missing Derived, or baseFee == nil (pre-v6.2.0 pacific-1).

Downstream effects of the field changes

Three read-time call sites that touch synthetic-receipt-shaped data:

  1. evmrpc/tx.go:147eth_getTransactionReceipt read-time backfill. Predicate is Status==0 && GasUsed==0. New receipts skip it (already populated). Old receipts (GasUsed==0) continue to hit it and get GasUsed=0 reaffirmed — historical wire-format preserved. Not touched.

  2. evmrpc/utils.go:254 — block-tx-list nonce verification. Predicate is Status==0 && EffectiveGasPrice==0. New receipts skip it (now populated) — safe, the EndBlock loop already gates writes on GetNonceBumped. The verification was a belt-and-suspenders. Not touched.

  3. evmrpc/info.go:377,406,456eth_feeHistory / gas-price congestion / CalculateGasUsedRatio sum receipt.GasUsed across a block. For floor-data-gas-failed txs, contribution goes from 0etx.Gas(). So:

    • eth_feeHistory reward percentiles include floor-data-gas txs as full-gas contributors.
    • eth_gasPrice congestion detection may flip isChainCongested slightly sooner.
    • CalculateGasUsedRatio reports higher ratios for blocks containing these failures.

    Note: AdjustDynamicBaseFeePerGas uses cosmos-sdk's blockGasUsed (block gas meter), not receipts, so base fee adjustment is unaffected. Post-fix, the receipt says "I used etx.Gas()" while the block gas meter says "I used 0" — matches the spec interpretation: the user paid for the full gas (receipt), but no opcodes ran (gas meter). Floor-data-gas failures are rare enough that the practical impact on (1)-(3) is minimal.

Tests

TestEndBlock_ReceiptCreatedWhenNonceBumped extended to set msg.Derived (mirroring what the ante handler does in prod) and assert the new Status/TxType/GasUsed/From/EffectiveGasPrice/To fields.

Things done

  • gofmt -s clean
  • go test ./x/evm/keeper/...

🤖 Generated with Claude Code

…on errors

The EndBlock synthetic-receipt path for txs that bumped nonce but failed in
msg_server before WriteReceipt (notably EIP-7623 floor-data-gas underflow)
left GasUsed=0, EffectiveGasPrice=0, From="", and TxType=0. RPC clients
relying on these fields saw a charged-and-included tx as if it had used no
gas, which is misleading: the sender paid gasLimit * effectiveGasPrice in
ante with no refund.

The closest in-spec analog for this failure shape is OOG, which go-ethereum
reports as GasUsed = gasLimit. Match that here, and populate From/To/
ContractAddress/TxType/EffectiveGasPrice/Status from the original message +
captured base fee, so V2 synthetic receipts match the Giga path (which
already calls WriteReceipt directly with these values, see #3383).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented May 16, 2026

PR Summary

Medium Risk
Changes receipt construction for certain failed EVM txs and adjusts base-fee timing, which can affect RPC-visible receipt fields and downstream fee/gas analytics but not core state transition logic.

Overview
Synthetic receipts created in EndBlock for EVM txs that bumped nonce but failed before WriteReceipt now populate key fields instead of leaving them zeroed.

The EndBlock path captures the current block baseFee before AdjustDynamicBaseFeePerGas mutates it, and uses a new buildSyntheticReceipt helper to set Status=failed, TxType, GasUsed=gasLimit, From/To/ContractAddress, and EIP-1559 EffectiveGasPrice (with defensive fallbacks when tx/derived/baseFee data is unavailable). Tests are extended to assert the new synthetic-receipt fields.

Reviewed by Cursor Bugbot for commit d775dee. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 16, 2026

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 16, 2026, 12:49 AM

@wen-coding
Copy link
Copy Markdown
Contributor Author

Closing — Giga executor is rolling out everywhere soon, so V2's EndBlock synthetic-receipt path will be dead code shortly. The cosmetic improvement isn't worth the merge surface in the migration window. Receipts for floor-data-gas failures under Giga are already correct via #3383's inline WriteReceipt.

@wen-coding wen-coding closed this May 16, 2026
@wen-coding wen-coding deleted the wen/v2-synthetic-receipt-fields branch May 16, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant