From 3d44dfd6d38438e67d717e5f624c7ddbbd5d9c27 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 27 Jun 2025 22:44:17 +0000 Subject: [PATCH 1/2] Use `TransitionTool.default_tool` --- src/ethereum_clis/clis/eels_t8n.py | 139 ---------------------------- src/pytest_plugins/filler/filler.py | 31 +++---- src/pytest_plugins/forks/forks.py | 10 +- 3 files changed, 17 insertions(+), 163 deletions(-) delete mode 100644 src/ethereum_clis/clis/eels_t8n.py diff --git a/src/ethereum_clis/clis/eels_t8n.py b/src/ethereum_clis/clis/eels_t8n.py deleted file mode 100644 index e5c4bfaa589..00000000000 --- a/src/ethereum_clis/clis/eels_t8n.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -EELS T8N transition tool. - -https://github.com/ethereum/execution-specs -""" - -from abc import ABC, abstractmethod -from typing import List - -from ethereum_clis import TransitionTool, TransitionToolOutput -from ethereum_test_base_types import BlobSchedule -from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, Environment, Transaction - - -class EELST8NWrapper(TransitionTool, ABC): - """ - Abstract base class for EELS T8N wrapper implementations. - - This class provides an interface for calling the EELS T8N entrypoint directly - without going through a binary. It implements a registration mechanism for - concrete implementations to be set as the default wrapper. - - Attributes: - _default (class): The registered default subclass to use for instantiation. - Should be set via `set_default()`. - - Usage: - 1. Create a concrete subclass implementing all abstract methods - 2. Register it using `EELST8NWrapper.set_default(YourSubclass)` - 3. Instantiate via `EELST8NWrapper.default()` - - """ - - _default = None - - @classmethod - def set_default(cls, default_cls): - """ - Register a default subclass for instantiation. - - Args: - default_cls: The subclass to register as default - - Raises: - TypeError: If `default_cls` is not a subclass of EELST8NWrapper - - """ - if not issubclass(default_cls, cls): - raise TypeError(f"{default_cls.__name__} is not a subclass of {cls.__name__}") - cls._default = default_cls - - @classmethod - def default(cls, *args, **kwargs): - """ - Instantiate the registered default implementation. - - Returns: - Instance of the registered default subclass - - Raises: - RuntimeError: If no default implementation has been registered - - """ - if cls._default is None: - raise RuntimeError("Default subclass not set!") - return cls._default(*args, **kwargs) - - @abstractmethod - def version(self): - """Return the version string of the transition tool implementation.""" - pass - - @abstractmethod - def is_fork_supported(self, fork) -> bool: - """Check if a specific fork is supported by the tool.""" - pass - - @abstractmethod - def _evaluate_eels_t8n( - self, - *, - t8n_data: TransitionTool.TransitionToolData, - debug_output_path: str = "", - ) -> TransitionToolOutput: - """ - Execute the transition tool implementation (core functionality). - - This must be implemented by concrete subclasses to provide the actual - transition tool evaluation logic. - - Args: - t8n_data: Input data for the state transition - debug_output_path: Optional path for writing debug output - - Returns: - TransitionToolOutput: Result of the state transition - - """ - pass - - def evaluate( - self, - *, - alloc: Alloc, - txs: List[Transaction], - env: Environment, - fork: Fork, - chain_id: int, - reward: int, - blob_schedule: BlobSchedule | None, - debug_output_path: str = "", - state_test: bool = False, - slow_request: bool = False, - ) -> TransitionToolOutput: - """ - Execute the relevant evaluate method as required by the `t8n` tool. - - If a client's `t8n` tool varies from the default behavior, this method - can be overridden. - """ - fork_name = fork.transition_tool_name( - block_number=env.number, - timestamp=env.timestamp, - ) - if env.number == 0: - reward = -1 - t8n_data = self.TransitionToolData( - alloc=alloc, - txs=txs, - env=env, - fork_name=fork_name, - chain_id=chain_id, - reward=reward, - blob_schedule=blob_schedule, - state_test=state_test, - ) - - return self._evaluate_eels_t8n(t8n_data=t8n_data, debug_output_path=debug_output_path) diff --git a/src/pytest_plugins/filler/filler.py b/src/pytest_plugins/filler/filler.py index ec645ebba2a..5d387aad9d2 100644 --- a/src/pytest_plugins/filler/filler.py +++ b/src/pytest_plugins/filler/filler.py @@ -22,7 +22,6 @@ from cli.gen_index import generate_fixtures_index from config import AppConfig from ethereum_clis import TransitionTool -from ethereum_clis.clis.eels_t8n import EELST8NWrapper from ethereum_clis.clis.geth import FixtureConsumerTool from ethereum_test_base_types import Account, Address, Alloc, ReferenceSpec from ethereum_test_fixtures import ( @@ -118,19 +117,12 @@ def default_html_report_file_path() -> str: def pytest_addoption(parser: pytest.Parser): """Add command-line options to pytest.""" evm_group = parser.getgroup("evm", "Arguments defining evm executable behavior") - evm_group.addoption( - "--eels", - action="store_true", - dest="eels", - default=False, - help="Use eels t8n entry point", - ) evm_group.addoption( "--evm-bin", action="store", dest="evm_bin", type=Path, - default="ethereum-spec-evm-resolver", + default=None, help=( "Path to an evm executable (or name of an executable in the PATH) that provides `t8n`." " Default: `ethereum-spec-evm-resolver`." @@ -353,11 +345,13 @@ def pytest_configure(config): # Instantiate the transition tool here to check that the binary path/trace option is valid. # This ensures we only raise an error once, if appropriate, instead of for every test. - if config.getoption("eels", False): - t8n = EELST8NWrapper.default(trace=config.getoption("evm_collect_traces")) + evm_bin = config.getoption("evm_bin") + if evm_bin is None: + assert TransitionTool.default_tool is not None, "No default transition tool found" + t8n = TransitionTool.default_tool(trace=config.getoption("evm_collect_traces")) else: t8n = TransitionTool.from_binary_path( - binary_path=config.getoption("evm_bin"), trace=config.getoption("evm_collect_traces") + binary_path=evm_bin, trace=config.getoption("evm_collect_traces") ) if ( isinstance(config.getoption("numprocesses"), int) @@ -549,7 +543,7 @@ def pytest_html_report_title(report): @pytest.fixture(autouse=True, scope="session") -def evm_bin(request: pytest.FixtureRequest) -> Path: +def evm_bin(request: pytest.FixtureRequest) -> Path | None: """Return configured evm tool binary path used to run t8n.""" return request.config.getoption("evm_bin") @@ -564,10 +558,13 @@ def verify_fixtures_bin(request: pytest.FixtureRequest) -> Path | None: @pytest.fixture(autouse=True, scope="session") -def t8n(request: pytest.FixtureRequest, evm_bin: Path) -> Generator[TransitionTool, None, None]: +def t8n( + request: pytest.FixtureRequest, evm_bin: Path | None +) -> Generator[TransitionTool, None, None]: """Return configured transition tool.""" - if request.config.getoption("eels"): - t8n = EELST8NWrapper.default(trace=request.config.getoption("evm_collect_traces")) + if evm_bin is None: + assert TransitionTool.default_tool is not None, "No default transition tool found" + t8n = TransitionTool.default_tool(trace=request.config.getoption("evm_collect_traces")) else: t8n = TransitionTool.from_binary_path( binary_path=evm_bin, trace=request.config.getoption("evm_collect_traces") @@ -604,7 +601,7 @@ def do_fixture_verification( def evm_fixture_verification( request: pytest.FixtureRequest, do_fixture_verification: bool, - evm_bin: Path, + evm_bin: Path | None, verify_fixtures_bin: Path | None, ) -> Generator[FixtureConsumer | None, None, None]: """ diff --git a/src/pytest_plugins/forks/forks.py b/src/pytest_plugins/forks/forks.py index 95c6800ace4..ed1e27e4e59 100644 --- a/src/pytest_plugins/forks/forks.py +++ b/src/pytest_plugins/forks/forks.py @@ -14,7 +14,6 @@ from pytest import Mark, Metafunc from ethereum_clis import TransitionTool -from ethereum_clis.clis.eels_t8n import EELST8NWrapper from ethereum_test_forks import ( Fork, get_deployed_forks, @@ -525,14 +524,11 @@ def get_fork_option(config, option_name: str, parameter_name: str) -> Set[Fork]: return evm_bin = config.getoption("evm_bin", None) - eels = config.getoption("eels", False) - - if eels: - t8n = EELST8NWrapper.default() + if evm_bin is None: + assert TransitionTool.default_tool is not None, "No default transition tool found" + t8n = TransitionTool.default_tool() elif evm_bin is not None: t8n = TransitionTool.from_binary_path(binary_path=evm_bin) - else: - pytest.exit("One of --eels or --evm-bin should be specified") config.unsupported_forks = frozenset( # type: ignore fork for fork in selected_fork_set if not t8n.is_fork_supported(fork) From 5ee5065f7253e25b18a8e18bdcbbcf0bf66ccfc4 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 27 Jun 2025 23:06:12 +0000 Subject: [PATCH 2/2] Refactor `TransitionTool.evaluate` --- src/ethereum_clis/clis/besu.py | 32 +++--------- .../tests/test_execution_specs.py | 16 +++--- src/ethereum_clis/transition_tool.py | 51 ++++++++----------- src/ethereum_test_specs/blockchain.py | 16 +++--- src/ethereum_test_specs/state.py | 18 ++++--- 5 files changed, 56 insertions(+), 77 deletions(-) diff --git a/src/ethereum_clis/clis/besu.py b/src/ethereum_clis/clis/besu.py index dc6c59f9854..6bde1e844f5 100644 --- a/src/ethereum_clis/clis/besu.py +++ b/src/ethereum_clis/clis/besu.py @@ -7,11 +7,10 @@ import tempfile import textwrap from pathlib import Path -from typing import ClassVar, Dict, List, Optional +from typing import ClassVar, Dict, Optional import requests # type: ignore -from ethereum_test_base_types import BlobSchedule from ethereum_test_exceptions import ( BlockException, ExceptionBase, @@ -19,10 +18,9 @@ TransactionException, ) from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, Environment, Transaction from ..transition_tool import TransitionTool, dump_files_to_directory, model_dump_config -from ..types import TransitionToolInput, TransitionToolOutput +from ..types import TransitionToolOutput class BesuTransitionTool(TransitionTool): @@ -98,36 +96,20 @@ def shutdown(self): def evaluate( self, *, - alloc: Alloc, - txs: List[Transaction], - env: Environment, - fork: Fork, - chain_id: int, - reward: int, - blob_schedule: BlobSchedule | None = None, + transition_tool_data: TransitionTool.TransitionToolData, debug_output_path: str = "", - state_test: bool = False, slow_request: bool = False, ) -> TransitionToolOutput: """Execute `evm t8n` with the specified arguments.""" if not self.process: self.start_server() - fork_name = fork.transition_tool_name( - block_number=env.number, - timestamp=env.timestamp, - ) - - input_json = TransitionToolInput( - alloc=alloc, - txs=txs, - env=env, - ).model_dump(mode="json", **model_dump_config) + input_json = transition_tool_data.to_input().model_dump(mode="json", **model_dump_config) state_json = { - "fork": fork_name, - "chainid": chain_id, - "reward": reward, + "fork": transition_tool_data.fork_name, + "chainid": transition_tool_data.chain_id, + "reward": transition_tool_data.reward, } post_data = {"state": state_json, "input": input_json} diff --git a/src/ethereum_clis/tests/test_execution_specs.py b/src/ethereum_clis/tests/test_execution_specs.py index ed115b88419..410c58a831c 100644 --- a/src/ethereum_clis/tests/test_execution_specs.py +++ b/src/ethereum_clis/tests/test_execution_specs.py @@ -164,13 +164,15 @@ def test_evm_t8n( expected = json.load(exp) t8n_output = default_t8n.evaluate( - alloc=alloc, - txs=txs, - env=env, - fork=Berlin, - chain_id=1, - reward=0, - blob_schedule=Berlin.blob_schedule(), + transition_tool_data=TransitionTool.TransitionToolData( + alloc=alloc, + txs=txs, + env=env, + fork=Berlin, + chain_id=1, + reward=0, + blob_schedule=Berlin.blob_schedule(), + ), ) assert to_json(t8n_output.alloc) == expected.get("alloc") if isinstance(default_t8n, ExecutionSpecsTransitionTool): diff --git a/src/ethereum_clis/transition_tool.py b/src/ethereum_clis/transition_tool.py index 27b0471baaf..f732c12c136 100644 --- a/src/ethereum_clis/transition_tool.py +++ b/src/ethereum_clis/transition_tool.py @@ -136,11 +136,24 @@ class TransitionToolData: alloc: Alloc txs: List[Transaction] env: Environment - fork_name: str + fork: Fork chain_id: int reward: int blob_schedule: BlobSchedule | None - state_test: bool + state_test: bool = False + + @property + def fork_name(self) -> str: + """Return the fork name.""" + return self.fork.transition_tool_name( + block_number=self.env.number, + timestamp=self.env.timestamp, + ) + + def __post_init__(self): + """Modify the reward if the environment number is 0.""" + if self.env.number == 0: + self.reward = -1 def to_input(self) -> TransitionToolInput: """Convert the data to a TransactionToolInput object.""" @@ -493,15 +506,8 @@ def dump_debug_stream( def evaluate( self, *, - alloc: Alloc, - txs: List[Transaction], - env: Environment, - fork: Fork, - chain_id: int, - reward: int, - blob_schedule: BlobSchedule | None, + transition_tool_data: TransitionToolData, debug_output_path: str = "", - state_test: bool = False, slow_request: bool = False, ) -> TransitionToolOutput: """ @@ -510,36 +516,21 @@ def evaluate( If a client's `t8n` tool varies from the default behavior, this method can be overridden. """ - fork_name = fork.transition_tool_name( - block_number=env.number, - timestamp=env.timestamp, - ) - if env.number == 0: - reward = -1 - t8n_data = self.TransitionToolData( - alloc=alloc, - txs=txs, - env=env, - fork_name=fork_name, - chain_id=chain_id, - reward=reward, - blob_schedule=blob_schedule, - state_test=state_test, - ) - if self.t8n_use_server: if not self.process: self.start_server() return self._evaluate_server( - t8n_data=t8n_data, + t8n_data=transition_tool_data, debug_output_path=debug_output_path, timeout=SLOW_REQUEST_TIMEOUT if slow_request else NORMAL_SERVER_TIMEOUT, ) if self.t8n_use_stream: - return self._evaluate_stream(t8n_data=t8n_data, debug_output_path=debug_output_path) + return self._evaluate_stream( + t8n_data=transition_tool_data, debug_output_path=debug_output_path + ) return self._evaluate_filesystem( - t8n_data=t8n_data, + t8n_data=transition_tool_data, debug_output_path=debug_output_path, ) diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index 4ec141632c8..b43211d072c 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -498,13 +498,15 @@ def generate_block_data( ) transition_tool_output = t8n.evaluate( - alloc=previous_alloc, - txs=txs, - env=env, - fork=fork, - chain_id=self.chain_id, - reward=fork.get_reward(env.number, env.timestamp), - blob_schedule=fork.blob_schedule(), + transition_tool_data=TransitionTool.TransitionToolData( + alloc=previous_alloc, + txs=txs, + env=env, + fork=fork, + chain_id=self.chain_id, + reward=fork.get_reward(env.number, env.timestamp), + blob_schedule=fork.blob_schedule(), + ), debug_output_path=self.get_next_transition_tool_output_path(), slow_request=self.is_tx_gas_heavy_test(), ) diff --git a/src/ethereum_test_specs/state.py b/src/ethereum_test_specs/state.py index 3bb63f762d3..04fdf13bcab 100644 --- a/src/ethereum_test_specs/state.py +++ b/src/ethereum_test_specs/state.py @@ -182,15 +182,17 @@ def make_state_test_fixture( raise Exception(f"Empty accounts in pre state: {empty_accounts}") transition_tool_output = t8n.evaluate( - alloc=pre_alloc, - txs=[tx], - env=env, - fork=fork, - chain_id=self.chain_id, - reward=0, # Reward on state tests is always zero - blob_schedule=fork.blob_schedule(), + transition_tool_data=TransitionTool.TransitionToolData( + alloc=pre_alloc, + txs=[tx], + env=env, + fork=fork, + chain_id=self.chain_id, + reward=0, # Reward on state tests is always zero + blob_schedule=fork.blob_schedule(), + state_test=True, + ), debug_output_path=self.get_next_transition_tool_output_path(), - state_test=True, slow_request=self.is_tx_gas_heavy_test(), )