Skip to content

Exact eth_estimateGas limit can revert valid addTransaction; no CLI gas override #402

Description

@Leokings

Summary

On Bradbury, genlayer write used the exact eth_estimateGas result as the outer EVM gas limit for an addTransaction call. The outer transaction reverted twice before GenVM, while identical calldata succeeded when replayed with a larger explicit gas limit. The CLI currently exposes no gas-limit override for this path.

Environment

  • genlayer CLI: 0.39.2 (current stable npm release)
  • Bundled genlayer-js: 1.1.8
  • Node.js: v24.13.0
  • OS: Windows
  • Network: testnet-bradbury, EVM chain ID 4221
  • ConsensusMain: 0x0112Bf6e83497965A5fdD6Dad1E447a6E004271D
  • Intelligent Contract: 0x91d6E838b93c9CCA32Ab964d096949210F60b1DF
  • Method: submit_case_base64

Observed behavior

The corrected submission produced these outer EVM transactions:

  1. 0x07f4f7c04caa496243b0505626def616842b73a23d1baf9a691c1d79048a3294 — reverted
  2. 0xcc4a5f0627f39dadd850ce8afe4538a5379eca73c66b8cf34a15b28218389554 — reverted

For the second transaction:

  • gas limit: 1,319,997
  • gas used: 1,260,766
  • calldata size: 868 bytes
  • calldata Keccak-256: 0x8da473d71d9ab2a909d285661295e6c3c7bae665d10e5bd93e6cec8a3961c094
  • a fresh eth_estimateGas call still returns exactly 1,319,997

Replaying the exact same destination, value, and calldata with a 2,000,000 gas limit succeeded:

  • outer EVM tx: 0x5ea3f6aa6f571357700b59edee9ba98787083f89626bea01656dc4add1df6cbc
  • gas used: 1,254,917
  • calldata Keccak-256: 0x8da473d71d9ab2a909d285661295e6c3c7bae665d10e5bd93e6cec8a3961c094
  • resulting GenLayer tx: 0x50201a17859f57a7787241d683fbefa7da8b0a93d9e1410714ace1ccd6e2a4bc
  • resulting GenLayer transaction is now FINALIZED

The reverted outer transactions emitted no GenLayer transaction and changed no Intelligent Contract state.

Full public test record: https://github.com/Leokings/digital-deliverable-verifier/blob/main/docs/BRADBURY_TEST_RECORD.md

Expected behavior

Either:

  1. apply a safety margin above eth_estimateGas for consensus submissions; and/or
  2. expose a --gas option on deploy and write and pass it through to the SDK transaction request.

The CLI should also retain the outer EVM hash and provide a clear diagnostic when the transaction reverts before GenVM.

Impact

This does not alter Intelligent Contract logic or validator consensus, but it can make an otherwise valid write fail before reaching GenVM. Users pay gas for the reverted outer transaction, automated integrations can fail repeatedly, and the current CLI offers no direct recovery path other than using a separate signer/SDK path.

Activity

  1. Leokings commented on Jul 31, 2026

    @Leokings
    Author

    Additional source audit: the root behavior is in genlayer-js 1.1.8, which is bundled by stable CLI 0.39.2.

    A 2× headroom implementation has already been merged on unreleased SDK branches:

    CLI 0.40.0-rc2 also pins an SDK commit containing the 2× helper:

    So the remaining request is a stable 0.39.x/Bradbury backport or release, plus ideally a CLI --gas/ --gas-limit escape hatch. I am not recommending that production users switch to the breaking prerelease solely for this workaround.

  2. ygd58 commented on Aug 7, 2026

    @ygd58

    Opened two PRs covering both parts of the ask:

    One useful finding along the way: this repo already has a 2x gas-headroom safety margin implemented (withTransactionGasHeadroom in genlayer-js's source), which would have covered the exact numbers in this report (1,319,997 estimated × 2 = 2,639,994, comfortably above the 2,000,000 that worked). I checked the actual published genlayer-js@1.1.8 npm tarball directly and confirmed that headroom code isn't in it yet — so part of this may resolve itself once a new version is published, independent of these two PRs.

  3. Snehal707 commented on Sep 11, 2026

    @Snehal707

    Additional Bradbury reproduction, related to the gas-estimation failure but with a separate nonce/RPC symptom.

    Environment:

    • Bradbury, chain ID 4221
    • CLI 0.39.2
    • bundled genlayer-js 1.1.8
    • ConsensusMain: 0x0112bf6e83497965a5fdd6dad1e447a6e004271d
    • Governor target: 0x36b49eFFd0b9d5C47D8Cf93734BE34b911a6c3C9
    • upgrade calldata: approximately 41,157 bytes

    Gas-estimation reproduction:

    • eth_estimateGas returned approximately 32,489,986 for the exact upgrade calldata.
    • 0xa5a7b89c9d41edfd70b98ba771bfa1c549b009208d8b680f1eb4c93a8a972cb9 used a 10,000,000 gas limit, consumed 9,317,134 gas, and reverted before creating a GenLayer transaction.

    Separate nonce/pool symptom:

    • 0x0ef10e15dbca0bf1183770ab6fe7fb7792aa0639f05e0378ff87de217d5f553a became unretrievable.
    • Nonce 14 was observed with successive occupants 0x315b764536a902535f9a443d43037bdef82ad29ebdf88176829afe511ef67fa3 and 0x48c90dea913adee5ed0f69171b5030c06e2e5c7280f76164472822a79377e3a3.
    • A replacement 0x71d8b36664cb644ea1c788cde80ef87488043861e6f3cd4882a07cac98c861c6 was submitted at nonce 14 with 35,000,000 gas and a higher gas price, then also became unretrievable.
    • During these transitions eth_getTransactionCount reported latest=14 and pending=15, while eth_getTransactionBySenderAndNonce(address, 0xe) eventually returned null.
    • txpool_content, txpool_inspect, and eth_pendingTransactions were unavailable on the Bradbury RPC.

    We also audited genlayer-js 1.1.8 directly: the Intelligent Contract write path calls eth_getTransactionCount(address, "pending") freshly for each submission, with no nonce cache, stale increment, or retry/backoff. So this symptom is not explained by SDK nonce caching and appears to be a separate Bradbury RPC/pool behavior.

    Question: does Bradbury support standard EVM replacement-by-fee semantics? If not, what is the supported procedure for clearing or replacing a dropped nonce-14 transaction when pending remains 15?

  4. ygd58 commented on Sep 11, 2026

    @ygd58

    Tried a minimal, direct test of the RBF question using our own funded test wallet on Bradbury (plain EIP-1559 self-transfer, not the governance/upgrade path from your report) — sharing the result since it's a data point either way, though it didn't reproduce the "unretrievable" symptom.

    Test: signed two type-2 txs at the identical nonce with eth_account (Python) — one at the current eth_gasPrice, one at 3x that — then submitted the low-fee one, waited ~3s, and submitted the "replacement":

    [LOW]  nonce=3 gasPrice=163783200  -> submitted OK
    [HIGH] nonce=3 gasPrice=491349600  -> rejected: "transaction nonce is not consistent: next nonce 4, tx nonce 3"
    

    The LOW tx had already confirmed (status: 0x1, block included) by the time the replacement landed — Bradbury's confirmation was fast enough that our 3s gap wasn't a real race. latest/pending stayed consistent (4/4) throughout a 40s poll afterward, no drift like the 14 vs 15 you saw.

    So this specific test shows: a same-nonce resubmission after the original already confirmed gets a clean, correctly-worded rejection — not the silent "vanishes from the pool, nonce count doesn't catch up" behavior you're describing. It doesn't rule out standard RBF working or not working while the original is genuinely still pending (our original never stayed pending long enough to test that), and it's plain-transfer calldata (~0 bytes) vs. your ~41KB governance upgrade payload — size or the specific ConsensusMain/governor contract path could plausibly matter here in ways a trivial self-transfer wouldn't surface.

    Not a full answer to your RBF question, but ruling this simpler case in as "working correctly" narrows where to look next if anyone wants to chase the actual stuck-nonce-14 scenario further.

  5. Snehal707 commented on Sep 15, 2026

    @Snehal707

    Follow-up: confirmed Bradbury per-transaction gas ceiling below 16,000,000

    This is a distinct, larger platform finding from the earlier 10M-underestimate reproduction. After confirming the upgrader nonce was clear (latest=14, pending=14), we tested the current Governor upgrade source (46,535 bytes; patched source at commit ad8fd2d) with explicit outer gas limits:

    • gasLimit=16,777,216: rejected by Bradbury as "gas limit too high" before acceptance.
    • gasLimit=16,000,000: rejected by Bradbury as "gas limit too high" before acceptance; returned hash 0x88f803e1e5e34ad2203a0ad4bf6c52e225b381d32becb0215b1f05c5995e6239, but eth_getTransactionByHash and the receipt lookup both returned null.

    The earlier automatic/current-path attempt was also rejected as "gas limit too high" (returned hash 0xdddbe9ca4d7a7de77f931b718d46cf4fcff43b7095273d9a01b1d4bba2920dbc). After the explicit 16M rejection, the account remained latest=14, pending=14, confirming these attempts did not enter the chain/pool. No Intelligent Contract state changed.

    The upgrade requires materially more gas than this ceiling, so this is not a small tuning issue: a sub-16M Bradbury ceiling may block sizably-sized Intelligent Contract deployments or upgrades generally, not only this Governor. The upgrade payload is approximately 41KB.

    Could you confirm Bradbury's actual per-transaction gas ceiling and the supported path for Intelligent Contract deployments/upgrades that require more than it? Is there a protocol-specific chunking or upgrade route, or documented size/gas guidance?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions