Repository navigation
Exact eth_estimateGas limit can revert valid addTransaction; no CLI gas override #402
Description
Activity
Leokings commented
on Jul 31, 2026 AuthorMore actionsAdditional source audit: the root behavior is in
genlayer-js 1.1.8, which is bundled by stable CLI0.39.2.A 2× headroom implementation has already been merged on unreleased SDK branches:
- v1 fix commit: genlayerlabs/genlayer-js@0edc3f8
- helper and 20,000-bps multiplier: https://github.com/genlayerlabs/genlayer-js/blob/0edc3f8fc62f062423ccbaf846e400dd717797c6/src/contracts/actions.ts#L1027-L1057
- application after
estimateTransactionGas: https://github.com/genlayerlabs/genlayer-js/blob/0edc3f8fc62f062423ccbaf846e400dd717797c6/src/contracts/actions.ts#L2000-L2009
CLI
0.40.0-rc2also pins an SDK commit containing the 2× helper:- RC2 release: https://github.com/genlayerlabs/genlayer-cli/releases/tag/v0.40.0-rc2
- pinned SDK commit: genlayerlabs/genlayer-js@5788928
- helper: https://github.com/genlayerlabs/genlayer-js/blob/57889281db1e34df49abcf6129ffe6f347e4de4d/src/contracts/actions.ts#L1225-L1256
- estimate application: https://github.com/genlayerlabs/genlayer-js/blob/57889281db1e34df49abcf6129ffe6f347e4de4d/src/contracts/actions.ts#L2323-L2331
So the remaining request is a stable
0.39.x/Bradbury backport or release, plus ideally a CLI--gas/--gas-limitescape hatch. I am not recommending that production users switch to the breaking prerelease solely for this workaround.Opened two PRs covering both parts of the ask:
- genlayer-js#205 — adds an explicit
gasoverride towriteContract/deployContract, bypassingeth_estimateGasentirely when set. - genlayer-cli#404 — adds the
--gasCLI flag todeploy/write, depends on chore(deps): update dependency node to v22 - autoclosed #205.
One useful finding along the way: this repo already has a 2x gas-headroom safety margin implemented (
withTransactionGasHeadroomin 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 publishedgenlayer-js@1.1.8npm 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.- genlayer-js#205 — adds an explicit
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?
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 currenteth_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/pendingstayed consistent (4/4) throughout a 40s poll afterward, no drift like the14vs15you 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.
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?
Summary
On Bradbury,
genlayer writeused the exacteth_estimateGasresult as the outer EVM gas limit for anaddTransactioncall. 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
genlayerCLI:0.39.2(current stable npm release)genlayer-js:1.1.8v24.13.0testnet-bradbury, EVM chain ID42210x0112Bf6e83497965A5fdD6Dad1E447a6E004271D0x91d6E838b93c9CCA32Ab964d096949210F60b1DFsubmit_case_base64Observed behavior
The corrected submission produced these outer EVM transactions:
0x07f4f7c04caa496243b0505626def616842b73a23d1baf9a691c1d79048a3294— reverted0xcc4a5f0627f39dadd850ce8afe4538a5379eca73c66b8cf34a15b28218389554— revertedFor the second transaction:
1,319,9971,260,766868bytes0x8da473d71d9ab2a909d285661295e6c3c7bae665d10e5bd93e6cec8a3961c094eth_estimateGascall still returns exactly1,319,997Replaying the exact same destination, value, and calldata with a
2,000,000gas limit succeeded:0x5ea3f6aa6f571357700b59edee9ba98787083f89626bea01656dc4add1df6cbc1,254,9170x8da473d71d9ab2a909d285661295e6c3c7bae665d10e5bd93e6cec8a3961c0940x50201a17859f57a7787241d683fbefa7da8b0a93d9e1410714ace1ccd6e2a4bcFINALIZEDThe 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:
eth_estimateGasfor consensus submissions; and/or--gasoption ondeployandwriteand 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.