Skip to content

eth_estimateGas: optimistic-try multiplier reuses the binary-search-stop margin instead of Geth's EIP-150 64/63 budget #12087

Description

@manusw7

Description

GasEstimator.cs uses a single constant for two different purposes:

  1. The optimistic-guess ceiling — what gas value to try first to short-circuit the binary search.
  2. The binary-search-stop threshold — how tight the (left, right) bracket must get before accepting the result.

Both come from marginMultiplier = errorMargin / 10000 + 1 = 1.015. Geth uses two separate constants:

  • Optimistic guess: (MaxUsedGas + CallStipend) * 64/63 — compensates for EIP-150's 1/64 child-call reservation (eth/gasestimator/gasestimator.go:154).
  • Binary-search stop: ErrorRatio = 0.015 (= 150 bps).

NM's optimistic multiplier (1.015) is smaller than Geth's (≈1.01587), so NM's binary search starts from a lower upper bound and settles 2-4 gas under Geth on typical contract calls.

This is not a correctness bug — NM's estimates are still safely above intrinsic — but it causes constant low-grade divergence on every cross-client comparison involving contract execution.

Steps to Reproduce

Any contract call that exercises EIP-150's child-call budget reproduces the divergence:

{
  "jsonrpc": "2.0",
  "method": "eth_estimateGas",
  "params": [{"from": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "to": "0x2525680000000000000000000000000000000000", "data": "0x..."}, "latest"],
  "id": 1
}

Expected (Geth)

{"jsonrpc":"2.0","id":1,"result":"0x5309"}

Actual (Nethermind v1.39.0-rc+c6739713)

{"jsonrpc":"2.0","id":1,"result":"0x5307"}

Consistently 2-4 gas lower than Geth across all affected fixtures.

EIP-7702 SetCodeTx variant (test_32)

The same multiplier gap surfaces on SetCodeTx transactions. A type 0x4 transaction with
an authorizationList but no calldata:

{
  "jsonrpc": "2.0",
  "method": "eth_estimateGas",
  "params": [
    {
      "from": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
      "to": "0xd3CdA913deB6f4967b2Ef3aa68f5A843aFB83557",
      "type": "0x4",
      "authorizationList": [
        {
          "chainId": "0x1",
          "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
          "nonce": "0x1",
          "yParity": "0x1",
          "r": "0xa8f2b4c1d3e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
          "s": "0x1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
        }
      ]
    },
    "latest"
  ],
  "id": 1
}

Expected (Geth): "0xb52e" (46382) — Actual (NM): "0xb3b0" (46000). The 382-gas gap is the optimistic-multiplier delta applied to the 46000 floor (21000 intrinsic + 25000 for one authorization).

Fix Location

src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs:

  • Add private const double OptimisticMultiplier = 64d / 63d; (mirrors Geth's EIP-150 budget compensation).
  • In BinarySearchEstimate (around line 148): rename marginMultiplier to errorRatio = errorMargin / BasisPointsDivisor and use it directly as the ShouldContinueSearch threshold.
  • In the call to TryOptimisticEstimate (around line 150): pass OptimisticMultiplier instead of the merged marginMultiplier.
  • Rename the parameter on TryOptimisticEstimate (around line 170) to optimisticMultiplier for clarity.

Three-line semantic change; existing GasEstimator unit tests still pass.

Notes

  • Cross-client triangulation: Erigon, Reth, and Besu all follow Geth's 64/63 + separate-error-ratio pattern. NM is the outlier.
  • Covers 9 fixtures in integration/mainnet/eth_estimateGas/:
    • test_13, test_15, test_16, test_18, test_20, test_24, test_25, test_26 — contract calls, 2-4 gas under Geth.
    • test_32 — EIP-7702 SetCodeTx, 382 gas under Geth ("0xb52e""0xb3b0").
  • All 9 flip to PASS when the OptimisticMultiplier = 64d/63d fix lands.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions