Skip to content

cartesi-rollups-cli hardcodes a 30M gas limit, breaking transactions on networks with a lower per-tx gas cap #786

Description

@vfusco

Summary

When sending transactions, the CLI/ethutil package hardcodes the gas limit to 30_000_000 instead of letting go-ethereum estimate it. On networks (testnets / L2s) that enforce a maximum gas-per-transaction below 30M, the node rejects the transaction during pre-flight validation with exceeds max transaction gas limit, so it never reaches a block. The fix is to default to gas estimation and expose an optional override flag.

Current behavior

pkg/ethutil/ethutil.go defines a fixed constant:

// Gas limit when sending transactions.
const GasLimit = 30_000_000

Every write path (AddInput, AddInputAsync, ExecuteOutput) forwards this constant, and pkg/ethutil/transaction.go writes it unconditionally onto the transaction options:

// _prepareTransaction
txOpts.GasLimit = gasLimit   // always 30_000_000
txOpts.GasPrice = gasPrice

Because go-ethereum's bind package only runs gas estimation when TransactOpts.GasLimit == 0, setting a non-zero value here disables estimation entirely and forces the fixed 30M onto every transaction. 30_000_000 happens to be Ethereum mainnet's block gas limit, which is fine on Anvil/Foundry but too high for networks that cap per-transaction gas below that.

Relevant code:

  • pkg/ethutil/ethutil.go#L23-L24 (the constant)
  • pkg/ethutil/transaction.go#L46-L68 (_prepareTransaction setting the limit)

Steps to reproduce

Against a testnet/L2 whose per-transaction gas cap is below 30M:

$ cartesi-rollups-cli execute tester 12 --yes
Error: failed to send transaction: exceeds max transaction gas limit

$ cartesi-rollups-cli send tester 0x68656c6c6f --hex --yes
Error: failed to send transaction: exceeds max transaction gas limit

Both commands fail identically: executeExecuteOutput, sendAddInput, both routed through sendTransaction with the same hardcoded GasLimit.

Root cause

The hardcoded GasLimit = 30_000_000 exceeds the maximum gas allowed per transaction on the target network. The node rejects it at the RPC/txpool validation step (before execution), so this is not a revert, an out-of-gas, or an insufficient-funds error — it's the network refusing a transaction whose declared gas limit is too high.

Proposed fix

  1. Default to estimation. Stop setting a fixed gas limit; leave TransactOpts.GasLimit = 0 so go-ethereum's bind runs eth_estimateGas (simulating against pending state) and picks an appropriate limit per transaction and per network. Concretely, drop the txOpts.GasLimit = gasLimit assignment (or set it to 0) in _prepareTransaction for the default path.
  2. Add an optional override. Expose a --gas-limit flag (and/or an env var) on the relevant CLI commands. When provided and > 0, set TransactOpts.GasLimit to that value (skipping estimation); when omitted, fall through to estimation. This preserves the ability to force a fixed limit when needed.

Additional considerations

  • Enabling estimation slightly changes the error surface: a transaction that would always revert now fails earlier at the eth_estimateGas step instead of after submission. This is generally an improvement (bad transactions are caught before broadcast), but worth noting for anyone parsing error strings.
  • Optionally apply a small safety margin on top of the estimate (e.g. estimate × 1.1) to absorb minor state changes between estimation and inclusion. go-ethereum already allows a tiny overestimate, so this is a refinement rather than a requirement.
  • AddInputAsync already carries a note about serializing nonce assignment; switching to estimation adds one extra RPC round-trip (eth_estimateGas) per call, which is fine for normal use but worth keeping in mind for the load-testing path.

Acceptance criteria

  • Transactions sent by the CLI succeed on networks whose per-transaction gas cap is below 30M (no more exceeds max transaction gas limit).
  • Gas limit is estimated by default when no override is provided.
  • A --gas-limit flag (and/or env var) allows forcing a fixed limit.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions