Skip to content

Commit fa774aa

Browse files
marioevzspencer-tb
authored andcommitted
bug(test-cli): fix extract config (ethereum#1947)
* fix(testing): Raise correct error on fixture parsing * fix(test-cli): Cleaner extract_config Co-authored-by: spencer <spencer.tb@ethereum.org> --------- Co-authored-by: spencer <spencer.tb@ethereum.org>
1 parent 4e30b6c commit fa774aa

2 files changed

Lines changed: 95 additions & 66 deletions

File tree

packages/testing/src/execution_testing/cli/extract_config.py

Lines changed: 86 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,27 @@
1313
import subprocess
1414
import sys
1515
from pathlib import Path
16-
from typing import Dict, Optional, Tuple, cast
16+
from typing import Dict, Optional, Self
1717

1818
import click
1919
from hive.simulation import Simulation
2020
from hive.testing import HiveTestResult
21+
from pydantic import (
22+
BaseModel,
23+
Field,
24+
SerializerFunctionWrapHandler,
25+
ValidationError,
26+
model_serializer,
27+
)
2128

22-
from execution_testing.base_types import Alloc, to_json
29+
from execution_testing.base_types import Alloc
2330
from execution_testing.cli.pytest_commands.plugins.consume.simulators.helpers.ruleset import (
2431
ruleset,
2532
)
26-
from execution_testing.fixtures import BlockchainFixtureCommon
33+
from execution_testing.fixtures import (
34+
BlockchainEngineFixture,
35+
BlockchainFixtureCommon,
36+
)
2737
from execution_testing.fixtures.blockchain import FixtureHeader
2838
from execution_testing.fixtures.file import Fixtures
2939
from execution_testing.fixtures.pre_alloc_groups import PreAllocGroup
@@ -119,54 +129,79 @@ def extract_client_files(
119129
return extracted_files
120130

121131

122-
def create_genesis_from_fixture(
123-
fixture_path: Path,
124-
) -> Tuple[FixtureHeader, Alloc, int]:
125-
"""Create a client genesis state from a fixture file."""
126-
genesis: FixtureHeader
132+
class GenesisState(BaseModel):
133+
header: FixtureHeader
127134
alloc: Alloc
128-
chain_id: int = 1
129-
with open(fixture_path, "r") as f:
130-
fixture_json = json.load(f)
131-
132-
if "_info" in fixture_json:
133-
# Load the fixture
134-
fixtures = Fixtures.model_validate_json(fixture_path.read_text())
135+
chain_id: int = Field(exclude=True)
136+
fork: Fork = Field(exclude=True)
137+
138+
@model_serializer(mode="wrap")
139+
def serialize_model(
140+
self, handler: SerializerFunctionWrapHandler
141+
) -> dict[str, object]:
142+
serialized = handler(self)
143+
output = serialized["header"]
144+
output["alloc"] = {
145+
k.replace("0x", ""): v for k, v in serialized["alloc"].items()
146+
}
147+
return output
135148

136-
# Get the first fixture (assuming single fixture file)
137-
fixture_id = list(fixtures.keys())[0]
138-
base_fixture = fixtures[fixture_id]
149+
@classmethod
150+
def from_fixture(cls, fixture_path: Path) -> Self:
151+
"""Create a client genesis state from a fixture file."""
152+
fixture_bytes = fixture_path.read_bytes()
139153

140-
if not isinstance(base_fixture, BlockchainFixtureCommon):
154+
try:
155+
# Try to load the fixture
156+
fixtures = Fixtures.model_validate_json(fixture_bytes)
157+
# Go through the fixtures contained to try to get an usable fixture
158+
for _, base_fixture in fixtures.items():
159+
if isinstance(
160+
base_fixture,
161+
(BlockchainFixtureCommon, BlockchainEngineFixture),
162+
):
163+
return cls(
164+
header=base_fixture.genesis,
165+
alloc=base_fixture.pre,
166+
chain_id=int(base_fixture.config.chain_id),
167+
fork=base_fixture.config.fork,
168+
)
141169
raise ValueError(
142-
f"Fixture {fixture_id} is not a blockchain fixture"
170+
f"Fixture {fixture_path} does not contain a genesis"
143171
)
172+
except ValidationError:
173+
pass
144174

145-
genesis = base_fixture.genesis
146-
alloc = base_fixture.pre
147-
chain_id = int(base_fixture.config.chain_id)
148-
else:
149-
pre_alloc_group = PreAllocGroup.model_validate(fixture_json)
150-
genesis = pre_alloc_group.genesis
151-
alloc = pre_alloc_group.pre
152-
153-
return genesis, alloc, chain_id
154-
175+
try:
176+
# Try to load pre-allocation group
177+
pre_alloc_group = PreAllocGroup.model_validate_json(fixture_bytes)
178+
return cls(
179+
header=pre_alloc_group.genesis,
180+
alloc=pre_alloc_group.pre,
181+
chain_id=1, # TODO: PreAllocGroups don't contain chain ID
182+
fork=pre_alloc_group.fork,
183+
)
184+
except ValidationError:
185+
pass
155186

156-
def get_client_environment_for_fixture(fork: Fork, chain_id: int) -> dict:
157-
"""
158-
Get the environment variables for starting a client with the given fixture.
159-
"""
160-
if fork not in ruleset:
161-
raise ValueError(f"Fork '{fork}' not found in hive ruleset")
187+
raise ValueError(
188+
f"File {fixture_path} does not have a recognizable format."
189+
)
162190

163-
return {
164-
"HIVE_CHAIN_ID": str(chain_id),
165-
"HIVE_FORK_DAO_VOTE": "1",
166-
"HIVE_NODETYPE": "full",
167-
"HIVE_CHECK_LIVE_PORT": "8545", # Using RPC port for liveness check
168-
**{k: f"{v:d}" for k, v in ruleset[fork].items()},
169-
}
191+
def get_client_environment(self) -> dict:
192+
"""
193+
Get the environment variables for starting a client with the given fixture.
194+
"""
195+
if self.fork not in ruleset:
196+
raise ValueError(f"Fork '{self.fork}' not found in hive ruleset")
197+
198+
return {
199+
"HIVE_CHAIN_ID": str(self.chain_id),
200+
"HIVE_FORK_DAO_VOTE": "1",
201+
"HIVE_NODETYPE": "full",
202+
"HIVE_CHECK_LIVE_PORT": "8545", # Using RPC port for liveness check
203+
**{k: f"{v:d}" for k, v in ruleset[self.fork].items()},
204+
}
170205

171206

172207
@click.command()
@@ -257,25 +292,16 @@ def extract_config(
257292
click.echo(f"Using fixture: {fixture_path}")
258293

259294
# Load fixture and create genesis
260-
genesis, alloc, chain_id = create_genesis_from_fixture(fixture_path)
261-
fork = genesis.fork
262-
assert fork is not None
263-
client_environment = get_client_environment_for_fixture(fork, chain_id)
264-
265-
genesis_json = to_json(genesis)
266-
alloc_json = to_json(alloc)
267-
genesis_json["alloc"] = {
268-
k.replace("0x", ""): v for k, v in alloc_json.items()
269-
}
270-
271-
genesis_json_str = json.dumps(genesis_json)
272-
genesis_bytes = genesis_json_str.encode("utf-8")
295+
genesis = GenesisState.from_fixture(fixture_path)
296+
client_environment = genesis.get_client_environment()
297+
genesis_bytes = genesis.model_dump_json(
298+
by_alias=True, exclude_none=True
299+
).encode("utf-8")
273300

274301
for client_type in client_types:
275-
client_files = {}
276-
client_files["/genesis.json"] = io.BufferedReader(
277-
cast(io.RawIOBase, io.BytesIO(genesis_bytes))
278-
)
302+
client_files = {
303+
"/genesis.json": io.BufferedReader(io.BytesIO(genesis_bytes))
304+
}
279305
# Get containers before starting client
280306
containers_before = get_docker_containers()
281307

packages/testing/src/execution_testing/fixtures/base.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,22 @@ def fixture_format_discriminator(v: Any) -> str | None:
2626
"""Discriminator function that returns the model type as a string."""
2727
if v is None:
2828
return None
29+
info_dict: Dict | None = None
2930
if isinstance(v, dict):
3031
info_dict = v.get("_info")
3132
elif hasattr(v, "info"):
3233
info_dict = v.info
33-
assert info_dict is not None, (
34-
f"Fixture does not have an info field, cannot determine fixture format: {v}"
35-
)
34+
if info_dict is None:
35+
raise ValueError(
36+
f"Fixture does not have an info field, cannot determine fixture format: {v}"
37+
)
3638
fixture_format = info_dict.get("fixture-format")
3739
if not fixture_format:
3840
fixture_format = info_dict.get("fixture_format")
39-
assert fixture_format is not None, (
40-
f"Fixture format not found in info field: {info_dict}"
41-
)
41+
if fixture_format is None:
42+
raise ValueError(
43+
f"Fixture format not found in info field: {info_dict}"
44+
)
4245
return fixture_format
4346

4447

0 commit comments

Comments
 (0)