|
13 | 13 | import subprocess |
14 | 14 | import sys |
15 | 15 | from pathlib import Path |
16 | | -from typing import Dict, Optional, Tuple, cast |
| 16 | +from typing import Dict, Optional, Self |
17 | 17 |
|
18 | 18 | import click |
19 | 19 | from hive.simulation import Simulation |
20 | 20 | from hive.testing import HiveTestResult |
| 21 | +from pydantic import ( |
| 22 | + BaseModel, |
| 23 | + Field, |
| 24 | + SerializerFunctionWrapHandler, |
| 25 | + ValidationError, |
| 26 | + model_serializer, |
| 27 | +) |
21 | 28 |
|
22 | | -from execution_testing.base_types import Alloc, to_json |
| 29 | +from execution_testing.base_types import Alloc |
23 | 30 | from execution_testing.cli.pytest_commands.plugins.consume.simulators.helpers.ruleset import ( |
24 | 31 | ruleset, |
25 | 32 | ) |
26 | | -from execution_testing.fixtures import BlockchainFixtureCommon |
| 33 | +from execution_testing.fixtures import ( |
| 34 | + BlockchainEngineFixture, |
| 35 | + BlockchainFixtureCommon, |
| 36 | +) |
27 | 37 | from execution_testing.fixtures.blockchain import FixtureHeader |
28 | 38 | from execution_testing.fixtures.file import Fixtures |
29 | 39 | from execution_testing.fixtures.pre_alloc_groups import PreAllocGroup |
@@ -119,54 +129,79 @@ def extract_client_files( |
119 | 129 | return extracted_files |
120 | 130 |
|
121 | 131 |
|
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 |
127 | 134 | 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 |
135 | 148 |
|
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() |
139 | 153 |
|
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 | + ) |
141 | 169 | raise ValueError( |
142 | | - f"Fixture {fixture_id} is not a blockchain fixture" |
| 170 | + f"Fixture {fixture_path} does not contain a genesis" |
143 | 171 | ) |
| 172 | + except ValidationError: |
| 173 | + pass |
144 | 174 |
|
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 |
155 | 186 |
|
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 | + ) |
162 | 190 |
|
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 | + } |
170 | 205 |
|
171 | 206 |
|
172 | 207 | @click.command() |
@@ -257,25 +292,16 @@ def extract_config( |
257 | 292 | click.echo(f"Using fixture: {fixture_path}") |
258 | 293 |
|
259 | 294 | # 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") |
273 | 300 |
|
274 | 301 | 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 | + } |
279 | 305 | # Get containers before starting client |
280 | 306 | containers_before = get_docker_containers() |
281 | 307 |
|
|
0 commit comments