Skip to content

Commit 323b2b3

Browse files
feat(fill): add --gas-benchmark-values command to support single genesis file (#1895)
* feat(fill): add benchmark gas valu command to support single genesis file * refactor(tests): update benchmark test for supported command * refactor(benchmark): consolidate benchmark configurations into a single entry * doc(fill): update command description and changelog * chore(fill): remove legacy gas benchmark values command * refactor(fill): create gas benchmakr value pytest plugin * test(fill): add pytest plugin test and update state test * refactor(fill): add env fixture for benchmarking with gas limit configuration * refactor: support both fill and execute mode * fix: update ci flag and test command
1 parent b9bef12 commit 323b2b3

File tree

9 files changed

+211
-35
lines changed

9 files changed

+211
-35
lines changed

.github/configs/feature.yaml

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,9 @@ static:
1111
evm-type: static
1212
fill-params: --until=Osaka --fill-static-tests ./tests/static
1313
solc: 0.8.21
14-
benchmark_1M:
14+
benchmark_test:
1515
evm-type: benchmark
16-
fill-params: --from=Cancun --until=Prague --block-gas-limit 1000000 -m benchmark ./tests
17-
solc: 0.8.21
18-
feature_only: true
19-
benchmark_10M:
20-
evm-type: benchmark
21-
fill-params: --from=Cancun --until=Prague --block-gas-limit 10000000 -m benchmark ./tests
22-
solc: 0.8.21
23-
feature_only: true
24-
benchmark_30M:
25-
evm-type: benchmark
26-
fill-params: --from=Cancun --until=Prague --block-gas-limit 30000000 -m benchmark ./tests
27-
solc: 0.8.21
28-
feature_only: true
29-
benchmark_60M:
30-
evm-type: benchmark
31-
fill-params: --from=Cancun --until=Prague --block-gas-limit 60000000 -m benchmark ./tests
32-
solc: 0.8.21
33-
feature_only: true
34-
benchmark_90M:
35-
evm-type: benchmark
36-
fill-params: --from=Cancun --until=Prague --block-gas-limit 90000000 -m benchmark ./tests
37-
solc: 0.8.21
38-
feature_only: true
39-
benchmark_120M:
40-
evm-type: benchmark
41-
fill-params: --from=Cancun --until=Prague --block-gas-limit 120000000 -m benchmark ./tests
16+
fill-params: --from=Cancun --until=Prague --gas-benchmark-values 1,10,30,60,90,120 -m benchmark --generate-all-formats ./tests
4217
solc: 0.8.21
4318
feature_only: true
4419
eip7692:

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Users can select any of the artifacts depending on their testing needs for their
7373
- 🔀 Changed INVALID_DEPOSIT_EVENT_LAYOUT to a BlockException instead of a TransactionException ([#1773](https://github.com/ethereum/execution-spec-tests/pull/1773)).
7474
- 🔀 Disabled writing debugging information to the EVM "dump directory" to improve performance. To obtain debug output, the `--evm-dump-dir` flag must now be explicitly set. As a consequence, the now redundant `--skip-evm-dump` option was removed ([#1874](https://github.com/ethereum/execution-spec-tests/pull/1874)).
7575
- ✨ Generate unique addresses with Python for compatible static tests, instead of using hard-coded addresses from legacy static test fillers ([#1781](https://github.com/ethereum/execution-spec-tests/pull/1781)).
76+
- ✨ Added support for the `--benchmark-gas-values` flag in the `fill` command, allowing a single genesis file to be used across different gas limit settings when generating fixtures. ([#1895](https://github.com/ethereum/execution-spec-tests/pull/1895)).
7677

7778
#### `consume`
7879

src/cli/pytest_commands/pytest_ini_files/pytest-execute-hive.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ addopts =
1414
-p pytest_plugins.execute.rpc.hive
1515
-p pytest_plugins.execute.execute
1616
-p pytest_plugins.shared.execute_fill
17+
-p pytest_plugins.shared.benchmarking
1718
-p pytest_plugins.shared.transaction_fixtures
1819
-p pytest_plugins.forks.forks
1920
-p pytest_plugins.pytest_hive.pytest_hive

src/cli/pytest_commands/pytest_ini_files/pytest-execute.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ addopts =
1414
-p pytest_plugins.execute.pre_alloc
1515
-p pytest_plugins.execute.execute
1616
-p pytest_plugins.shared.execute_fill
17+
-p pytest_plugins.shared.benchmarking
1718
-p pytest_plugins.shared.transaction_fixtures
1819
-p pytest_plugins.execute.rpc.remote_seed_sender
1920
-p pytest_plugins.execute.rpc.remote

src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ addopts =
1212
-p pytest_plugins.concurrency
1313
-p pytest_plugins.filler.pre_alloc
1414
-p pytest_plugins.filler.filler
15+
-p pytest_plugins.shared.execute_fill
1516
-p pytest_plugins.filler.ported_tests
1617
-p pytest_plugins.filler.static_filler
17-
-p pytest_plugins.shared.execute_fill
18+
-p pytest_plugins.shared.benchmarking
1819
-p pytest_plugins.shared.transaction_fixtures
1920
-p pytest_plugins.forks.forks
2021
-p pytest_plugins.eels_resolver
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Test the benchmarking pytest plugin for gas benchmark values."""
2+
3+
import textwrap
4+
5+
import pytest
6+
7+
test_module_dummy = textwrap.dedent(
8+
"""\
9+
import pytest
10+
11+
from ethereum_test_tools import Environment
12+
13+
@pytest.mark.valid_at("Istanbul")
14+
def test_dummy_benchmark_test(state_test, gas_benchmark_value):
15+
state_test(
16+
env=env,pre={},post={},tx=None)
17+
"""
18+
)
19+
20+
test_module_without_fixture = textwrap.dedent(
21+
"""\
22+
import pytest
23+
24+
from ethereum_test_tools import Environment
25+
26+
@pytest.mark.valid_at("Istanbul")
27+
def test_dummy_no_benchmark_test(state_test):
28+
state_test(env=env, pre={}, post={}, tx=None)
29+
"""
30+
)
31+
32+
33+
def setup_test_directory_structure(
34+
pytester: pytest.Pytester, test_content: str, test_filename: str
35+
):
36+
"""
37+
Set up the common test directory structure used across multiple tests.
38+
39+
Args:
40+
pytester: The pytest Pytester fixture
41+
test_content: The content to write to the test file
42+
test_filename: The name of the test file to create
43+
44+
Returns:
45+
The path to the created test module file
46+
47+
"""
48+
tests_dir = pytester.mkdir("tests")
49+
istanbul_tests_dir = tests_dir / "istanbul"
50+
istanbul_tests_dir.mkdir()
51+
dummy_dir = istanbul_tests_dir / "dummy_test_module"
52+
dummy_dir.mkdir()
53+
test_module = dummy_dir / test_filename
54+
test_module.write_text(test_content)
55+
56+
pytester.copy_example(name="src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini")
57+
58+
return test_module
59+
60+
61+
def test_gas_benchmark_option_added(pytester: pytest.Pytester):
62+
"""Test that the --gas-benchmark-values option is properly added."""
63+
pytester.copy_example(name="src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini")
64+
65+
# Command: pytest -p pytest_plugins.filler.benchmarking --help
66+
result = pytester.runpytest("-c", "pytest-fill.ini", "--help")
67+
68+
assert result.ret == 0
69+
assert any("--gas-benchmark-values" in line for line in result.outlines)
70+
assert any("Specify gas benchmark values for tests" in line for line in result.outlines)
71+
72+
73+
def test_benchmarking_mode_configured_with_option(pytester: pytest.Pytester):
74+
"""Test that fill_mode is set to BENCHMARKING when --gas-benchmark-values is used."""
75+
setup_test_directory_structure(pytester, test_module_dummy, "test_dummy_benchmark.py")
76+
77+
# Test with gas benchmark values
78+
result = pytester.runpytest(
79+
"-c",
80+
"pytest-fill.ini",
81+
"--fork",
82+
"Istanbul",
83+
"--gas-benchmark-values",
84+
"10,20,30",
85+
"tests/istanbul/dummy_test_module/",
86+
"--collect-only",
87+
"-q",
88+
)
89+
90+
assert result.ret == 0
91+
assert any("9 tests collected" in line for line in result.outlines)
92+
# Check that the test names include the benchmark gas values
93+
assert any("benchmark-gas-value_10M" in line for line in result.outlines)
94+
assert any("benchmark-gas-value_20M" in line for line in result.outlines)
95+
assert any("benchmark-gas-value_30M" in line for line in result.outlines)
96+
97+
98+
def test_benchmarking_mode_not_configured_without_option(pytester: pytest.Pytester):
99+
"""Test that fill_mode is not set to BENCHMARKING when --gas-benchmark-values is not used."""
100+
setup_test_directory_structure(pytester, test_module_dummy, "test_dummy_benchmark.py")
101+
102+
# Test without gas benchmark values
103+
result = pytester.runpytest(
104+
"-c",
105+
"pytest-fill.ini",
106+
"--fork",
107+
"Istanbul",
108+
"tests/istanbul/dummy_test_module/",
109+
"--collect-only",
110+
"-q",
111+
)
112+
113+
assert result.ret == 0
114+
# Should generate normal test variants (3) without parametrization
115+
assert any("3 tests collected" in line for line in result.outlines)
116+
assert not any("benchmark-gas-value_10M" in line for line in result.outlines)
117+
assert not any("benchmark-gas-value_20M" in line for line in result.outlines)
118+
assert not any("benchmark-gas-value_30M" in line for line in result.outlines)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""The module contains the pytest hooks for the gas benchmark values."""
2+
3+
import pytest
4+
5+
from ethereum_test_tools import Environment
6+
from ethereum_test_types import EnvironmentDefaults
7+
8+
from .execute_fill import OpMode
9+
10+
11+
def pytest_addoption(parser: pytest.Parser):
12+
"""Add command line options for gas benchmark values."""
13+
evm_group = parser.getgroup("evm", "Arguments defining evm executable behavior")
14+
evm_group.addoption(
15+
"--gas-benchmark-values",
16+
action="store",
17+
dest="gas_benchmark_value",
18+
type=str,
19+
default=None,
20+
help="Specify gas benchmark values for tests as a comma-separated list.",
21+
)
22+
23+
24+
@pytest.hookimpl(tryfirst=True)
25+
def pytest_configure(config):
26+
"""Configure the fill and execute mode to benchmarking."""
27+
if config.getoption("gas_benchmark_value"):
28+
config.op_mode = OpMode.BENCHMARKING
29+
30+
31+
def pytest_generate_tests(metafunc: pytest.Metafunc):
32+
"""Generate tests for the gas benchmark values."""
33+
if "gas_benchmark_value" in metafunc.fixturenames:
34+
gas_benchmark_values = metafunc.config.getoption("gas_benchmark_value")
35+
if gas_benchmark_values:
36+
gas_values = [int(x.strip()) for x in gas_benchmark_values.split(",")]
37+
gas_parameters = [
38+
pytest.param(gas_value * 1_000_000, id=f"benchmark-gas-value_{gas_value}M")
39+
for gas_value in gas_values
40+
]
41+
metafunc.parametrize("gas_benchmark_value", gas_parameters, scope="function")
42+
43+
44+
@pytest.fixture(scope="function")
45+
def gas_benchmark_value(request: pytest.FixtureRequest) -> int:
46+
"""Return a single gas benchmark value for the current test."""
47+
if hasattr(request, "param"):
48+
return request.param
49+
50+
return EnvironmentDefaults.gas_limit
51+
52+
53+
GIGA_GAS = 1_000_000_000
54+
55+
56+
@pytest.fixture
57+
def env(request: pytest.FixtureRequest) -> Environment: # noqa: D103
58+
"""Return an Environment instance with appropriate gas limit based on operation mode."""
59+
if request.config.op_mode == OpMode.BENCHMARKING: # type: ignore[attr-defined]
60+
return Environment(gas_limit=GIGA_GAS)
61+
return Environment()

src/pytest_plugins/shared/execute_fill.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
"""Shared pytest fixtures and hooks for EEST generation modes (fill and execute)."""
22

3+
from enum import StrEnum, unique
34
from typing import List
45

56
import pytest
67

78
from ethereum_test_execution import BaseExecute, LabeledExecuteFormat
89
from ethereum_test_fixtures import BaseFixture, LabeledFixtureFormat
910
from ethereum_test_specs import BaseTest
11+
from ethereum_test_tools import Environment
1012
from ethereum_test_types import EOA, Alloc
1113

1214
from ..spec_version_checker.spec_version_checker import EIPSpecTestItem
1315

1416

17+
@unique
18+
class OpMode(StrEnum):
19+
"""Operation mode for the fill and execute."""
20+
21+
CONSENSUS = "consensus"
22+
BENCHMARKING = "benchmarking"
23+
24+
1525
@pytest.hookimpl(tryfirst=True)
1626
def pytest_configure(config: pytest.Config):
1727
"""
@@ -60,6 +70,9 @@ def pytest_configure(config: pytest.Config):
6070
(f"{marker}: {description}"),
6171
)
6272

73+
if not hasattr(config, "op_mode"):
74+
config.op_mode = OpMode.CONSENSUS # type: ignore[attr-defined]
75+
6376
config.addinivalue_line(
6477
"markers",
6578
"yul_test: a test case that compiles Yul code.",
@@ -181,3 +194,8 @@ def pytest_addoption(parser: pytest.Parser):
181194
default=None,
182195
help=("Enable reading and filling from static test files."),
183196
)
197+
198+
199+
@pytest.fixture
200+
def env(request: pytest.FixtureRequest) -> Environment: # noqa: D103
201+
return Environment()

tests/benchmark/test_worst_blocks.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222

2323
@pytest.fixture
24-
def iteration_count(intrinsic_cost: int):
24+
def iteration_count(intrinsic_cost: int, gas_benchmark_value: int):
2525
"""Calculate the number of iterations based on the gas limit and intrinsic cost."""
26-
return Environment().gas_limit // intrinsic_cost
26+
return gas_benchmark_value // intrinsic_cost
2727

2828

2929
@pytest.fixture
@@ -173,10 +173,10 @@ def test_block_full_data(
173173
zero_byte: bool,
174174
intrinsic_cost: int,
175175
total_cost_floor_per_token: int,
176+
gas_benchmark_value: int,
177+
env: Environment,
176178
):
177179
"""Test a block with empty payload."""
178-
attack_gas_limit = Environment().gas_limit
179-
180180
# Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683)
181181
#
182182
# tx.gasUsed = 21000 + max(
@@ -201,7 +201,7 @@ def test_block_full_data(
201201
#
202202
# So we calculate how many bytes we can fit into calldata based on available gas.
203203

204-
gas_available = attack_gas_limit - intrinsic_cost
204+
gas_available = gas_benchmark_value - intrinsic_cost
205205

206206
# Calculate the token_in_calldata
207207
max_tokens_in_calldata = gas_available // total_cost_floor_per_token
@@ -212,12 +212,12 @@ def test_block_full_data(
212212
tx = Transaction(
213213
to=pre.fund_eoa(),
214214
data=byte_data * num_of_bytes,
215-
gas_limit=attack_gas_limit,
215+
gas_limit=gas_benchmark_value,
216216
sender=pre.fund_eoa(),
217217
)
218218

219219
state_test(
220-
env=Environment(),
220+
env=env,
221221
pre=pre,
222222
post={},
223223
tx=tx,

0 commit comments

Comments
 (0)