Skip to content

Draft: Steel Merge #1763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions eels_resolutions.json

This file was deleted.

7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ classifiers = [
]
dependencies = [
"click>=8.1.0,<9",
"ethereum-execution==1.17.0rc6.dev1",
"hive.py @ git+https://github.com/marioevz/hive.py",
"ethereum-spec-evm-resolver",
"gitpython>=3.1.31,<4",
"PyJWT>=2.3.0,<3",
"tenacity>8.2.0,<9",
"requests>=2.31.0,<3",
"requests_unixsocket2>=0.4.0",
"colorlog>=6.7.0,<7",
"platformdirs>=4.2,<5",
"py-ecc>=8.0.0b2,<9",
"pytest>=8,<9",
"pytest-custom-report>=1.0.1,<2",
"pytest-html>=4.1.0,<5",
Expand Down Expand Up @@ -145,6 +145,3 @@ plugins = ["pydantic.mypy"]
skip = ".venv,__pycache__,.git,build,dist,*.pyc,*.lock"
check-filenames = true
ignore-words-list = "ingenuous"

[tool.uv.sources]
ethereum-spec-evm-resolver = { git = "https://github.com/petertdavies/ethereum-spec-evm-resolver", rev = "0e5609737ce4f86dc98cca1a5cf0eb64b8cddef2" }
1 change: 0 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ addopts =
-p pytest_plugins.filler.ported_tests
-p pytest_plugins.shared.execute_fill
-p pytest_plugins.forks.forks
-p pytest_plugins.eels_resolver
-p pytest_plugins.help.help
--tb short
--ignore tests/cancun/eip4844_blobs/point_evaluation_vectors/
Expand Down
29 changes: 10 additions & 19 deletions src/ethereum_clis/clis/execution_specs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
"""
Ethereum Specs EVM Resolver Transition Tool Interface.

https://github.com/petertdavies/ethereum-spec-evm-resolver
"""
"""Ethereum Specs EVM Transition Tool Interface."""

import os
import re
Expand All @@ -28,23 +24,21 @@

class ExecutionSpecsTransitionTool(TransitionTool):
"""
Ethereum Specs EVM Resolver `ethereum-spec-evm-resolver` Transition Tool wrapper class.
Ethereum Specs EVM `ethereum-spec-evm` Transition Tool wrapper class.

`ethereum-spec-evm-resolver` is installed by default for `execution-spec-tests`:
`ethereum-spec-evm` is installed by default for `execution-spec-tests`:
```console
uv run fill --evm-bin=ethereum-spec-evm-resolver
uv run fill --evm-bin=ethereum-spec-evm
```

To use a specific version of the `ethereum-spec-evm-resolver` tool, update it to the
To use a specific version of the `ethereum-spec-evm` tool, update it to the
desired version in `pyproject.toml`.

The `ethereum-spec-evm-resolver` tool essentially wraps around the EELS evm daemon. It can
handle requests for different EVM forks, even when those forks are implemented by different
versions of EELS hosted in different places.
The `ethereum-spec-evm` tool is the EELS evm daemon.
"""

default_binary = Path("ethereum-spec-evm-resolver")
detect_binary_pattern = re.compile(r"^ethereum-spec-evm-resolver\b")
default_binary = Path("ethereum-spec-evm")
detect_binary_pattern = re.compile(r"^ethereum-spec-evm\b")
t8n_use_server: bool = True
server_dir: Optional[TemporaryDirectory] = None

Expand All @@ -64,13 +58,10 @@ def __init__(
result = subprocess.run(args, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise Exception(
"ethereum-spec-evm-resolver process unexpectedly returned a non-zero status code: "
f"{e}."
f"ethereum-spec-evm process unexpectedly returned a non-zero status code: {e}."
) from e
except Exception as e:
raise Exception(
f"Unexpected exception calling ethereum-spec-evm-resolver: {e}."
) from e
raise Exception(f"Unexpected exception calling ethereum-spec-evm: {e}.") from e
self.help_string = result.stdout

def start_server(self):
Expand Down
22 changes: 10 additions & 12 deletions src/ethereum_clis/ethereum_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,23 @@ class EthereumCLI:

registered_tools: List[Type[Any]] = []
default_tool: Optional[Type[Any]] = None
binary: Path
binary: Optional[Path]
default_binary: Path
detect_binary_pattern: Pattern
version_flag: str = "-v"
cached_version: Optional[str] = None

def __init__(self, *, binary: Optional[Path] = None):
"""Abstract initialization method that all subclasses must implement."""
# If an evm_bin is not specified, use the eels native t8n
if binary is None:
binary = self.default_binary
else:
# improve behavior of which by resolving the path: ~/relative paths don't work
resolved_path = Path(os.path.expanduser(binary)).resolve()
if resolved_path.exists():
binary = resolved_path
return

# improve behavior of which by resolving the path: ~/relative paths don't work
resolved_path = Path(os.path.expanduser(binary)).resolve()
if resolved_path.exists():
binary = resolved_path
binary = shutil.which(binary) # type: ignore
if not binary:
raise CLINotFoundInPathError(binary=binary)
Comment on lines -51 to -58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it'll give bad ux/unexpected errors for non-eels tools that don't get found in the PATH. Would need to test.

self.binary = Path(binary)

@classmethod
Expand All @@ -76,10 +75,9 @@ def from_binary_path(cls, *, binary_path: Optional[Path], **kwargs) -> Any:
This method will attempt to detect the CLI version and instantiate the appropriate
subclass based on the version output by running the CLI with the version flag.
"""
assert cls.default_tool is not None, "default CLI implementation was never set"

if binary_path is None:
return cls.default_tool(binary=binary_path, **kwargs)
assert cls.default_tool is not None
return cls.default_tool(**kwargs)

resolved_path = Path(os.path.expanduser(binary_path)).resolve()
if resolved_path.exists():
Expand Down
31 changes: 31 additions & 0 deletions src/ethereum_clis/transition_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class TransitionTool(EthereumCLI):
cached_version: Optional[str] = None
t8n_use_stream: bool = False
t8n_use_server: bool = False
t8n_use_eels: bool = False
server_url: str
process: Optional[subprocess.Popen] = None

Expand Down Expand Up @@ -424,6 +425,31 @@ def _evaluate_stream(

return output

def _evaluate_custom_t8n(
self,
*,
t8n_data: TransitionToolData,
debug_output_path: str = "",
) -> TransitionToolOutput:
"""
Execute a custom transition tool implementation.

This method can be overridden by subclasses to provide custom transition tool
implementations. The default implementation raises NotImplementedError.

Args:
t8n_data: The transition tool data to process
debug_output_path: Optional path to write debug output

Returns:
The transition tool output

Raises:
NotImplementedError: If not implemented by the subclass

"""
raise NotImplementedError("Custom transition tool implementation not provided")

def construct_args_stream(
self, t8n_data: TransitionToolData, temp_dir: tempfile.TemporaryDirectory
) -> List[str]:
Expand Down Expand Up @@ -527,6 +553,11 @@ def evaluate(
state_test=state_test,
)

if self.t8n_use_eels:
return self._evaluate_custom_t8n(
t8n_data=t8n_data, debug_output_path=debug_output_path
)

if self.t8n_use_server:
if not self.process:
self.start_server()
Expand Down
70 changes: 65 additions & 5 deletions src/ethereum_test_types/account_types.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""Account-related types for Ethereum tests."""

from dataclasses import dataclass
from typing import List, Literal
from dataclasses import dataclass, field
from typing import Dict, List, Literal, Optional, Tuple

from coincurve.keys import PrivateKey
from ethereum.frontier.fork_types import Account as FrontierAccount
from ethereum.frontier.fork_types import Address as FrontierAddress
from ethereum.frontier.state import State, set_account, set_storage, state_root
from ethereum_types.bytes import Bytes20
from ethereum_types.numeric import U256, Bytes32, Uint
from pydantic import PrivateAttr

Expand All @@ -26,8 +24,70 @@
)
from ethereum_test_vm import EVMCodeType

from .trie import EMPTY_TRIE_ROOT, FrontierAccount, Trie, root, trie_get, trie_set
from .utils import keccak256

FrontierAddress = Bytes20


@dataclass
class State:
"""Contains all information that is preserved between transactions."""

_main_trie: Trie[Bytes20, Optional[FrontierAccount]] = field(
default_factory=lambda: Trie(secured=True, default=None)
)
_storage_tries: Dict[Bytes20, Trie[Bytes32, U256]] = field(default_factory=dict)
_snapshots: List[
Tuple[
Trie[Bytes20, Optional[FrontierAccount]],
Dict[Bytes20, Trie[Bytes32, U256]],
]
] = field(default_factory=list)


def set_account(state: State, address: Bytes20, account: Optional[FrontierAccount]) -> None:
"""
Set the `Account` object at an address. Setting to `None` deletes
the account (but not its storage, see `destroy_account()`).
"""
trie_set(state._main_trie, address, account)


def set_storage(state: State, address: Bytes20, key: Bytes32, value: U256) -> None:
"""
Set a value at a storage key on an account. Setting to `U256(0)` deletes
the key.
"""
assert trie_get(state._main_trie, address) is not None

trie = state._storage_tries.get(address)
if trie is None:
trie = Trie(secured=True, default=U256(0))
state._storage_tries[address] = trie
trie_set(trie, key, value)
if trie._data == {}:
del state._storage_tries[address]


def storage_root(state: State, address: Bytes20) -> Bytes32:
"""Calculate the storage root of an account."""
assert not state._snapshots
if address in state._storage_tries:
return root(state._storage_tries[address])
else:
return EMPTY_TRIE_ROOT


def state_root(state: State) -> Bytes32:
"""Calculate the state root."""
assert not state._snapshots

def get_storage_root(address: Bytes20) -> Bytes32:
return storage_root(state, address)

return root(state._main_trie, get_storage_root=get_storage_root)


class EOA(Address):
"""
Expand Down
Loading
Loading