# reth-txcap — R-1 (Reth, EIP-8037 `TX_MAX_TOTAL_GAS_LIMIT` not enforced)

Pins used: reth `e0378eb691aa2770bb96fd4ecec2f4445a2a3e7c` (main, 2026-10-27 12:11Z); evm2 `4a53ec080dfbb904e98e3ca8f0abcc11ab8abae0` (reth `Cargo.toml` L441-443 and `[patch]` L676-679, DaniPopes/evm2 → alloy-rs/evm2); reth-core `33bfe6e8b7bacc4f2e464497262ee3f87c6c86ce` (`reth-primitives-traits` patch, reth `Cargo.toml` L688); execution-specs `7f2f4c9bf6d73b09ecb8b24eb14d08bf0cf0247b` (forks/bogota); EIPs `d4c57552`; Geth `8817f99f`. All code was read from shallow clones or raw.githubusercontent.com. Nothing was run against any node or network.

## VERDICT: CONFIRMED (already known upstream; not yet fixed)

At the pin, none of reth's block-validation paths checks `tx.gas <= 2^32-1` once EIP-8037 is active. The executor's only per-transaction gas-limit gate is the block-capacity check. evm2's `validate_tx_gas_limit_cap` compares against `tx_gas_caps().0`, and that value is `u64::MAX` when EIP-8037 is on. No header, consensus, or engine check fills the gap: the block gas limit is only capped at 2^63-1, and the spec has no invariant that keeps it below 2^32. Hive at the exact pinned commit shows the problem directly. In all 6 EEST `above_cap` cases, reth **executes** the over-cap transaction. reth still returns INVALID for those payloads, but only because the fixture's block access list (BAL) leaves out that transaction's writes. That reason is incidental. A block producer that builds the BAL from its own execution, such as a reth node using the same executor, would produce a block that reth accepts and that Geth and the spec reject. The gap is tracked in alloy-rs/evm2 issue #807 (open) and bluealloy/revm #3929 (open). Every reth-side fix PR was closed without merging.

## Strongest refutation attempt (and why it fails)

**Route (1), another check on the same path.** This is the strongest attempt because the hive engine run at the pin returned `INVALID` for every over-cap payload. So on the tested path, reth *does* reject the block, and the spec's block-level result (block invalid) was reached. The hive detail log shows the reason:

> `got: "[BlockException.INVALID_BLOCK_ACCESS_LIST] EVM execution failed: database error: Slot 0x0 not found in BAL for account 0xE4492B6021E10a7b66a358972a0a67E0668817Ef" expected: "TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM"`

(The same line appears for tx types 0-4 and, with account `0xF35C…421C`, for the fork-transition case.) This is a rejection by the BAL-driven parallel executor (`crates/engine/tree/src/tree/payload_processor/bal/execute.rs`). It happened because reth ran the transaction's `SSTORE(0, 1)`, and the fixture's BAL, built by the spec where the transaction is invalid, has no entry for that slot. The BAL is a commitment the block producer writes, not an independent check of `tx.gas`. A producer that includes the over-cap transaction and commits a BAL matching its execution passes reth's BAL checks. In that block, reth's only per-transaction gate, `validate_transaction_gas_limit`, does not test the total cap (see below), so reth would import the block. The refutation therefore holds only for fixtures whose BAL was computed by the spec. It does not hold for adversarial or reth-built blocks.

**Route (2), the condition cannot occur on a valid chain.** No such invariant exists:
- Spec: `check_gas_limit` (`src/ethereum/forks/amsterdam/fork.py` L1118-L1155) only limits the change per block (`< parent/1024`) and enforces `LIMIT_MINIMUM`. There is no upper bound.
- reth: `MAXIMUM_GAS_LIMIT_BLOCK = 2u64.pow(63) - 1` (reth-core `crates/primitives-traits/src/constants/mod.rs` L13-L15), checked at `crates/consensus/common/src/validation.rs` L30-L31. The parent-delta rule is at L373-L412.
- The spec's capacity rule needs `tx.gas <= state_gas_available <= block_gas_limit` (`vm/gas.py` L1084), so the condition needs block gas limit > 2^32-1. A custom genesis can set that directly; the EEST fixtures do so (`Environment(gas_limit=2 * total_cap)`). On a chain at about 60M gas it takes at least about 4,376 consecutive maximum-increase blocks (ln(2^32/6e7)/ln(1+1/1024), my arithmetic). EIP-8037 itself says that while the block gas limit is below the cap, "the cap rejects nothing that could be included today; what it adds is a ceiling that no later block gas limit increase can lift" (eip-8037.md L421). So the condition is reachable on valid chains but not imminent on mainnet-like ones.

