Skip to content

feat(evmonly): add eth_getTransactionByHash RPC to the EVM-only executor - #4205

Merged
shemnon merged 2 commits into
giga-1from
shemnon/giga-evmonly-tx-by-hash
Sep 16, 2026
Merged

shemnon merged 2 commits into
giga-1from
shemnon/giga-evmonly-tx-by-hash

Conversation

@shemnon

@shemnon shemnon commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Adds eth_getTransactionByHash to the Autobahn EVM-only executor's RPC server, on top of eth_call (#4194). Validated directly against a real happy-path failure: cast send with fully manual nonce/gas + --async, followed by cast tx <hash> to inspect the submitted transaction, currently hard-fails with the method eth_getTransactionByHash does not exist.

  • txAPI.GetTransactionByHash (giga/evmonly/rpc/tx.go) reuses the same receipt-store + Block() lookup chain GetTransactionReceipt already has (refactored into a shared lookupFinalizedTx helper, behavior unchanged), then decodes the raw transaction bytes out of the committed block's Data.Txs[index] and builds the response via go-ethereum's export.NewRPCTransaction (the same export package eth_call's TransactionArgs already uses, zero Cosmos dependency)
  • new EvmChainConfig plumbed through Proxy/Environment/evmOnlyApplication, mirroring the existing EvmCall type-assertion pattern rather than widening the shared abci.Application interface — NewRPCTransaction needs a full *params.ChainConfig, which evmOnlyApplication already builds once and now just exposes
  • scope matches GetTransactionReceipt: finalized only, null for pending/unknown hashes; no mempool lookup (no such plumbing exists in this server's Backend)
  • carries forward a real edge case from the evmrpc reference implementation: From is patched from the stored receipt when sender recovery fails (e.g. a legacy tx signed for a different chain ID than the node's), covered by a dedicated test
  • documents the new method and a cast tx usage example in the Autobahn README

Testing performed to validate your change

  • go build ./...
  • go test -count=1 ./giga/evmonly/... ./sei-tendermint/internal/proxy/... ./sei-tendermint/internal/evmonlyapp/... ./sei-tendermint/internal/rpc/core/... ./sei-tendermint/node/...
  • New coverage in tx_test.go: full field-accurate decode for both a DynamicFeeTx and a LegacyTx sent through a real committed block, the sender-recovery-failure/From-patch edge case, null-for-unknown-hash, null-for-not-yet-committed, and a full JSON-RPC round trip via httptest
  • golangci-lint run v2.13.2 scoped to all touched packages: 0 issues
  • gofmt -s / goimports clean

🤖 Generated with Claude Code

Fixes the Foundry happy path (`cast send` with manual gas/nonce, then
`cast tx <hash>`), which currently hard-fails with method-not-found.

- txAPI.GetTransactionByHash decodes the raw transaction bytes already
  present in the committed block's Data.Txs (the same bytes
  eth_sendRawTransaction accepted) via export.NewRPCTransaction, reusing
  GetTransactionReceipt's receipt+block lookup chain (factored out as
  lookupFinalizedTx).
- Adds EvmChainConfig to the Backend interface, plumbed from
  evmOnlyApplication's existing chainConfig field through proxy.Proxy
  (mirroring the EvmCall type-assertion pattern) and
  rpc/core.Environment, since NewRPCTransaction needs a real
  *params.ChainConfig to recover the sender.
- Scoped to finalized transactions only, matching
  eth_getTransactionReceipt: this server has no mempool-query path, so a
  pending or unknown hash returns null rather than a pending-shaped
  result.
- Documents the new method in integration_test/autobahn/README.md and
  narrows its "still missing" caveat accordingly.
@shemnon
shemnon marked this pull request as ready for review September 16, 2026 13:44
@cursor

cursor Bot commented Sep 16, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New transaction RPC and shared receipt/block lookup affect client-facing behavior; eth_call now depends on EvmBaseFee plumbing, with a documented caveat if base fee becomes historical.

Overview
Adds eth_getTransactionByHash to the Autobahn EVM-only JSON-RPC server so tools like cast tx can read finalized transactions after broadcast.

Lookups follow the same rules as eth_getTransactionReceipt: receipt store → committed block → decode raw tx at the receipt index, then build the response with go-ethereum’s export.NewRPCTransaction. Receipt resolution is factored into lookupFinalizedTx (receipt behavior unchanged). Unknown or not-yet-finalized hashes return null; there is no mempool/pending path. From is filled from the stored receipt when sender recovery fails (legacy / wrong chain ID), matching evmrpc.

The RPC Backend now requires EvmChainConfig and EvmBaseFee, wired through Proxy, Environment, and evmOnlyApplication. eth_call uses the backend base fee instead of a hardcoded zero so call defaults stay aligned with execution. A TODO notes that historical txs would misreport effective gas price if base fee becomes dynamic while still using the current fee.

Autobahn README documents the new method and cast tx usage.

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

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 16, 2026, 2:15 PM

@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.81132% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.54%. Comparing base (7e3ca24) to head (3898d3b).
⚠️ Report is 16 commits behind head on giga-1.

Files with missing lines Patch % Lines
giga/evmonly/rpc/tx.go 69.44% 11 Missing ⚠️
sei-tendermint/internal/evmonlyapp/app.go 0.00% 2 Missing ⚠️
sei-tendermint/internal/rpc/core/mempool.go 0.00% 2 Missing ⚠️
giga/evmonly/rpc/call.go 66.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           giga-1    #4205      +/-   ##
==========================================
- Coverage   65.55%   65.54%   -0.01%     
==========================================
  Files        2081     2082       +1     
  Lines      157460   157272     -188     
==========================================
- Hits       103222   103089     -133     
+ Misses      54097    54042      -55     
  Partials      141      141              
Flag Coverage Δ
sei-chain-pr 53.75% <69.81%> (?)
sei-db ?

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

Files with missing lines Coverage Δ
giga/evmonly/rpc/server.go 21.95% <ø> (-20.16%) ⬇️
sei-tendermint/internal/proxy/proxy.go 90.32% <100.00%> (-0.79%) ⬇️
giga/evmonly/rpc/call.go 87.50% <66.66%> (ø)
sei-tendermint/internal/evmonlyapp/app.go 81.72% <0.00%> (-0.06%) ⬇️
sei-tendermint/internal/rpc/core/mempool.go 48.66% <0.00%> (-2.75%) ⬇️
giga/evmonly/rpc/tx.go 85.10% <69.44%> (ø)

... and 5 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.

@bdchatham bdchatham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Adds eth_getTransactionByHash to the EVM-only RPC server by reusing the receipt-store + Block() lookup (correctly extracted into a shared lookupFinalizedTx with no behavior change to GetTransactionReceipt) and decoding the raw tx from the committed block. The plumbing, index semantics, signer/fork inputs, and test coverage all check out; two non-blocking robustness/maintainability suggestions.

Findings: 0 blocking | 2 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • None at the file/PR level.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/rpc/tx.go
stored.TransactionIndex, stored.BlockNumber, len(block.Block.Txs))
}
ethtx := new(ethtypes.Transaction)
if err := ethtx.UnmarshalBinary(block.Block.Txs[stored.TransactionIndex]); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The response is assembled entirely from stored.TransactionIndex without ever confirming the decoded transaction is the one that was asked for. Today the index is trustworthy — evmOnlyApplication.FinalizeBlock passes req.Txs straight through and executeBlockSequential increments txIndexUint 1:1 with that slice, so receipt index == Data.Txs index — but that invariant lives two packages away and nothing here asserts it. If the receipt store and block store ever disagree at a height (stale receipts surviving a rollback and re-execution with different ordering, a future filtering step in the prepare path), this returns a different transaction's full field set under the caller's hash rather than an error. A one-line guard after the decode makes the invariant local and turns silent wrong data into a diagnosable failure:

