ethereum / ethereum/execution-specs
Use historically-accurate per-fork block gas limits
- Dominant language
- Python
- Stars
- 1.2k
- Forks
- 505
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 116
Description
## Problem
All forks currently use the same default block gas limit when filling:
```python
CURRENT_MAINNET_BLOCK_GAS_LIMIT = 60_000_000 # 60M (Osaka, EIP-7935)
DEFAULT_BLOCK_GAS_LIMIT = CURRENT_MAINNET_BLOCK_GAS_LIMIT * 2 # 120M
```
This means a test marked `@pytest.mark.valid_from("Istanbul")` filled for Berlin produces fixtures with a 120M block gas limit - but Berlin mainnet never exceeded ~15M.
Testing a client against Berlin with 120M gas is unrealistic and doesn't reflect the conditions clients actually operated under during that fork.
Historically, the gas limit used for consensus tests was that important, but as gas limits increase, it seems to fill older forks with the ever increasing gas limits.
### Approximate historical mainnet gas limits
We should double-check these!
| Fork / Range | Era | Approx. mainnet gas limit (max per block) |
| -------------------------------------------- | --------------- | -------------------------------------------------------------- |
| Frontier -> Homestead | 2015-2016 | Very low initially -> ~4.7M by Homestead (raised gradually) |
| Byzantium -> Constantinople / St. Petersburg | 2017-2019 | ~4.7M -> ~10M (raised gradually over time) |
| Istanbul | 2019-2020 | ~10M -> 12.5M (12.5M becomes common in mid-2020) |
| Berlin | 2021 | Launched ~12.5M -> ~15M shortly after (miner/validator voting) |
| London -> Paris (Merge) | 2021-2022 | Max 30M (EIP-1559 elasticity; target 15M) |
| Shanghai -> early 2025 | 2023-early 2025 | Max 30M (target 15M) |
| 2025 (pre-Fusaka) | 2025 | Max 36M -> 45M (raised during 2025) |
| Fusaka / Osaka live | Dec 2025+ | Max ~60M (EIP-7935 client default; target ~30M) |
The 2x multiplier (`DEFAULT_BLOCK_GAS_LIMIT = mainnet * 2`) was introduced for testing headroom. With per-fork values we should decide whether to keep the 2x or use the actual mainnet value.
## Proposed change
Add a per-fork gas limit mapping, e.g. in `EnvironmentDefaults` or as a `Fork` method:
```python
# Option A: Fork method
class Prague(Cancun):
@classmethod
def default_gas_limit(cls) -> int:
return 30_000_000
class Osaka(Prague):
@classmethod
def default_gas_limit(cls) -> int:
return 60_000_000 # EIP-7935
```
```python
# Option B: mapping in block_types.py; values are only exemplary
FORK_GAS_LIMITS: dict[str, int] = {
"Frontier": 5_000_000,
"Byzantium": 8_000_000,
"Istanbul": 12_000_000,
"London": 30_000_000,
"Osaka": 60_000_000,
}
```
The filler would look up the fork-specific default instead of using the global `EnvironmentDefaults.gas_limit`. `--block-gas-limit` would still override per-fork defaults.
### Impact on subdirectory names
With per-fork gas limits, the output becomes more meaningful:
```text
fixtures/blockchain_tests/for_berlin_at_0012M/...
fixtures/blockchain_tests/for_cancun_at_0030M/...
fixtures/blockchain_tests/for_osaka_at_0060M/...
```
## Static test compatibility
Static tests (under `tests/static/`) define their own `currentGasLimit` in JSON. A survey of ~2100 static test environment gas limits shows:
| Gas limit value | Count | Notes |
| --------------------------------------------- | ----- | ----------------------------- |
| `0x7fffffffffffffff` (max int64, ~9.2 exagas) | 600 | Intentionally unlimited |
| 10M | 438 | Reasonable for pre-London |
| 30M | 282 | Matches London-era mainnet |
| 100M (`0x5f5e100`) | 227 | Higher than any mainnet limit |
| 1M | 218 | Low-gas edge case tests |
| 100M (decimal) | 91 | Same as above |
| 42.9B, 3B, 111B, 10B, 1B | 177 | Intentionally extreme values |
**Key observations:**
- Static tests set their **own block gas limit** via `currentGasLimit` in the env section - the framework default does not override this.
- The subdirectory name (`for_{fork}_at_{gas}M`) is derived from the framework default, **not** from the per-test gas limit. So a static test with `currentGasLimit: 10000000` (10M) still lands in `for_berlin_at_0120M/`.
- This mismatch is cosmetic (the directory name doesn't affect test execution) but could confuse consumers who expect the directory name to reflect what's inside the fixtures.
### Options for static tests
1. **Ignore the mismatch** - static tests already encode their gas limit in the fixture JSON; the directory is just organizational.
2. **Use the per-test gas limit for the directory** - more accurate but would scatter static tests across many subdirectories.
3. **Keep the framework default for directory naming** but make it per-fork accurate - at least `for_berlin_at_0012M/` is plausible even if some tests inside use 10M or max-int.
## Tasks
- [ ] Research exact historical gas limits per fork (or pick representative values from etherscan block data)
- [ ] Decide: use exact mainnet limit or keep the 2x multiplier?
- [ ] Add `default_gas_limit()` to `Fork` class hierarchy
- [ ] Update `EnvironmentDefaults.gas_limit` to use fork-specific value during filling
- [ ] Update `filler.py` to pass the target fork to the gas limit lookup
- [ ] Update tests (`test_filler.py`, `test_benchmarking.py`) to use fork-specific expected values
- [ ] Verify static tests still fill correctly
- [ ] Update docs in `releases.md`
Contributor guide
Assessment
This issue has not been assessed yet.