feat(evmonly): add eth_gasPrice and eth_feeHistory to the EVM-only executor - #4220
Conversation
…ecutor Exposes the application's fixed admission gas-price floor through the same optional-interface plumbing as EvmBaseFee/EvmGasLimit (Proxy -> Environment -> Backend), and reuses eth_getBlockByNumber's height resolution to walk a range of blocks for fee history. - eth_gasPrice suggests 10% above the admission floor - eth_feeHistory reports real per-block gasUsedRatio and always-zero baseFeePerGas; reward is a documented simplification (fixed at the floor for every requested percentile), since this chain has no congestion-based fee market - extracts blockAPI.resolveBlockByNumber into a package-level helper shared with the new range walk
…ry end, harden edges - Must: eth_feeHistory's reward was the raw admission floor while eth_gasPrice suggested floor*1.1; an EIP-1559 wallet computing maxFeePerGas from reward would construct a transaction at exactly the rejection boundary. Both now share one suggestedGasPrice helper. - Should: eth_feeHistory's lastBlock now errors on a future or pruned height instead of silently returning an empty success result, and resolves "earliest" to height 1 (this executor's first committed height) instead of the literal 0 this go-ethereum fork uses for the sentinel, which this executor never commits. eth_getBlockByNumber's null-for-unavailable semantics are unchanged; only feeHistory's own resolution changed. - Should: walkFeeHistoryRange now checks ctx cancellation each iteration. Clamping the walk to the store's earliest retained height is left for when a lower-bound accessor exists (same gap already tracked in sei-tendermint/internal/rpc/core/blocks.go's autobahnCheckAndGetHeight) -- building that here would be new infrastructure, not a review fix. - Nit: fixedReward now copies the suggested price per cell instead of aliasing one *big.Int across every entry. - Nit: lastTxGasUsedRatio now ignores a receipt whose stored block number doesn't match the queried height, the same defense v2 has against a resubmitted tx hash overwriting an earlier receipt. - Nit: EvmMinGasPrice gets the same Proxy success/failure tests EvmBaseFee and EvmGasLimit already have.
Rationale for the deferred pruning-floor clamp belongs in the prior commit message, not a code comment.
fixedReward and block.go's fullTx receipt-per-transaction loop are deferred for the same reason: no bulk/range receipt read to build a real per-transaction value from.
PR SummaryMedium Risk Overview
Autobahn README documents Reviewed by Cursor Bugbot for commit d086104. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
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 @@
## giga-1 #4220 +/- ##
==========================================
+ Coverage 65.55% 65.57% +0.01%
==========================================
Files 2081 2083 +2
Lines 157460 157472 +12
==========================================
+ Hits 103222 103256 +34
+ Misses 54097 54075 -22
Partials 141 141
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
eth_gasPrice and eth_feeHistory are implemented cleanly on the existing optional-provider plumbing pattern, with good test coverage and no blocking defects. Four non-blocking findings: a hardcoded earliest height that breaks on a chain with InitialHeight > 1, a null wire shape for the empty result, request amplification on the public RPC port, and two undocumented gasUsedRatio approximations.
Findings: 0 blocking | 5 non-blocking | 3 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- [suggestion] The README documents the
baseFeePerGasandrewardsimplifications but not twogasUsedRatioones: the denominator isEvmGasLimit()— the current committed block's limit, derived from consensusblock.max_gas— applied to every historical block in the range, so ratios skew if that parameter ever changes; and a block whose last-transaction receipt is missing or block-number-mismatched reports ratio0, indistinguishable from an empty block. The second is a deliberate guard, but it makes "no data" look like "no gas used" — the exact conflation the ending-block error path was added to avoid. Worth a sentence each alongside the existing limitation notes. - 3 suggestion(s)/nit(s) flagged inline on specific lines.
- 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.
Pre-existing issues
- [suggestion]
blockAPI.receiptFor(giga/evmonly/rpc/block.go:190) has nostored.BlockNumber != block heightcheck, soeth_getBlockByNumber'sgasUsedcan be taken from a resubmitted transaction hash's receipt written by a different block. This PR'slastTxGasUsedRatioadds exactly that guard foreth_feeHistory, leaving the two paths inconsistent; the block path predates this change.
|
|
||
| // maxFeeHistoryBlockCount caps a single eth_feeHistory request, matching | ||
| // go-ethereum's own default block-count cap. | ||
| const maxFeeHistoryBlockCount = 1024 |
There was a problem hiding this comment.
[suggestion] The 1024 cap matches go-ethereum's number, but not its cost: geth serves the range from an LRU header/block cache, whereas walkFeeHistoryRange issues one backend.Block full-block load per height — each through Proxy's timing-instrumented path — plus a receipt read for every non-empty block. One unauthenticated request on 0.0.0.0:8545 therefore fans out to ~1024 store reads sequentially, with nothing else bounding concurrent requests. The ctx.Err() check per iteration limits the damage from a client that disconnects, but not from one that waits. Consider a lower cap for this executor until a bulk/header-only read exists.
There was a problem hiding this comment.
Disagreeing, not lowering the cap: 1024 already matches go-ethereum's own default, and this repo's own production evmrpc config (MaxBlocksForLog, fed into the identical maxBlocks parameter) defaults to 2000 — higher, not lower. The real gap is the missing cache, not the cap number; picking an arbitrary smaller value wouldn't close that, and would make this devnet-facing surface stricter than both stock geth and this repo's own production default for the same knob. A cache or the bulk/header-only read (already tracked elsewhere as deferred) is the real fix.
…ocument ratio caveats - eth_feeHistory's empty result now matches go-ethereum's shape (oldestBlock 0x0, empty gasUsedRatio) instead of a zero-value struct with nil fields, which a strict client's unconditional BigInt(oldestBlock) would reject. - walkFeeHistoryRange's post-loop nil-OldestBlock fallback is now an error, not silent empty success; it was already unreachable except a store eviction racing the just-completed end-height resolution. - block.go's GetBlockByNumber gasUsed now ignores a receipt whose stored block number doesn't match the queried height, the same guard eth_feeHistory's lastTxGasUsedRatio already has, closing an inconsistency the reviewer flagged as pre-existing. - Documented two gasUsedRatio approximations in the README and inline: the current gas limit is applied to every historical block in the range, and a missing/stale receipt reads as 0, indistinguishable from an empty block. - earliestCommittedHeight's doc comment now states its height-1 assumption and why nothing currently exposes a real InitialHeight after restart, rather than silently asserting a fact that's false for a chain configured with InitialHeight > 1.
| // now since a block today is exactly one lane's transactions. | ||
| var gasUsed hexutil.Uint64 | ||
| if lastReceipt != nil { | ||
| if lastReceipt != nil && lastReceipt.BlockNumber == uint64(number) { //nolint:gosec // G115: number is a validated block height. |
Describe your changes and provide context
Adds
eth_gasPriceandeth_feeHistoryto the Autobahn EVM-only executor's RPC server, on top ofeth_getBlockByNumber/eth_getBlockByHash(#4208). Reviewed locally (Cursor) and fixed before opening.eth_gasPricereturns this application's fixed admission floor (1 gwei) plus 10%, via a newEvmMinGasPriceexposing the previously-privateevmOnlyMinGasPriceconstant, plumbed through the sameEvmBaseFee/EvmGasLimitoptional-interface patterneth_feeHistorywalks the requested block range reusingblock.go'sresolveBlockByNumber;gasUsedRatiocomes from the last transaction's receiptCumulativeGasUsed(equivalent to summing every tx, cheaper);reward, when requested, is the same suggested gas price for every percentile — a documented simplification, since the receipt store only supports point lookups (GetReceiptby hash), not the bulk/range read a real per-transaction percentile would need (same constraint noted inblock.go'sfullTxencoding)eth_gasPriceandeth_feeHistory'srewardare computed through one sharedsuggestedGasPricehelper, so they can't drift apartearliestresolves to height 1, this executor's first committed heightwalkFeeHistoryRangechecks context cancellation each iterationlastTxGasUsedRatioignores a receipt whose stored block number doesn't match the queried height, guarding against a resubmitted tx hash overwriting an earlier receipt (same defenseevmrpc's v2 path has)cast gas-price/cast rpc eth_feeHistoryexample, and therewardlimitation in the Autobahn READMETesting 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/...golangci-lint runv2.13.2 scoped to all touched packages: 0 issuesgolangci-lint fmt --diff: clean🤖 Generated with Claude Code