if ethtx.Hash() != hash {
	return nil, fmt.Errorf("block %d index %d holds transaction %s, not %s", stored.BlockNumber, stored.TransactionIndex, ethtx.Hash(), hash)
}

ethtx.Hash() is memoized, so the cost is one keccak on a path that already does a store read and a block fetch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's leave open design space where TxId may actually be an ID and not a hash of the body. Thus don't verify but trust.

Comment thread giga/evmonly/rpc/tx.go Outdated
return nil, fmt.Errorf("block %d time is negative: %s", stored.BlockNumber, block.Block.Time)
}
// Must match the base fee EvmCall executes under (evmOnlyBaseFee).
baseFee := new(big.Int)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This hardcodes a second copy of the block base fee; the real one is evmOnlyBaseFee() in sei-tendermint/internal/evmonlyapp/app.go, and the comment is the only thing connecting them. For a DynamicFeeTx, NewRPCTransaction derives gasPrice as min(tip+baseFee, feeCap) — the same formula the receipt's effectiveGasPrice comes from via evmonly.EffectiveGasPrice(tx, baseFee). So if evmOnlyBaseFee() ever stops returning zero, eth_getTransactionByHash and eth_getTransactionReceipt start reporting different prices for the same transaction, and nothing fails: every test in tx_test.go builds its own backend, so no test compares the two endpoints against a shared base fee.

Since this PR already establishes the pattern for exactly this shape of dependency, consider plumbing it the same way — an EvmBaseFee() *big.Int on Backend backed by evmOnlyBaseFee() — so the value has one definition instead of a comment asking the next reader to keep two in sync.

Give the base fee EvmCall executes under a single definition instead
of a comment linking two hardcoded copies in tx.go and call.go.

- Add evmOnlyApplication.EvmBaseFee(), proxied through Proxy and
  Environment the same way EvmChainConfig is.
- GetTransactionByHash and Call now read it from Backend instead of
  hardcoding new(big.Int).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shemnon
shemnon enabled auto-merge September 16, 2026 14:13
@shemnon
shemnon added this pull request to the merge queue Sep 16, 2026
Merged via the queue into giga-1 with commit 3ef5849 Sep 16, 2026
62 checks passed
@shemnon
shemnon deleted the shemnon/giga-evmonly-tx-by-hash branch September 16, 2026 14:38
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.

3 participants