Skip to content

Add eth_estimateGas to the EVM-only Giga RPC - #4312

Closed
masih wants to merge 8 commits into
mainfrom
masih/1790168306-evmonly-estimate-gas
Closed

masih wants to merge 8 commits into
mainfrom
masih/1790168306-evmonly-estimate-gas

Conversation

@masih

@masih masih commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

The EVM-only Giga RPC serves eth_call but not eth_estimateGas, so sei-load, cast send, ethers and viem all fail before they can submit a transaction unless the caller hard-codes a gas limit.

Executor.EstimateGas in giga/evmonly opens one committed state-store view and hands it to go-ethereum's gasestimator.Estimate, which copies that nativeStateDB for every probe, so the whole search reads a single snapshot and inherits geth's plain-transfer short-circuit, UsedGas-based lower bound, optimistic guess, balance/fee-cap capping and 1.5% error ratio. The estimator only needs a *types.Header and a core.ChainContext, both built from the same BlockContext Call uses; the one intentional gap is BLOBBASEFEE, which is the EIP-4844 minimum rather than the context's value because a header cannot express it directly. It is plumbed through evmOnlyApplication, Proxy, the RPC Environment and the Backend interface as an optional capability like EvmCall, and callAPI.EstimateGas only validates the block selector, fills call defaults with a zero gas so an omitted gas bounds by the block gas limit, caps by defaultCallGasCap, and maps vm.ErrExecutionReverted (with or without data) to the same code-3 revertError as eth_call.

Read-only and consensus-neutral; the only behaviour change is that eth_estimateGas now exists (the TestHandlerRejectsUnknownMethods probe moved to eth_getStorageAt). Executor tests deploy bytecode into an in-memory store and assert plain-transfer cost, convergence against Call, block-limit and cap bounds, revert with and without data, block-context parity with Call, no state mutation, timeout and cancellation; the RPC tests assert the selector, gas and error mapping against a stub backend and the JSON-RPC shape over a real ethrpc handler.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

github-actions Bot commented Sep 23, 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 24, 2026, 2:23 PM

@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.90244% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.45%. Comparing base (812105d) to head (1a94fd7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
giga/evmonly/estimate.go 92.68% 3 Missing ⚠️
sei-tendermint/internal/evmonlyapp/app.go 91.66% 1 Missing ⚠️
sei-tendermint/internal/rpc/core/mempool.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4312      +/-   ##
==========================================
- Coverage   67.54%   66.45%   -1.09%     
==========================================
  Files        2181     2062     -119     
  Lines      167832   155885   -11947     
==========================================
- Hits       113359   103597    -9762     
+ Misses      54463    52278    -2185     
  Partials       10       10              
Flag Coverage Δ
sei-chain-pr 77.94% <93.90%> (?)
sei-db 74.50% <ø> (ø)
sei-db-state-db ?

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

Files with missing lines Coverage Δ
giga/evmonly/rpc/call.go 95.83% <100.00%> (ø)
giga/evmonly/rpc/estimate.go 100.00% <100.00%> (ø)
giga/evmonly/rpc/server.go 23.25% <ø> (ø)
sei-tendermint/internal/proxy/proxy.go 95.12% <100.00%> (+0.31%) ⬆️
sei-tendermint/internal/evmonlyapp/app.go 85.65% <91.66%> (+0.45%) ⬆️
sei-tendermint/internal/rpc/core/mempool.go 51.83% <0.00%> (-0.28%) ⬇️
giga/evmonly/estimate.go 92.68% <92.68%> (ø)

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

Base automatically changed from masih/1790165996-evmonly-get-code to main September 23, 2026 17:04
@devin-ai-integration
devin-ai-integration Bot force-pushed the masih/1790168306-evmonly-estimate-gas branch from c201c68 to 965c679 Compare September 23, 2026 17:04
@masih
masih marked this pull request as ready for review September 24, 2026 11:56
@cursor

cursor Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Read-only RPC surface, but gas estimation drives repeated EVM execution against live committed state and must match geth semantics (reverts, caps, timeouts) to avoid wrong tx gas or DoS via expensive estimates.

Overview
Adds eth_estimateGas to the EVM-only Giga JSON-RPC so clients (cast, ethers, viem, sei-load) can size transactions without hard-coded gas limits.

Executor (giga/evmonly/estimate.go): new EstimateGas runs go-ethereum’s gasestimator.Estimate against a single committed state snapshot (read-only), with the same 1.5% error ratio as geth, callTimeout handling, and minimal chain/header stubs so block opcodes match eth_call (including EIP-4844 blob base fee for BLOBBASEFE).

RPC (callAPI.EstimateGas): mirrors eth_call—requireCurrentState, CallDefaults / defaultCallGasCap, omitted gas left at zero so the estimator can use the block gas limit—and maps execution reverts to the existing code-3 revertError. newRevertError now takes raw revert bytes (shared by call and estimate).

Stack wiring: Backend.EvmEstimateGas through evmonly app (callContext shared with EvmCall), ABCI proxy, and Tendermint Environment. Autobahn README documents behavior; unknown-method test now probes eth_getStorageAt instead of eth_estimateGas.

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

@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.

This adds a well-tested eth_estimateGas to the EVM-only Giga RPC, modelled on go-ethereum's gas estimator. I found no blocking problems. Two suggestions: the block gas limit is skipped when the caller omits gas, and the search's probes don't share one state snapshot.

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

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] Each probe runs through backend.EvmCall, which uses whatever committed state and block context are latest when it runs. The balance read in estimateUpperBound works the same way. If a block commits during the search, the passing and failing bounds come from different states, so the result can be a limit that never succeeded against any single state. go-ethereum avoids this by running the whole estimate against one state. Consider fixing the state/height once per EstimateGas call if the backend can do it, or at least document the limitation. (Also raised by Codex.)
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/rpc/estimate.go Outdated

