Skip to content

fix(giga): write receipt for state-transition errors that bump the nonce (CON-256)#3383

Open
wen-coding wants to merge 9 commits intomainfrom
wen/write_evm_receipt_for_failed_tx
Open

fix(giga): write receipt for state-transition errors that bump the nonce (CON-256)#3383
wen-coding wants to merge 9 commits intomainfrom
wen/write_evm_receipt_for_failed_tx

Conversation

@wen-coding
Copy link
Copy Markdown
Contributor

@wen-coding wen-coding commented May 4, 2026

Invariant

A receipt is written for an EVM tx iff that tx bumped the sender's nonce. Equivalently: any tx the chain treats as having advanced the sender's account state — and only those — is observable via eth_getTransactionByHash / eth_getTransactionReceipt. Txs included in a block but rejected before the nonce bump (e.g. ante-handler failures, pre-execution state-transition errors) leave no receipt and are invisible to standard EVM clients, matching go-ethereum semantics.

This PR fixes a place where the Giga executor violated the invariant: a state-transition error inside Execute() (notably EIP-7623 floor-data-gas underflow, post-Pectra) bumped the sender's nonce but produced no receipt, so eth_getTransactionReceipt returned null forever and clients that polled for it hung. The invariant is now enforced symmetrically across V2 and Giga.

How the invariant is enforced

V2 path (no change in this PR — already correct):

  1. BasicDecorator.AnteHandle registers a WithDeliverTxCallback (x/evm/ante/basic.go:33-39) that fires after DeliverTx regardless of success/failure. The callback bumps the sender's nonce and calls SetNonceBumped.
  2. After msgServer.EVMTransaction returns err, the SDK populates ExecTxResult.Code != 0 and ExecTxResult.Log = err.Error().
  3. At EndBlock, GetAllEVMTxDeferredInfo (x/evm/keeper/deferred.go:24) synthesizes a DeferredInfo{TxHash, Error: txRes.Log} for each tx that didn't append its own.
  4. The EndBlock loop (x/evm/keeper/abci.go:100-113) writes a synthetic receipt for entries with Error != "" — gated on GetNonceBumped, which is exactly the invariant. Existing coverage: TestEndBlock_NoReceiptForNonceMismatch and TestEndBlock_ReceiptCreatedWhenNonceBumped in x/evm/keeper/abci_test.go.

Giga path (fixed in this PR):

  1. executeEVMTxWithGigaExecutor already explicitly bumps the sender's nonce in its execErr != nil branch (app/app.go:1887).
  2. It also calls AppendToEvmTxDeferredInfo for surplus tracking — but with Error == "", so the EndBlock synthetic-receipt loop above skips it.
  3. Fix: call WriteReceipt directly in the err-branch, gated on the explicit nonce bump it already does. EIP-1559 effective gas price is used (not GasFeeCap) so the receipt's EffectiveGasPrice matches go-ethereum semantics for dynamic-fee txs.

Tests

  • TestGiga_FailedExecution_ProducesReceipt (giga/tests/giga_test.go) drives the EIP-7623 floor-data-gas-underflow case through the Giga executor, asserts the explicit nonce bump (nonceBefore+1 == nonceAfter) to lock in the invariant, and asserts the receipt was written with status=0, gasUsed=gasLimit, and the floor-data-gas error in VmError.

Things done

  • Giga path (app/app.go): write receipt in execErr != nil branch using EIP-1559 effective gas price
  • Giga test: nonce-bump assertion + receipt assertion
  • gofmt -s clean

🤖 Generated with Claude Code

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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 6, 2026, 4:40 PM

@wen-coding wen-coding changed the title evm: write status=0 receipt for state-transition errors evm: write status=0 receipt for state-transition errors (CON-256) May 4, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.08%. Comparing base (7004ef1) to head (2ffebc5).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
app/app.go 66.66% 4 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3383   +/-   ##
=======================================
  Coverage   59.08%   59.08%           
=======================================
  Files        2100     2099    -1     
  Lines      173008   173019   +11     
=======================================
+ Hits       102222   102235   +13     
+ Misses      61922    61920    -2     
  Partials     8864     8864           
Flag Coverage Δ
sei-chain-pr 59.08% <66.66%> (?)
sei-db 70.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
app/app.go 70.46% <66.66%> (+0.60%) ⬆️

... and 31 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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>
wen-coding and others added 6 commits May 4, 2026 17:16
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>
V2's existing EndBlock synthetic-receipt path (x/evm/keeper/abci.go:100)
already writes a receipt for state-transition errors: the
GetAllEVMTxDeferredInfo fallback synthesizes a DeferredInfo from
txRes.Log when none was appended, and EndBlock's loop gates the
synthetic-receipt write on GetNonceBumped. BasicDecorator's
WithDeliverTxCallback bumps the nonce + calls SetNonceBumped on every
DeliverTx (including failures), so the rule "receipt iff the tx bumped
the sender's nonce" already holds for V2. The explicit WriteReceipt
added to msg_server.go was redundant and also bypassed the
GetNonceBumped gate, so it's removed.

The Giga path is still missing the receipt because its
AppendToEvmTxDeferredInfo call in the err-branch doesn't propagate the
Error string, so EndBlock's `if deferredInfo.Error != ""` check fails
and skips the synthetic-receipt write. Giga's explicit WriteReceipt
in app.go is kept and is gated on the explicit nonce bump it already
does at app.go:1887.

Both tests are extended with nonce-before/after assertions to lock in
the invariant. The V2 test is rewritten to drive the full production
sequence (BasicDecorator -> msgServer -> deliverTxCallback ->
SetMsgs/SetTxResults -> EndBlock) so it exercises the synthetic path
end-to-end for an EIP-7623 floor-data-gas-underflow tx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wen-coding wen-coding changed the title evm: write status=0 receipt for state-transition errors (CON-256) fix(giga): write receipt for state-transition errors that bump the nonce (CON-256) May 6, 2026
V2's msg_server.go is unchanged on this branch (the EndBlock synthetic
path already handles state-transition errors), so the V2 test added
earlier — useful as the experiment that proved the V2 path works for
floor-data-gas-underflow — is out of scope for a Giga-only PR. The
existing tests in x/evm/keeper/abci_test.go (TestEndBlock_NoReceipt
ForNonceMismatch, TestEndBlock_ReceiptCreatedWhenNonceBumped) already
cover the EndBlock synthetic-receipt mechanism and its GetNonceBumped
gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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