**Route (3), the spec was misread.** The spec says what the candidate claims. EIP-8037 (EIPs `d4c57552`, `EIPS/eip-8037.md` L40, L76):
> | `TX_MAX_TOTAL_GAS_LIMIT` | 4,294,967,295 (2^32 - 1) |
> 1. The transaction's **gas limit is not higher than `TX_MAX_TOTAL_GAS_LIMIT`**. Concretely, `tx.gas <= TX_MAX_TOTAL_GAS_LIMIT`. … this bound applies to `tx.gas` as a whole …

execution-specs `src/ethereum/forks/amsterdam/transactions.py` L640-L641:
```python
    if tx.gas > GasCosts.TX_MAX_TOTAL_GAS_LIMIT:
        raise TransactionGasLimitExceededError("Gas limit too high")
```
`vm/gas.py` L154: `TX_MAX_TOTAL_GAS_LIMIT: Final[Uint] = Uint(4_294_967_295)`. `validate_transaction` is called from `check_transaction` (`fork.py` L539) before `check_block_gas_capacity` (`fork.py` L541-L543), and `InvalidTransaction` makes the block invalid. EIP-8037 is listed in the Glamsterdam meta EIP (`EIPS/eip-7773.md` L35 at `d4c57552`).

**Route (4), a later fix.** No fix exists:
- `api.github.com/repos/paradigmxyz/reth/commits?path=crates/ethereum/evm/src/executor.rs`: the newest commit is `9d1638f328` (2026-10-24, "replace revm with evm2 (#25002)"). The pin `e0378eb6` is the newest reth main commit since 2026-10-27 12:00Z.
- `alloy-rs/evm2` commits after `4a53ec08` (to 2026-10-27 14:33Z) touching `version/mod.rs` or `ethereum/mod.rs`: `5652ca0a1e` (JIT host layouts), `66082b7758`, `c8acd8b4eb` (JIT), `e06fb4b399` (eest runner). None of them changes `tx_gas_caps`.
- evm2 issue #807 is **open**. It proposes changing `tx_gas_caps` to `(MAX_TX_TOTAL_GAS_LIMIT_AMSTERDAM, …)` and is "blocked on fixtures".

**Route (5), other checks on the block path and the pool.**
- `crates/engine/tree/src/tree/payload_validator.rs` L536-L546 compares the block gas limit only against `parent * MAX_EXPECTED_GAS_LIMIT_MULTIPLIER`, and only to decide whether to wait for pre-execution checks. L577-L582 checks the BAL size against the gas limit. L1307 (prewarm reuse) calls the same `validate_transaction_gas_limit`. No 2^32-1 constant appears anywhere on the path.
- The BAL path `payload_processor/bal/execute.rs` L169 and L179 calls the same `validate_transaction_gas_limit`.
- The txpool (not consensus) has the same gap. `crates/ethereum/evm/src/lib.rs` L160-L171 sets `tx_gas_limit_cap = 0` when EIP-8037 is on. `crates/transaction-pool/src/validate/eth.rs` L648-L653 skips the cap when it is 0, and L534-L541 bounds `tx.gas` only by the block gas limit. A reth pool and builder on a chain with block gas limit ≥ 2^32 can therefore admit and include such a transaction.

## Code at the pin (why reth accepts)

`crates/ethereum/evm/src/executor.rs#L275-L302` (`validate_transaction_gas_limit`, called at L313 and L350), EIP-8037 branch:
```rust
let regular_limit = transaction_gas_limit.min(self.evm.version().tx_gas_limit_cap);
if regular_limit > regular_available { … }
else if transaction_gas_limit > state_available { … }
```
This implements only spec rule 3 (`vm/gas.py` L1081-L1085). It has no total-cap test.