@shemnon shemnon 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.

Requesting changes. v2 calls geth's estimator; this copies it, and the copy is already drifting. Reuse gasestimator.Estimate / export.DoEstimateGas, or say exactly why this backend cannot.

// of gas against the current committed state. Like eth_call it creates no
// transaction and persists no state change; a call that reverts even at the
// highest allowed gas returns the revert error rather than an estimate.
func (api *callAPI) EstimateGas(ctx context.Context, args export.TransactionArgs, block *ethrpc.BlockNumberOrHash) (hexutil.Uint64, error) {

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.

v2's eth_estimateGas is export.DoEstimateGas, which calls gasestimator.Estimate. "DoEstimateGas needs an ethapi.Backend" is not yet a reason to copy the search: Estimate takes a core.Message and gasestimator.Options, not that backend. If EvmCall cannot supply the State, Header, and Chain those options need, say so. Otherwise call it.

The copy is already off geth. execute there only treats ErrIntrinsicGas as "raise the limit" and returns every other error; this treats ErrFloorDataGas as raisable too.

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.

Done in 1a94fd7: the search is gone. Executor.EstimateGas (giga/evmonly/estimate.go) opens one committed store view, wraps it in a nativeStateDB and calls gasestimator.Estimate with a *types.Header and a minimal core.ChainContext built from the same BlockContext Call uses (a test asserts NUMBER/TIMESTAMP/PREVRANDAO/GASLIMIT/BASEFEE/COINBASE/BLOCKHASH parity). The RPC layer only validates the selector, fills defaults, caps by defaultCallGasCap and maps the estimator error.

Comment thread giga/evmonly/rpc/estimate.go Outdated
return 0, err
}
if failed {
if len(result.Revert()) > 0 {

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.

A bare revert() has empty revert data. Geth and v2 still return code 3 with data: "0x" because they key off ErrExecutionReverted. This builds revertError only when result.Revert() is non-empty, so the same call is a plain -32000 execution reverted with no data. Match geth: code 3 whenever the error is ErrExecutionReverted.

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.

Done in 1a94fd7: vm.ErrExecutionReverted from the estimator now maps to the code-3 revertError regardless of data length, so a bare revert() returns {code: 3, data: "0x"}; covered at the API level and over a real ethrpc handler.

Comment thread giga/evmonly/rpc/estimate.go Outdated
func (api *callAPI) executeWithGas(ctx context.Context, msg *core.Message, gas uint64) (failed bool, result *core.ExecutionResult, err error) {
attempt := *msg
attempt.GasLimit = gas
result, err = api.backend.EvmCall(ctx, &attempt)

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.

Add a TODO for the block overrides geth applies in BlockOverrides.Apply: number, difficulty, time, gasLimit, feeRecipient, prevRandao, baseFeePerGas, blobBaseFee. Geth rejects beaconRoot and withdrawals on this method, so leave those out.

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.

Done in 1a94fd7: TODO on EstimateGas listing number, difficulty, time, gasLimit, feeRecipient, prevRandao, baseFeePerGas and blobBaseFee.

Comment thread giga/evmonly/rpc/estimate.go Outdated
ethrpc "github.com/ethereum/go-ethereum/rpc"
)

// estimateGasErrorRatio is the relative gap between the highest failing and

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.

The comments restate the code. Keep the geth match and the zero-gas CallDefaults trick. Drop the search narration (63/64, geometric mid, "one execution settles it").

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.

Done in 1a94fd7: the search and its comments are gone with the swap to gasestimator.Estimate; the zero-gas CallDefaults comment is the one that remains.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Swapped to gasestimator.Estimate in 1a94fd7. Executor.EstimateGas opens a single committed snapshot for the whole search and lets the estimator copy the nativeStateDB per probe, which also settles the shared-snapshot finding from the earlier review (review 5304077926). The other three comments are addressed in the same commit.

@masih

masih commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

Replaced by #4325 for better giga implementation alignment; shoutout to @shemnon 🙌

@masih masih closed this Sep 24, 2026
revofusion pushed a commit to revofusion/sei-chain that referenced this pull request Sep 25, 2026
…ol#4325)

## Summary
- Adds `eth_estimateGas` to the EVM-only Giga RPC by calling the
standard `gasestimator.Estimate` library function directly, the same
function the mainline (Cosmos-backed) EVM module's `eth_estimateGas`
already uses, instead of the hand-rolled bisection search in sei-protocol#4312
(which had known correctness bugs and was not merged).
- `Executor.EstimateGas` (`giga/evmonly/estimate.go`) opens one state
snapshot for the whole search — `gasestimator.Estimate` holds it across
every probe rather than reopening state per probe — and builds the
minimal `*types.Header` / `core.ChainContext` / consensus `Engine`
adapter `gasestimator` needs, reproducing `buildBlockContext`'s existing
semantics (zero Coinbase, parent-hash-only `BLOCKHASH`).
- Threads a new `EvmEstimateGas` backend primitive through
`evmOnlyApplication` → `Proxy` → `Environment`, mirroring the existing
`EvmCall` wiring.
- Fixes two bugs found in review before merge:
- `gasestimator.Estimate` never checks whether a probe's EVM was
cancelled; the vendored interpreter only samples cancellation at
`JUMP`/`JUMPI` and clears it to a normal halt, so a probe cut off by the
search's deadline could read back as a successful low estimate. Now
checked explicitly after `Estimate` returns.
- `BLOBBASEFEE` panicked (nil `*uint256.Int` push) because the header
carries no `ExcessBlobGas` and a non-blob call leaves `BlobGasFeeCap`
nil; `gasestimator`'s panic recovery turned that into a permanent "not
enough gas". Fixed by defaulting `BlobGasFeeCap` to zero on a copy of
the message, matching the literal zero `buildBlockContext` already uses
for `eth_call`.
- Adds a table-driven test covering every context-reading opcode in the
`0x30`-`0x4a` range (not just `BLOBBASEFEE`) to guard against the same
class of bug recurring.

## Test plan
- [x] `go build ./...`
- [x] `gofmt -s -l` / `goimports -l` clean on all touched files
- [x] `go test ./giga/evmonly/...
./sei-tendermint/internal/evmonlyapp/...
./sei-tendermint/internal/proxy/...
./sei-tendermint/internal/rpc/core/...` — all green
- [x] Manually reverted each fix and confirmed the corresponding new
test fails, then restored and confirmed it passes
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.

2 participants