Skip to content

fix: use latest block for eth_fillTransaction gas estimation#1138

Open
luchenqun wants to merge 11 commits into
cosmos:mainfrom
luchenqun:fix-fill-transaction-latest
Open

fix: use latest block for eth_fillTransaction gas estimation#1138
luchenqun wants to merge 11 commits into
cosmos:mainfrom
luchenqun:fix-fill-transaction-latest

Conversation

@luchenqun

Copy link
Copy Markdown

Summary

  • use EthLatestBlockNumber instead of numeric 0 when eth_fillTransaction estimates gas
  • keep eth_fillTransaction aligned with the latest state instead of block 1

Why

SetTxDefaults currently builds a BlockNumberOrHash with NewBlockNumber(big.NewInt(0)) before calling EstimateGas.
That is not equivalent to latest in this codebase:

  • BlockNumberFromComet returns the numeric block number unchanged
  • BlockNumber.Int64() maps numeric 0 to 1
  • so CometHeaderByNumber resolves block 1, not the latest block

Using EthLatestBlockNumber makes the call match the intended latest-state behavior.

Verification

  • go test ./rpc/backend -run TestDoesNotExist -count=0

Related: #1120

@luchenqun luchenqun requested a review from a team as a code owner April 25, 2026 03:44
@luchenqun

luchenqun commented Apr 25, 2026

Copy link
Copy Markdown
Author

@swift1337 the crux is in getHeightByBlockNum itself. Here is the relevant code path:

func (b *Backend) getHeightByBlockNum(ctx context.Context, blockNum rpctypes.BlockNumber) (height int64, err error) {
	ctx, span := tracer.Start(ctx, "getHeightByBlockNum", trace.WithAttributes(attribute.Int64("blockNum", blockNum.Int64())))
	defer func() { evmtrace.EndSpanErr(span, err) }()

	if blockNum == rpctypes.EthEarliestBlockNumber {
		status, err := b.ClientCtx.Client.Status(ctx)
		if err != nil {
			return 0, errors.New("failed to get earliest block height")
		}

		return status.SyncInfo.EarliestBlockHeight, nil
	}

	height = blockNum.Int64()
	if height <= 0 {
		// In cometBFT, LatestBlockNumber, FinalizedBlockNumber, SafeBlockNumber all map to the latest block height.

The important detail is that eth_fillTransaction was not passing EthLatestBlockNumber. It was passing plain numeric 0 via NewBlockNumber(big.NewInt(0)).

In this codebase, when blockNum is plain numeric 0, height = blockNum.Int64() becomes 1, not 0.
So if height <= 0 is never reached in that case. That behavior comes from rpc/types/block.go:

// Int64 converts block number to primitive type
func (bn BlockNumber) Int64() int64 {
	if bn < 0 {
		return 0
	} else if bn == 0 {
		return 1
	}

	return int64(bn)
}

That means the old flow is effectively:

  • eth_fillTransaction passes numeric 0
  • BlockNumberFromComet returns that numeric 0 unchanged
  • height = blockNum.Int64() turns it into 1
  • the height <= 0 fallback is skipped
  • the RPC resolves block 1, not latest

So the comment in getHeightByBlockNum is correct for the special negative enum values like EthLatestBlockNumber, but it does not apply to plain numeric 0.

This is also easy to verify in practice: if you deploy a contract after block 1 and then call eth_fillTransaction against that contract to estimate gas, the old path will still resolve block 1, so the estimation fails reproducibly.

@vladjdk

vladjdk commented Apr 27, 2026

Copy link
Copy Markdown
Member

@greptile review

@vladjdk vladjdk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should add a test for the intended behaviour here to prevent future regressions.

@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a bug in SetTxDefaults where gas estimation for eth_fillTransaction used NewBlockNumber(big.NewInt(0)), which resolves to block 1 instead of the latest block. The fix replaces it with EthLatestBlockNumber so gas is estimated against the current chain state.

Confidence Score: 5/5

Safe to merge — targeted one-line bug fix with no regressions expected.

The change is a single-line, well-understood bug fix. The old code resolved block 1 due to a quirk in BlockNumber.Int64() mapping numeric 0 to 1; using EthLatestBlockNumber correctly targets the latest state. No surrounding logic is changed and the fix directly matches the stated intent.

No files require special attention.

Important Files Changed

Filename Overview
rpc/backend/call_tx.go Single-line fix replacing NewBlockNumber(big.NewInt(0)) with EthLatestBlockNumber to correctly target the latest block for gas estimation.
CHANGELOG.md Adds a changelog entry for PR #1138 under the unreleased section.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Backend as rpc/backend
    participant EstimateGas
    participant Comet as CometHeaderByNumber

    Client->>Backend: eth_fillTransaction(tx without gas)
    Note over Backend: args.Gas == nil
    Backend->>EstimateGas: EstimateGas(callArgs, EthLatestBlockNumber)
    EstimateGas->>Comet: resolve "latest" block
    Comet-->>EstimateGas: current head header
    EstimateGas-->>Backend: estimated gas
    Backend-->>Client: filled transaction (with gas)
Loading

Reviews (1): Last reviewed commit: "Merge branch 'main' into fix-fill-transa..." | Re-trigger Greptile

@luchenqun luchenqun requested a review from vladjdk April 28, 2026 01:40
@luchenqun luchenqun changed the title Use latest block for eth_fillTransaction gas estimation fix: use latest block for eth_fillTransaction gas estimation Apr 28, 2026

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

fix is correct, but two issues:

ci has not run: all workflows are action_required -- fork pr, needs maintainer approval to trigger.

test doesn't pin the fix: the "estimate gas with latest block" case mocks EstimateGas with mock.AnythingOfType("*types.EthCallRequest"), which matches any request regardless of block number. the specific regression (block 1 vs latest) isn't asserted and could be reintroduced without breaking the test. assert that the BlockNumber on the EthCallRequest is EthLatestBlockNumber.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants