diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 68a560b2b9..23edbc7f9f 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -25,6 +25,7 @@ FixtureEngineNewPayload, Header, HistoryStorageAddress, + HiveFixture, JSONEncoder, Removable, Storage, @@ -77,6 +78,7 @@ "FixtureEngineNewPayload", "Header", "HistoryStorageAddress", + "HiveFixture", "Initcode", "JSONEncoder", "Opcode", diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index c1b55f4347..672616e190 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -41,6 +41,7 @@ Hash, Header, HeaderNonce, + HiveFixture, JSONEncoder, Number, Removable, @@ -77,6 +78,7 @@ "Header", "HeaderNonce", "HistoryStorageAddress", + "HiveFixture", "JSONEncoder", "Number", "Removable", diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index df74a9a448..0e9a566f1a 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2642,13 +2642,6 @@ class FixtureBlock: to_json=True, ), ) - new_payload: Optional[FixtureEngineNewPayload] = field( - default=None, - json_encoder=JSONEncoder.Field( - name="engineNewPayload", - to_json=True, - ), - ) expected_exception: Optional[str] = field( default=None, json_encoder=JSONEncoder.Field( @@ -2690,9 +2683,9 @@ class FixtureBlock: @dataclass(kw_only=True) -class Fixture: +class BaseFixture: """ - Cross-client compatible Ethereum test fixture. + Base Ethereum test fixture class. """ info: Dict[str, str] = field( @@ -2702,36 +2695,78 @@ class Fixture: to_json=True, ), ) - blocks: List[FixtureBlock] = field( + fork: str = field( json_encoder=JSONEncoder.Field( - name="blocks", - to_json=True, + name="network", ), ) - fcu_version: Optional[int] = field( + name: str = field( + default="", json_encoder=JSONEncoder.Field( - name="engineFcuVersion", + skip=True, ), ) - genesis: FixtureHeader = field( + _json: Dict[str, Any] | None = field( + default=None, json_encoder=JSONEncoder.Field( - name="genesisBlockHeader", - to_json=True, + skip=True, ), ) + + def __post_init__(self): + """ + Post init hook to convert to JSON after instantiation. + """ + self._json = to_json(self) + + def to_json(self) -> Dict[str, Any]: + """ + Convert to JSON. + """ + assert self._json is not None, "Fixture not initialized" + self._json["_info"] = self.info + return self._json + + def fill_info( + self, + t8n: TransitionTool, + ref_spec: ReferenceSpec | None, + ): + """ + Fill the info field for this fixture + """ + self.info["filling-transition-tool"] = t8n.version() + if ref_spec is not None: + ref_spec.write_info(self.info) + + +@dataclass(kw_only=True) +class Fixture(BaseFixture): + """ + Cross-client specific test fixture information. + """ + genesis_rlp: Bytes = field( json_encoder=JSONEncoder.Field( name="genesisRLP", ), ) - head: Hash = field( + genesis: FixtureHeader = field( json_encoder=JSONEncoder.Field( - name="lastblockhash", + name="genesisBlockHeader", + to_json=True, ), ) - fork: str = field( + blocks: Optional[List[FixtureBlock]] = field( + default=None, json_encoder=JSONEncoder.Field( - name="network", + name="blocks", + to_json=True, + ), + ) + head: Hash = field( + json_encoder=JSONEncoder.Field( + name="lastblockhash", ), ) pre_state: Mapping[str, Account] = field( @@ -2754,42 +2789,45 @@ class Fixture: name="sealEngine", ), ) - name: str = field( - default="", + + +@dataclass(kw_only=True) +class HiveFixture(BaseFixture): + """ + Hive specific test fixture information. + """ + + genesis: FixtureHeader = field( json_encoder=JSONEncoder.Field( - skip=True, + name="genesisBlockHeader", + to_json=True, ), ) - - _json: Dict[str, Any] | None = field( + payloads: Optional[List[Optional[FixtureEngineNewPayload]]] = field( default=None, json_encoder=JSONEncoder.Field( - skip=True, + name="engineNewPayloads", + to_json=True, + ), + ) + fcu_version: Optional[int] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="engineFcuVersion", + ), + ) + pre_state: Mapping[str, Account] = field( + json_encoder=JSONEncoder.Field( + name="pre", + cast_type=Alloc, + to_json=True, + ), + ) + post_state: Optional[Mapping[str, Account]] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="postState", + cast_type=Alloc, + to_json=True, ), ) - - def __post_init__(self): - """ - Post init hook to convert to JSON after instantiation. - """ - self._json = to_json(self) - - def to_json(self) -> Dict[str, Any]: - """ - Convert to JSON. - """ - assert self._json is not None, "Fixture not initialized" - self._json["_info"] = self.info - return self._json - - def fill_info( - self, - t8n: TransitionTool, - ref_spec: ReferenceSpec | None, - ): - """ - Fill the info field for this fixture - """ - self.info["filling-transition-tool"] = t8n.version() - if ref_spec is not None: - ref_spec.write_info(self.info) diff --git a/src/ethereum_test_tools/filling/fill.py b/src/ethereum_test_tools/filling/fill.py index b675d701a2..7eb07a777b 100644 --- a/src/ethereum_test_tools/filling/fill.py +++ b/src/ethereum_test_tools/filling/fill.py @@ -1,12 +1,12 @@ """ Filler object definitions. """ -from typing import List, Optional +from typing import List, Optional, Union from ethereum_test_forks import Fork from evm_transition_tool import TransitionTool -from ..common import Fixture, alloc_to_accounts +from ..common import Fixture, HiveFixture, alloc_to_accounts from ..reference_spec.reference_spec import ReferenceSpec from ..spec import BaseTest @@ -18,7 +18,7 @@ def fill_test( engine: str, spec: ReferenceSpec | None, eips: Optional[List[int]] = None, -) -> Fixture: +) -> Optional[Union[Fixture, HiveFixture]]: """ Fills fixtures for the specified fork. """ @@ -26,7 +26,7 @@ def fill_test( pre, genesis_rlp, genesis = test_spec.make_genesis(t8n, fork) - (blocks, head, alloc, fcu_version) = test_spec.make_blocks( + (blocks, payloads, head, alloc, fcu_version) = test_spec.make_blocks( t8n, genesis, pre, @@ -34,19 +34,37 @@ def fill_test( eips=eips, ) - fork_name = fork.name() - fixture = Fixture( - blocks=blocks, - genesis=genesis, - genesis_rlp=genesis_rlp, - head=head, - fork="+".join([fork_name] + [str(eip) for eip in eips]) if eips is not None else fork_name, - pre_state=pre, - post_state=alloc_to_accounts(alloc), - seal_engine=engine, - name=test_spec.tag, - fcu_version=fcu_version, + network_info = ( + "+".join([fork.name()] + [str(eip) for eip in eips]) if eips is not None else fork.name() ) + + fixture: Union[Fixture, HiveFixture] + if test_spec.base_test_config.enable_hive: + if fork.engine_new_payload_version() is not None: + fixture = HiveFixture( + payloads=payloads, + fcu_version=fcu_version, + genesis=genesis, + fork=network_info, + pre_state=pre, + post_state=alloc_to_accounts(alloc), + name=test_spec.tag, + ) + else: # pre Merge tests are not supported in Hive + # TODO: remove this logic. if hive enabled set --from to Merge + return None + else: + fixture = Fixture( + blocks=blocks, + genesis=genesis, + genesis_rlp=genesis_rlp, + head=head, + fork=network_info, + pre_state=pre, + post_state=alloc_to_accounts(alloc), + seal_engine=engine, + name=test_spec.tag, + ) fixture.fill_info(t8n, spec) return fixture diff --git a/src/ethereum_test_tools/spec/base_test.py b/src/ethereum_test_tools/spec/base_test.py index 416c6e5879..92df2e5102 100644 --- a/src/ethereum_test_tools/spec/base_test.py +++ b/src/ethereum_test_tools/spec/base_test.py @@ -17,6 +17,7 @@ Bytes, Environment, FixtureBlock, + FixtureEngineNewPayload, FixtureHeader, Hash, Transaction, @@ -127,7 +128,13 @@ def make_blocks( fork: Fork, chain_id: int = 1, eips: Optional[List[int]] = None, - ) -> Tuple[List[FixtureBlock], Hash, Dict[str, Any], Optional[int]]: + ) -> Tuple[ + Optional[List[FixtureBlock]], + Optional[List[Optional[FixtureEngineNewPayload]]], + Hash, + Dict[str, Any], + Optional[int], + ]: """ Generate the blockchain that must be executed sequentially during test. """ diff --git a/src/ethereum_test_tools/spec/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain_test.py index e9596fee87..6ba13934f4 100644 --- a/src/ethereum_test_tools/spec/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain_test.py @@ -51,6 +51,13 @@ def pytest_parameter_name(cls) -> str: """ return "blockchain_test" + @property + def hive_enabled(self) -> bool: + """ + Returns the true if hive fixture generation is enabled, false otherwise. + """ + return self.base_test_config.enable_hive + def make_genesis( self, t8n: TransitionTool, @@ -114,7 +121,7 @@ def make_block( previous_head: Hash, chain_id=1, eips: Optional[List[int]] = None, - ) -> Tuple[FixtureBlock, Environment, Dict[str, Any], Hash]: + ) -> Tuple[FixtureBlock, Optional[FixtureEngineNewPayload], Environment, Dict[str, Any], Hash]: """ Produces a block based on the previous environment and allocation. If the block is an invalid block, the environment and allocation @@ -207,18 +214,20 @@ def make_block( withdrawals=env.withdrawals, ) - new_payload: FixtureEngineNewPayload | None = None - if self.base_test_config.enable_hive: - new_payload = FixtureEngineNewPayload.from_fixture_header( + fixture_payload = ( + FixtureEngineNewPayload.from_fixture_header( fork=fork, header=header, transactions=txs, withdrawals=env.withdrawals, error_code=block.engine_api_error_code, ) + if self.hive_enabled + else None + ) if block.exception is None: - fixture_block = ( + return ( FixtureBlock( rlp=rlp, block_header=header, @@ -226,38 +235,20 @@ def make_block( txs=txs, ommers=[], withdrawals=env.withdrawals, - ) - if not self.base_test_config.enable_hive - else ( - FixtureBlock( - new_payload=new_payload, - ) - ) - ) - # Return environment and allocation of the following block - return ( - fixture_block, + ), + fixture_payload, env.apply_new_parent(header), next_alloc, header.hash, ) else: - fixture_block = ( + return ( FixtureBlock( rlp=rlp, block_number=Number(header.number), expected_exception=block.exception, - ) - if not self.base_test_config.enable_hive - else ( - FixtureBlock( - new_payload=new_payload, - expected_exception=block.exception, - ) - ) - ) - return ( - fixture_block, + ), + fixture_payload, previous_env, previous_alloc, previous_head, @@ -268,6 +259,7 @@ def make_block( rlp=Bytes(block.rlp), expected_exception=block.exception, ), + fixture_payload, previous_env, previous_alloc, previous_head, @@ -281,7 +273,13 @@ def make_blocks( fork: Fork, chain_id=1, eips: Optional[List[int]] = None, - ) -> Tuple[List[FixtureBlock], Hash, Dict[str, Any], Optional[int]]: + ) -> Tuple[ + Optional[List[FixtureBlock]], + Optional[List[Optional[FixtureEngineNewPayload]]], + Hash, + Dict[str, Any], + Optional[int], + ]: """ Create a block list from the blockchain test definition. Performs checks against the expected behavior of the test. @@ -289,13 +287,17 @@ def make_blocks( """ alloc = to_json(pre) env = Environment.from_parent_header(genesis) - blocks: List[FixtureBlock] = [] + fixture_blocks: List[FixtureBlock] | None = [] if not self.hive_enabled else None + + fixture_payloads: List[Optional[FixtureEngineNewPayload]] | None = ( + [] if self.hive_enabled else None + ) fcu_version: Optional[int] = None last_valid: Optional[FixtureHeader] = None head = genesis.hash if genesis.hash is not None else Hash(0) for block in self.blocks: - fixture_block, env, alloc, head = self.make_block( + fixture_block, fixture_payload, env, alloc, head = self.make_block( t8n=t8n, fork=fork, block=block, @@ -305,11 +307,14 @@ def make_blocks( chain_id=chain_id, eips=eips, ) - blocks.append(fixture_block) + if not self.hive_enabled and fixture_blocks is not None: + fixture_blocks.append(fixture_block) + if self.hive_enabled and fixture_payloads is not None: + fixture_payloads.append(fixture_payload) if block.exception is None: last_valid = fixture_block.block_header - if self.base_test_config.enable_hive and last_valid is not None: + if self.hive_enabled and last_valid: fcu_version = fork.engine_forkchoice_updated_version( block_number=last_valid.number, timestamp=last_valid.timestamp, @@ -321,7 +326,13 @@ def make_blocks( print_traces(t8n.get_traces()) raise e - return (blocks, head, alloc, fcu_version) + return ( + fixture_blocks, + fixture_payloads, + head, + alloc, + fcu_version, + ) BlockchainTestSpec = Callable[[str], Generator[BlockchainTest, None, None]] diff --git a/src/ethereum_test_tools/spec/state_test.py b/src/ethereum_test_tools/spec/state_test.py index 7f863cda56..82f81b460f 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -127,7 +127,13 @@ def make_blocks( fork: Fork, chain_id=1, eips: Optional[List[int]] = None, - ) -> Tuple[List[FixtureBlock], Hash, Dict[str, Any], Optional[int]]: + ) -> Tuple[ + Optional[List[FixtureBlock]], + Optional[List[Optional[FixtureEngineNewPayload]]], + Hash, + Dict[str, Any], + Optional[int], + ]: """ Create a block from the state test definition. Performs checks against the expected behavior of the test. @@ -178,33 +184,30 @@ def make_blocks( withdrawals=env.withdrawals, ) - # Hive specific fields - new_payload: FixtureEngineNewPayload | None = None fcu_version: int | None = None + fixture_payload: FixtureEngineNewPayload | None = None + fixture_block: FixtureBlock | None = None if self.base_test_config.enable_hive: - new_payload = FixtureEngineNewPayload.from_fixture_header( + fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) + fixture_payload = FixtureEngineNewPayload.from_fixture_header( fork=fork, header=header, transactions=txs, withdrawals=env.withdrawals, error_code=None, ) - fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) - - fixture_block = ( - FixtureBlock( + else: + fixture_block = FixtureBlock( rlp=block, block_header=header, txs=txs, ommers=[], withdrawals=env.withdrawals, ) - if not self.base_test_config.enable_hive - else (FixtureBlock(new_payload=new_payload)) - ) return ( - [fixture_block], + [fixture_block] if fixture_block is not None else None, + [fixture_payload] if fixture_payload is not None else None, header.hash, alloc, fcu_version, diff --git a/src/pytest_plugins/test_filler/test_filler.py b/src/pytest_plugins/test_filler/test_filler.py index d52537097e..01a5de5727 100644 --- a/src/pytest_plugins/test_filler/test_filler.py +++ b/src/pytest_plugins/test_filler/test_filler.py @@ -9,7 +9,7 @@ import os import re from pathlib import Path -from typing import Any, Dict, Generator, List, Tuple, Type +from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union import pytest @@ -20,6 +20,7 @@ BlockchainTest, BlockchainTestFiller, Fixture, + HiveFixture, StateTest, StateTestFiller, Yul, @@ -226,10 +227,13 @@ def __init__(self, output_dir: str, flat_output: bool) -> None: self.output_dir = output_dir self.flat_output = flat_output - def add_fixture(self, item, fixture: Fixture) -> None: + def add_fixture(self, item, fixture: Optional[Union[Fixture, HiveFixture]]) -> None: """ Adds a fixture to the list of fixtures of a given test case. """ + # TODO: remove this logic. if hive enabled set --from to Merge + if fixture is None: + return def get_module_dir(item) -> str: """