Skip to content

Commit 9b30723

Browse files
authored
fix(specs): Fix State Tests Genesis Environment (#1912)
* feat(filler): Add unit test to verify pre-alloc grouping on different state test envs * fix(specs): Genesis generation for state tests
1 parent 485ff27 commit 9b30723

File tree

2 files changed

+182
-20
lines changed

2 files changed

+182
-20
lines changed

src/ethereum_test_specs/state.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,38 +90,43 @@ def discard_fixture_format_by_marks(
9090
def _generate_blockchain_genesis_environment(self, *, fork: Fork) -> Environment:
9191
"""Generate the genesis environment for the BlockchainTest formatted test."""
9292
assert self.env.number >= 1, (
93-
"genesis block number cannot be negative, set state test env.number to 1"
93+
"genesis block number cannot be negative, set state test env.number to at least 1"
9494
)
95-
96-
# Modify values to the proper values for the genesis block
97-
# TODO: All of this can be moved to a new method in `Fork`
98-
updated_values: Dict[str, Any] = {
99-
"withdrawals": None,
100-
"parent_beacon_block_root": None,
95+
assert self.env.timestamp >= 1, (
96+
"genesis timestamp cannot be negative, set state test env.timestamp to at least 1"
97+
)
98+
# There's only a handful of values that we need to set in the genesis for the
99+
# environment values at block 1 to make sense:
100+
# - Number: Needs to be N minus 1
101+
# - Timestamp: Needs to be zero, because the subsequent block can come at any time.
102+
# - Gas Limit: Changes from parent to child, needs to be set in genesis
103+
# - Base Fee Per Gas: Block's base fee depends on the parent's value
104+
# - Excess Blob Gas: Block's excess blob gas value depends on the parent's value
105+
kwargs: Dict[str, Any] = {
101106
"number": self.env.number - 1,
107+
"timestamp": 0,
102108
}
109+
110+
if "gas_limit" in self.env.model_fields_set:
111+
kwargs["gas_limit"] = self.env.gas_limit
112+
113+
if self.env.base_fee_per_gas:
114+
# Calculate genesis base fee per gas from state test's block#1 env
115+
kwargs["base_fee_per_gas"] = HexNumber(
116+
int(int(str(self.env.base_fee_per_gas), 0) * 8 / 7)
117+
)
118+
103119
if self.env.excess_blob_gas:
104120
# The excess blob gas environment value means the value of the context (block header)
105121
# where the transaction is executed. In a blockchain test, we need to indirectly
106122
# set the excess blob gas by setting the excess blob gas of the genesis block
107123
# to the expected value plus the TARGET_BLOB_GAS_PER_BLOCK, which is the value
108124
# that will be subtracted from the excess blob gas when the first block is mined.
109-
updated_values["excess_blob_gas"] = self.env.excess_blob_gas + (
125+
kwargs["excess_blob_gas"] = self.env.excess_blob_gas + (
110126
fork.target_blobs_per_block() * fork.blob_gas_per_blob()
111127
)
112-
if self.env.base_fee_per_gas:
113-
# Calculate genesis base fee per gas from state test's block#1 env
114-
updated_values["base_fee_per_gas"] = HexNumber(
115-
int(int(str(self.env.base_fee_per_gas), 0) * 8 / 7)
116-
)
117-
if fork.header_prev_randao_required():
118-
# Set current random
119-
updated_values["difficulty"] = None
120-
updated_values["prev_randao"] = (
121-
self.env.prev_randao if self.env.prev_randao is not None else self.env.difficulty
122-
)
123128

124-
return self.env.copy(**updated_values)
129+
return Environment(**kwargs)
125130

126131
def _generate_blockchain_blocks(self, *, fork: Fork) -> List[Block]:
127132
"""Generate the single block that represents this state test in a BlockchainTest format."""

src/pytest_plugins/filler/tests/test_prealloc_group.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"""Test the pre_alloc_group marker functionality."""
22

3+
import textwrap
4+
from pathlib import Path
5+
from typing import List
36
from unittest.mock import Mock
47

8+
import pytest
9+
10+
from ethereum_clis import TransitionTool
511
from ethereum_test_forks import Fork, Prague
612
from ethereum_test_specs.base import BaseTest
713
from ethereum_test_types import Alloc, Environment
814

15+
from ..filler import default_output_directory
16+
917

1018
class MockTest(BaseTest):
1119
"""Mock test class for testing."""
@@ -193,3 +201,152 @@ def test_pre_alloc_group_with_reason():
193201

194202
# Hashes should be the same - reason doesn't affect grouping
195203
assert hash1 == hash2
204+
205+
206+
state_test_diff_envs = textwrap.dedent(
207+
"""\
208+
import pytest
209+
210+
from ethereum_test_tools import (
211+
Account,
212+
Alloc,
213+
Environment,
214+
StateTestFiller,
215+
Transaction
216+
)
217+
from ethereum_test_tools.vm.opcode import Opcodes as Op
218+
219+
220+
@pytest.mark.valid_from("Istanbul")
221+
def test_chainid(state_test: StateTestFiller, pre: Alloc):
222+
contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP)
223+
sender = pre.fund_eoa()
224+
225+
tx = Transaction(
226+
ty=0x0,
227+
chain_id=0x01,
228+
to=contract_address,
229+
gas_limit=100_000,
230+
sender=sender,
231+
)
232+
233+
post = {{
234+
contract_address: Account(storage={{"0x01": "0x01"}}),
235+
}}
236+
237+
state_test(env={env}, pre=pre, post=post, tx=tx)
238+
"""
239+
)
240+
241+
242+
@pytest.mark.parametrize(
243+
"environment_definitions,expected_different_pre_alloc_groups",
244+
[
245+
# Environment fields not affecting the pre-alloc groups
246+
pytest.param(
247+
[
248+
"Environment(fee_recipient=pre.fund_eoa(amount=0))",
249+
"Environment(fee_recipient=1)",
250+
"Environment(fee_recipient=2)",
251+
],
252+
1,
253+
id="different_fee_recipients",
254+
),
255+
pytest.param(
256+
[
257+
"Environment(prev_randao=1)",
258+
"Environment(prev_randao=2)",
259+
],
260+
1,
261+
id="different_prev_randaos",
262+
),
263+
pytest.param(
264+
[
265+
"Environment(timestamp=1)",
266+
"Environment(timestamp=2)",
267+
],
268+
1,
269+
id="different_timestamps",
270+
),
271+
pytest.param(
272+
[
273+
"Environment(extra_data='0x01')",
274+
"Environment(extra_data='0x02')",
275+
],
276+
1,
277+
id="different_extra_data",
278+
),
279+
# Environment fields affecting the pre-alloc groups
280+
pytest.param(
281+
[
282+
"Environment(gas_limit=100_000_000)",
283+
"Environment(gas_limit=200_000_000)",
284+
],
285+
2,
286+
id="different_gas_limits",
287+
),
288+
pytest.param(
289+
[
290+
"Environment(number=10)",
291+
"Environment(number=20)",
292+
],
293+
2,
294+
id="different_block_numbers",
295+
),
296+
pytest.param(
297+
[
298+
"Environment(base_fee_per_gas=10)",
299+
"Environment(base_fee_per_gas=20)",
300+
],
301+
2,
302+
id="different_base_fee",
303+
),
304+
pytest.param(
305+
[
306+
"Environment(excess_blob_gas=10)",
307+
"Environment(excess_blob_gas=20)",
308+
],
309+
2,
310+
id="different_excess_blob_gas",
311+
),
312+
],
313+
)
314+
def test_state_tests_pre_alloc_grouping(
315+
pytester: pytest.Pytester,
316+
default_t8n: TransitionTool,
317+
environment_definitions: List[str],
318+
expected_different_pre_alloc_groups: int,
319+
):
320+
"""Test pre-alloc grouping when filling state tests, and the effect of the `state_test.env`."""
321+
tests_dir = Path(pytester.mkdir("tests"))
322+
for i, env in enumerate(environment_definitions):
323+
test_module = tests_dir / f"test_state_test_{i}.py"
324+
test_module.write_text(state_test_diff_envs.format(env=env))
325+
pytester.copy_example(name="src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini")
326+
args = [
327+
"-c",
328+
"pytest-fill.ini",
329+
"--generate-pre-alloc-groups",
330+
"--fork=Cancun",
331+
"--t8n-server-url",
332+
]
333+
assert default_t8n.server_url is not None
334+
args.append(default_t8n.server_url)
335+
result = pytester.runpytest(*args)
336+
result.assert_outcomes(
337+
passed=len(environment_definitions),
338+
failed=0,
339+
skipped=0,
340+
errors=0,
341+
)
342+
343+
output_dir = (
344+
Path(default_output_directory()).absolute() / "blockchain_tests_engine_x" / "pre_alloc"
345+
)
346+
assert output_dir.exists()
347+
348+
# All different environments should only generate a single genesis pre-alloc
349+
assert (
350+
len([f for f in output_dir.iterdir() if f.name.endswith(".json")])
351+
== expected_different_pre_alloc_groups
352+
)

0 commit comments

Comments
 (0)