evm2 `crates/evm2/src/version/mod.rs#L87-L93`:
```rust
pub const fn tx_gas_caps(&self) -> (u64, u64) {
    if self.feature(EvmFeatures::EIP8037) {
        (u64::MAX, self.tx_gas_limit_cap)
```
evm2 `crates/evm2/src/ethereum/mod.rs#L336-L343` `validate_tx_gas_limit_cap` uses `tx_gas_caps().0`, which is `u64::MAX`, so it never fails under EIP-8037. It is called by every Ethereum transaction type (for example `ethereum/eip1559.rs` L47, `legacy.rs` L41, `eip2930.rs` L42, `eip4844.rs` L54, `eip7702.rs` L54). `validate_execution_gas_limit_cap` (L349-L365) covers only spec rule 2. evm2's own unit test `amsterdam_allows_total_gas_above_osaka_cap_when_execution_gas_fits` (L1366-L1408) asserts that a value above the Osaka cap is `Ok`; it does not test 2^32. No `2^32-1` constant exists anywhere in `crates/evm2/src`; I searched for `1 << 32`, `0xffff_ffff`, `u32::MAX as u64` and `TOTAL_GAS`.

## Other client (Geth `8817f99f`)

`core/state_transition.go` L590-L594:
```go
if !msg.skipGasLimitCapCheck {
    if rules.IsAmsterdam && msg.GasLimit > params.MaxTxTotalGas {
        return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxTotalGas, msg.GasLimit)
```
`params/protocol_params.go` L32: `MaxTxTotalGas uint64 = 1<<32 - 1`. `skipGasLimitCapCheck` is set only by the `SkipGasLimitCapCheck()` helper (L314-L318, "disables the check … against the protocol defined tx gaslimit cap"). I did not trace its callers; going by its doc comment, it is meant for non-consensus use. Hive (Geth `8817f99f`, run 2026-10-27 03:44Z) passes all 10 `test_tx_total_gas_limit_cap` cases and both `test_tx_total_gas_limit_cap_at_transition` cases.

## Hive / EEST evidence

- EEST: execution-specs PR #3582, "feat(spec-specs,forks,tests): cap total transaction gas at 2^32 - 1 (EIP-8037)", merged 2026-09-21 into `forks/amsterdam`. Tests at the pin:
  - `tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py` L704 `test_tx_total_gas_limit_cap`, `env=Environment(gas_limit=2 * total_cap)` at L761.
  - `test_state_gas_fork_transition.py` L488 `test_tx_total_gas_limit_cap_at_transition`, `genesis_environment=Environment(gas_limit=2 * total_cap)` at L557.
  - So both fixtures use a block gas limit of 8,589,934,590, which is ≥ 2^32. The test docstring says the block limit "sits above it … the only rule the above-cap transaction breaks is the cap itself".
- Hive `glamsterdam-quick`: the newest reth `eels/consume-engine` run is `1793107028-864528883216a896bffd75995a87ae56.json` (start 2026-10-27T13:04:55Z, client "Reth Version: 2.8.1-dev+e0378eb6", which is the pinned commit; 4929 tests, 4906 pass, 23 fail). Tests whose names match `*tx_gas_limit*` / `*total_gas*` (80 in total):
  - FAIL (6): `test_tx_total_gas_limit_cap[fork_Amsterdam-tx_type_{0,1,2,3,4}-blockchain_test_engine_from_state_test-above_cap]` and `test_tx_total_gas_limit_cap_at_transition[fork_BPO2ToAmsterdamAtTime15k-blockchain_test_engine-above_total_cap]`. Each one returned INVALID_BLOCK_ACCESS_LIST "Slot 0x0 not found in BAL" where `GAS_LIMIT_EXCEEDS_MAXIMUM` was expected (details log `details/1793106295-fb69…bdbf-0.log`).
  - PASS: all 5 `…-at_cap`, `…at_transition[…-at_total_cap]`, all `test_tx_gas_limit_block_boundary[…]` (bgl 0x0fffffd and 0x01ffffe), and both `test_block_2d_gas_tx_gas_limit_exceeds_execution_remaining[…]`.
  - Related but separate FAILs (EIP-7825 intrinsic-cap rule 2, not this candidate): `test_calldata_floor_exceeding_tx_gas_limit_cap[…exceeds_cap]`, `test_intrinsic_execution_gas_exceeds_cap[…exceeds_cap]`, `test_tx_gas_above_cap_at_transition[…intrinsic_above_cap]`. I did not analyze them; they should be handled as a separate candidate.
