Skip to content

eth_estimateGas intermittently returns incorrect results / reverts on v1.36.0 (regression from v1.35.x) #10552

@smartprogrammer93

Description

@smartprogrammer93

Description

eth_estimateGas intermittently fails or returns incorrect results on Nethermind v1.36.0. The same calls succeed consistently on v1.35.7/v1.35.8. Multiple independent node operators have confirmed this regression.

Steps to Reproduce

  1. Run Nethermind v1.36.0
  2. Send eth_estimateGas RPC calls under normal load
  3. Observe intermittent failures — the same call may succeed on retry
  4. Optionally: run v1.35.7/v1.35.8 side-by-side and send identical calls to both to confirm discrepancy

Actual behavior

eth_estimateGas intermittently returns a gas estimate of 0 or fails with one of:

Code: -32000 Message: execution reverted

Code: -32000 Message: Transaction execution fails

Failures are non-deterministic — retrying the same call moments later often succeeds. A side-by-side test of v1.35.7 and v1.36.0 against the same calls at the same time confirmed v1.36.0 sometimes returns 0 where v1.35.7 returns the correct estimate.

Expected behavior

eth_estimateGas should return consistent, correct gas estimates matching v1.35.x behavior.

Screenshots

Image

Desktop (please complete the following information):

• Operating System: Linux
• Version: v1.36.0 (regression not present in v1.35.7 / v1.35.8)
• Installation Method: Unknown
• Consensus Client: Unknown
• Network: Gnosis (at minimum, may affect others)

Node Config:

--config=gnosis
--datadir=/data
--log=INFO
--logger-config=/etc/nethermind/NLog.config
--Sync.SnapSync=true
--Sync.NetworkingEnabled=true
--JsonRpc.Enabled=true
--JsonRpc.Host=0.0.0.0
--JsonRpc.Port=8545
--JsonRpc.JwtSecretFile=/data/jwt.hex
--JsonRpc.EngineHost=0.0.0.0
--JsonRpc.EnginePort=8551
--Network.DiscoveryPort=30303
--Network.MaxActivePeers=20
--HealthChecks.Enabled=true
--Metrics.Enabled=true
--Metrics.ExposeHost=0.0.0.0
--Metrics.ExposePort=6060

Additional context

• Potentially related to PR #9822 (Allow Inner Reverts for eth_gasEstimate), which modified how contract reverts are handled during gas estimation. The original fix for #9064 may incorrectly treat inner reverts as top-level failures. Ben Adams identified this as a likely cause in Discord.
• Multiple independent node operators have downgraded to v1.35.8 as a workaround.

Script to reproduce:

import secrets
import sys
from datetime import datetime

from eth_account import Account
from web3 import Web3

rpc_node = "https://gnosis-node.localhost/"
SAFE_SINGLETON_ADDRESS = "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"
PROXY_FACTORY_ADDRESS = "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2"
FALLBACK_HANDLER_ADDRESS = "0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4"
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
PRIVATE_KEY = open("pk.txt").read().strip()
ACCOUNT = Account.from_key(PRIVATE_KEY)

# GnosisSafe setup function ABI
SAFE_SETUP_ABI = {
    "inputs": [
        {"internalType": "address[]", "name": "_owners", "type": "address[]"},
        {"internalType": "uint256", "name": "_threshold", "type": "uint256"},
        {"internalType": "address", "name": "to", "type": "address"},
        {"internalType": "bytes", "name": "data", "type": "bytes"},
        {"internalType": "address", "name": "fallbackHandler", "type": "address"},
        {"internalType": "address", "name": "paymentToken", "type": "address"},
        {"internalType": "uint256", "name": "payment", "type": "uint256"},
        {
            "internalType": "address payable",
            "name": "paymentReceiver",
            "type": "address",
        },
    ],
    "name": "setup",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function",
}

# GnosisSafeProxyFactory createProxyWithNonce function ABI
FACTORY_CREATE_PROXY_ABI = {
    "inputs": [
        {"internalType": "address", "name": "_singleton", "type": "address"},
        {"internalType": "bytes", "name": "initializer", "type": "bytes"},
        {"internalType": "uint256", "name": "saltNonce", "type": "uint256"},
    ],
    "name": "createProxyWithNonce",
    "outputs": [
        {"internalType": "contract GnosisSafeProxy", "name": "proxy", "type": "address"}
    ],
    "stateMutability": "nonpayable",
    "type": "function",
}


def get_estimation(rpc_url: str) -> int:
    web3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": 30}))
    safe_contract = web3.eth.contract(
        address=web3.to_checksum_address(SAFE_SINGLETON_ADDRESS),
        abi=[SAFE_SETUP_ABI],
    )
    factory_contract = web3.eth.contract(
        address=web3.to_checksum_address(PROXY_FACTORY_ADDRESS),
        abi=[FACTORY_CREATE_PROXY_ABI],
    )
    owner_address = web3.to_checksum_address(ACCOUNT.address)
    setup_call = safe_contract.functions.setup(
        [owner_address],
        1,
        web3.to_checksum_address(ZERO_ADDRESS),
        b"",
        web3.to_checksum_address(FALLBACK_HANDLER_ADDRESS),
        web3.to_checksum_address(ZERO_ADDRESS),
        0,
        web3.to_checksum_address(ZERO_ADDRESS),
    )
    initializer = setup_call._encode_transaction_data()
    salt_nonce = secrets.SystemRandom().randint(0, 2**256 - 1)
    return factory_contract.functions.createProxyWithNonce(
        web3.to_checksum_address(SAFE_SINGLETON_ADDRESS),
        initializer,
        salt_nonce,
    ).estimate_gas({"from": owner_address})


if __name__ == "__main__":
    for attempt in range(250):
        try:
            gas = get_estimation(rpc_node)
            prefix = f"[{datetime.now()}] Gnosis"
            print(f"{prefix} Gas Estimation (Attempt {attempt}): {gas}")
        except Exception as e:
            prefix = f"[{datetime.now()}] Gnosis"
            print(f"{prefix} Failed to estimate gas on attempt {attempt}: {e}")
            sys.exit(1)

Metadata

Metadata

Assignees

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