- The previous reth run (`+4c1584b7`, 2026-10-26 23:00Z) had 288 failures. I did not inspect it.

## Corrected record

- **Client / EIP:** Reth (via evm2) / EIP-8037. Amsterdam is not active on mainnet, so this is not withheld.
- **Spec:** EIP-8037 §Transaction validation rule 1: "`tx.gas <= TX_MAX_TOTAL_GAS_LIMIT`" (eip-8037.md L76). execution-specs `amsterdam/transactions.py` L640-L641 and `vm/gas.py` L154.
- **Code:**
  - reth `crates/ethereum/evm/src/executor.rs#L275-L302` (on block path only capacity is checked).
  - evm2 `4a53ec08` `crates/evm2/src/version/mod.rs#L87-L93` (`tx_gas_caps` returns `u64::MAX` as the total cap under EIP-8037).
  - evm2 `crates/evm2/src/ethereum/mod.rs#L336-L343` (`validate_tx_gas_limit_cap` is therefore a no-op).
  - Pool: reth `crates/ethereum/evm/src/lib.rs#L160-L171` and `crates/transaction-pool/src/validate/eth.rs#L648-L653`.
- **Condition:** an Amsterdam block with `gas_limit >= tx.gas > 2^32-1` (reachable by genesis or by gradual increase; reth allows up to 2^63-1). It contains a transaction with `2^32-1 < tx.gas`, intrinsic/floor ≤ 2^24, `min(2^24, tx.gas)` ≤ remaining execution gas, `tx.gas` ≤ remaining state gas, and a sender balance ≥ `tx.gas * max_fee`. The block's BAL and post-state come from executing that transaction.
- **Outcome:** reth executes the transaction and imports the block. The spec and Geth reject it with `TransactionGasLimitExceededError` / `ErrGasLimitTooHigh`. A reth builder can also include such a transaction, because its pool does not cap it.
- **Confidence:** high. The code was read at the pins, and hive at the exact commit shows reth executing the over-cap transaction. The only mitigation seen is the BAL mismatch in fixtures built by the spec. I did not build a self-consistent block; that was out of scope, since no execution was allowed.
- **Severity:** consensus split (invalid block accepted). It is latent: it cannot trigger until the block gas limit is ≥ 2^32 (about 72× today's mainnet-scale limit), but devnets or testnets with large genesis gas limits can trigger it immediately.

## Known-check

| Ref | State | Note |
|---|---|---|
| alloy-rs/evm2 #807 "Enforce EIP-8037 TX_MAX_TOTAL_GAS_LIMIT for Amsterdam (blocked on fixtures)" | **open** (2026-10-26) | Exactly this gap. Waiting for a fixture release with execution-specs #3582; says current `tests-glamsterdam-devnet@v8.1.4` fixtures (for example `ecrecover_short_buff`) expect over-cap transactions to succeed. |
| alloy-rs/evm2 #515 "fix: enforce EIP-8037 total transaction gas cap" | closed, not merged (2026-09-30) | Per #807, closed because 4 EEST cases failed. |
| bluealloy/revm #3929 "EIP-8037: missing TX_MAX_TOTAL_GAS_LIMIT enforcement" | open | Same gap in revm (reth no longer uses revm after #25002). |
| bluealloy/revm #3931 "feat(handler): enforce EIP-8037's TX_MAX_TOTAL_GAS_LIMIT" | open PR | — |
| bluealloy/revm #3957 "fix: cap total tx gas limit under EIP-8037" | closed, not merged | — |
| paradigmxyz/reth #27589 "fix(txpool): enforce EIP-8037 total gas cap" | closed, not merged (2026-09-30) | Body: "On mainnet the block gas limit check already hides the gap, so this only matters for chains with a block gas limit above 2^32 - 1." |
| paradigmxyz/reth #27641 "fix(evm): enforce configurable EIP-8037 gas limits" | closed, not merged (2026-10-23) | — |
| paradigmxyz/reth #27616 "fix(engine): apply the total gas cap and blob budget to IL checks" | draft, closed (2026-10-01) | — |
| paradigmxyz/reth #27587 "fix(txpool): cap intrinsic regular gas under EIP-8037" | merged 2026-09-30 | Rule 2 (intrinsic) only, not the total cap. |
| ethereum/execution-specs #3582 | merged 2026-09-21 | Spec and tests for the cap. |

**Status: KNOWN** (evm2 #807 open, revm #3929 open). It is unfixed at reth `e0378eb6` / evm2 `4a53ec08` and shows up as 6 failing hive `consume-engine` cases.

## URLs read

- https://github.com/paradigmxyz/reth (git fetch of `e0378eb691aa2770bb96fd4ecec2f4445a2a3e7c`, depth 1)
- https://github.com/alloy-rs/evm2 (git fetch of `4a53ec080dfbb904e98e3ca8f0abcc11ab8abae0`)
- https://github.com/paradigmxyz/reth-core (git fetch of `33bfe6e8b7bacc4f2e464497262ee3f87c6c86ce`)
- https://github.com/ethereum/execution-specs (git fetch of `forks/bogota` = `7f2f4c9bf6d73b09ecb8b24eb14d08bf0cf0247b`)
- https://raw.githubusercontent.com/ethereum/EIPs/d4c57552/EIPS/eip-8037.md
- https://raw.githubusercontent.com/ethereum/EIPs/d4c57552/EIPS/eip-7773.md
- https://raw.githubusercontent.com/ethereum/go-ethereum/8817f99f/core/state_transition.go
- https://raw.githubusercontent.com/ethereum/go-ethereum/8817f99f/params/protocol_params.go
- https://raw.githubusercontent.com/alloy-rs/alloy/v2.5.0/crates/eips/src/eip1559/constants.rs and `/eip7825.rs`
- https://api.github.com/repos/paradigmxyz/reth/commits?path=crates/ethereum/evm/src/executor.rs and `/commits?sha=main&since=2026-10-27T12:00:00Z`
- https://api.github.com/repos/alloy-rs/evm2/commits?path=crates/evm2/src/version/mod.rs, `?path=crates/evm2/src/ethereum/mod.rs`, `?since=2026-10-26T21:00:00Z`
- https://api.github.com/repos/paradigmxyz/reth/pulls/27589, /27641, /27616
- https://api.github.com/repos/bluealloy/revm/pulls/3957, https://api.github.com/repos/bluealloy/revm/issues/3929
- https://api.github.com/repos/alloy-rs/evm2/issues/807, https://api.github.com/repos/alloy-rs/evm2/pulls/515
- https://api.github.com/repos/ethereum/execution-specs/pulls/3582
- https://api.github.com/search/issues (queries: reth "8037 total gas", reth/evm2/revm "TX_MAX_TOTAL_GAS_LIMIT", evm2 "8037 cap")
- https://hive.ethpandaops.io/glamsterdam-quick/listing.jsonl
- https://hive.ethpandaops.io/glamsterdam-quick/results/1793107028-864528883216a896bffd75995a87ae56.json (reth @ e0378eb6)
- https://hive.ethpandaops.io/glamsterdam-quick/results/details/1793106295-fb69b277c64a8ffd1dc669182d6c36a258aebb6ed743c8e33266c0ba36f3bdbf-0.log (byte ranges of the 6 failing cases)
- https://hive.ethpandaops.io/glamsterdam-quick/results/1793073392-57e9bcd5e12b8c11144f8119950d952b.json (Geth @ 8817f99f)
