Skip to content
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

feature: Use evmone-t8n to fill tests #142

Merged
merged 30 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e7b7f9a
Initial integration of evmone-t8n.
spencer-tb Mar 14, 2023
f67e477
t8n: Add static method to detect tool type
marioevz Jul 3, 2023
ccfb102
t8n: nit method change
marioevz Jul 3, 2023
360233a
test_filler: rollback check for spec fixture parameters
marioevz Jul 3, 2023
a38de32
evm_transition_tool: refactor each tool to separate files
marioevz Jul 5, 2023
3a28c9c
evm_transition_tool/evmone: clarify tracing
marioevz Jul 5, 2023
eb01d57
refactor: return instantiated t8n tool object instead of subclass def
danceratopz Jul 5, 2023
6c60144
refactor: change evm-bin command-line argument to type Path
danceratopz Jul 5, 2023
34cc32d
refactor: move common constructor code to base class
danceratopz Jul 5, 2023
7915b58
refactor: move matches_binary_path to a classmethod in base class
danceratopz Jul 5, 2023
2fbf1cf
fix: fix --evm-bin behaviour with relative paths and tilde
danceratopz Jul 5, 2023
2ccb377
chore: raise an error only once if evm bin/tracing options are invalid
danceratopz Jul 5, 2023
953e2b5
fix: error message
marioevz Jul 5, 2023
3952bb6
fix: change error name
marioevz Jul 5, 2023
622f181
improvement: harden t8n tool detection
marioevz Jul 5, 2023
a35fe2c
improvement: efficient tool detection
marioevz Jul 5, 2023
78454be
tox: whitelist
marioevz Jul 5, 2023
0aaf20f
fix: shutil.which usage
marioevz Jul 5, 2023
b9d86b5
fix: remove try-catch
marioevz Jul 5, 2023
4e56dff
Separate binary not found and unknown binary errors
marioevz Jul 6, 2023
a64b028
nit: indentation
marioevz Jul 6, 2023
9ff2150
fix: evm_transition_tool tests
marioevz Jul 6, 2023
68737c7
Merge branch 'main' into evmone-t8n
marioevz Jul 6, 2023
2c7eb0d
fix: save the resolved path to binary_path
danceratopz Jul 6, 2023
2198a35
fix: don't try to access the evm binary for collect-only
danceratopz Jul 6, 2023
53fe032
fix: use popen as a context manager
danceratopz Jul 6, 2023
60f4a18
chore: fix docstring in exception class
danceratopz Jul 6, 2023
fdf2ea6
fix: distinguish between not found in path & unknown t8n tool errors
danceratopz Jul 6, 2023
f4fd9b6
fix: error indentation
marioevz Jul 6, 2023
2a13c11
fix: evm_transition_tool tests
marioevz Jul 6, 2023
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
10 changes: 5 additions & 5 deletions src/ethereum_test_tools/tests/test_filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from ethereum_test_forks import Berlin, Fork, Istanbul, London
from evm_transition_tool import EvmTransitionTool
from evm_transition_tool import GethTransitionTool

from ..code import Yul
from ..common import Account, Block, Environment, JSONEncoder, TestAddress, Transaction
Expand Down Expand Up @@ -59,7 +59,7 @@ def test_make_genesis(fork: Fork, hash: bytes):
TestAddress: Account(balance=0x0BA1A9CE0BA1A9CE),
}

t8n = EvmTransitionTool()
t8n = GethTransitionTool()

_, genesis = StateTest(env=env, pre=pre, post={}, txs=[], tag="some_state_test").make_genesis(
t8n,
Expand Down Expand Up @@ -111,7 +111,7 @@ def test_fill_state_test(fork: Fork, expected_json_file: str):

state_test = StateTest(env=env, pre=pre, post=post, txs=[tx], tag="my_chain_id_test")

t8n = EvmTransitionTool()
t8n = GethTransitionTool()

fixture = {
f"000/my_chain_id_test/{fork}": fill_test(
Expand Down Expand Up @@ -398,7 +398,7 @@ def test_fill_london_blockchain_test_valid_txs(fork: Fork):
tag="fill_london_blockchain_test_valid_txs",
)

t8n = EvmTransitionTool()
t8n = GethTransitionTool()

fixture = {
f"000/my_blockchain_test/{fork.name()}": fill_test(
Expand Down Expand Up @@ -733,7 +733,7 @@ def test_fill_london_blockchain_test_invalid_txs(fork):
tag="fill_london_blockchain_test_invalid_txs",
)

t8n = EvmTransitionTool()
t8n = GethTransitionTool()

fixture = {
f"000/my_blockchain_test/{fork.name()}": fill_test(
Expand Down
285 changes: 12 additions & 273 deletions src/evm_transition_tool/__init__.py
Original file line number Diff line number Diff line change
@@ -1,278 +1,17 @@
"""
Python wrapper for the `evm t8n` tool.
Library of Python wrappers for the different implementations of transition tools.
"""

import json
import os
import subprocess
import tempfile
from abc import abstractmethod
from pathlib import Path
from shutil import which
from typing import Any, Dict, List, Optional, Tuple
from .evmone import EvmOneTransitionTool
from .geth import GethTransitionTool
from .transition_tool import TransitionTool, TransitionToolNotFoundInPath, UnknownTransitionTool

from ethereum_test_forks import Fork
TransitionTool.set_default_tool(GethTransitionTool)


class TransitionTool:
"""
Transition tool frontend.
"""

traces: List[List[List[Dict]]] | None = None

@abstractmethod
def evaluate(
self,
alloc: Any,
txs: Any,
env: Any,
fork: Fork,
chain_id: int = 1,
reward: int = 0,
eips: Optional[List[int]] = None,
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
"""
Simulate a state transition with specified parameters
"""
pass

@abstractmethod
def version(self) -> str:
"""
Return name and version of tool used to state transition
"""
pass

@abstractmethod
def is_fork_supported(self, fork: Fork) -> bool:
"""
Returns True if the fork is supported by the tool
"""
pass

def reset_traces(self):
"""
Resets the internal trace storage for a new test to begin
"""
self.traces = None

def append_traces(self, new_traces: List[List[Dict]]):
"""
Appends a list of traces of a state transition to the current list
"""
if self.traces is None:
self.traces = []
self.traces.append(new_traces)

def get_traces(self) -> List[List[List[Dict]]] | None:
"""
Returns the accumulated traces
"""
return self.traces

def calc_state_root(self, alloc: Any, fork: Fork) -> bytes:
"""
Calculate the state root for the given `alloc`.
"""
env: Dict[str, Any] = {
"currentCoinbase": "0x0000000000000000000000000000000000000000",
"currentDifficulty": "0x0",
"currentGasLimit": "0x0",
"currentNumber": "0",
"currentTimestamp": "0",
}

if fork.header_base_fee_required(0, 0):
env["currentBaseFee"] = "7"

if fork.header_prev_randao_required(0, 0):
env["currentRandom"] = "0"

if fork.header_withdrawals_required(0, 0):
env["withdrawals"] = []

_, result = self.evaluate(alloc, [], env, fork)
state_root = result.get("stateRoot")
if state_root is None or not isinstance(state_root, str):
raise Exception("Unable to calculate state root")
return bytes.fromhex(state_root[2:])

def calc_withdrawals_root(self, withdrawals: Any, fork: Fork) -> bytes:
"""
Calculate the state root for the given `alloc`.
"""
if type(withdrawals) is list and len(withdrawals) == 0:
# Optimize returning the empty root immediately
return bytes.fromhex(
"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
)

env: Dict[str, Any] = {
"currentCoinbase": "0x0000000000000000000000000000000000000000",
"currentDifficulty": "0x0",
"currentGasLimit": "0x0",
"currentNumber": "0",
"currentTimestamp": "0",
"withdrawals": withdrawals,
}

if fork.header_base_fee_required(0, 0):
env["currentBaseFee"] = "7"

if fork.header_prev_randao_required(0, 0):
env["currentRandom"] = "0"

if fork.header_excess_data_gas_required(0, 0):
env["currentExcessDataGas"] = "0"

_, result = self.evaluate({}, [], env, fork)
withdrawals_root = result.get("withdrawalsRoot")
if withdrawals_root is None:
raise Exception(
"Unable to calculate withdrawals root: no value returned from transition tool"
)
if type(withdrawals_root) is not str:
raise Exception(
"Unable to calculate withdrawals root: "
+ "incorrect type returned from transition tool: "
+ f"{withdrawals_root}"
)
return bytes.fromhex(withdrawals_root[2:])


class EvmTransitionTool(TransitionTool):
"""
Go-ethereum `evm` Transition tool frontend.
"""

binary: Path
cached_version: Optional[str] = None
trace: bool

def __init__(
self,
binary: Optional[Path | str] = None,
trace: bool = False,
):
if binary is None:
which_path = which("evm")
if which_path is not None:
binary = Path(which_path)
if binary is None or not Path(binary).exists():
raise Exception(
"""`evm` binary executable is not accessible, please refer to
https://github.com/ethereum/go-ethereum on how to compile and
install the full suite of utilities including the `evm` tool"""
)
self.binary = Path(binary)
self.trace = trace
args = [str(self.binary), "t8n", "--help"]
try:
result = subprocess.run(args, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise Exception("evm process unexpectedly returned a non-zero status code: " f"{e}.")
except Exception as e:
raise Exception(f"Unexpected exception calling evm tool: {e}.")
self.help_string = result.stdout

def evaluate(
self,
alloc: Any,
txs: Any,
env: Any,
fork: Fork,
chain_id: int = 1,
reward: int = 0,
eips: Optional[List[int]] = None,
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
"""
Executes `evm t8n` with the specified arguments.
"""
fork_name = fork.name()
if eips is not None:
fork_name = "+".join([fork_name] + [str(eip) for eip in eips])

temp_dir = tempfile.TemporaryDirectory()

if int(env["currentNumber"], 0) == 0:
reward = -1
args = [
str(self.binary),
"t8n",
"--input.alloc=stdin",
"--input.txs=stdin",
"--input.env=stdin",
"--output.result=stdout",
"--output.alloc=stdout",
"--output.body=txs.rlp",
f"--output.basedir={temp_dir.name}",
f"--state.fork={fork_name}",
f"--state.chainid={chain_id}",
f"--state.reward={reward}",
]

if self.trace:
args.append("--trace")

stdin = {
"alloc": alloc,
"txs": txs,
"env": env,
}

encoded_input = str.encode(json.dumps(stdin))
result = subprocess.run(
args,
input=encoded_input,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

if result.returncode != 0:
raise Exception("failed to evaluate: " + result.stderr.decode())

output = json.loads(result.stdout)

if "alloc" not in output or "result" not in output:
raise Exception("malformed result")

if self.trace:
receipts: List[Any] = output["result"]["receipts"]
traces: List[List[Dict]] = []
for i, r in enumerate(receipts):
h = r["transactionHash"]
trace_file_name = f"trace-{i}-{h}.jsonl"
with open(os.path.join(temp_dir.name, trace_file_name), "r") as trace_file:
tx_traces: List[Dict] = []
for trace_line in trace_file.readlines():
tx_traces.append(json.loads(trace_line))
traces.append(tx_traces)
self.append_traces(traces)

temp_dir.cleanup()

return output["alloc"], output["result"]

def version(self) -> str:
"""
Gets `evm` binary version.
"""
if self.cached_version is None:
result = subprocess.run(
[str(self.binary), "-v"],
stdout=subprocess.PIPE,
)

if result.returncode != 0:
raise Exception("failed to evaluate: " + result.stderr.decode())

self.cached_version = result.stdout.decode().strip()

return self.cached_version

def is_fork_supported(self, fork: Fork) -> bool:
"""
Returns True if the fork is supported by the tool
"""
return fork().name() in self.help_string
__all__ = (
"EvmOneTransitionTool",
"GethTransitionTool",
"TransitionTool",
"TransitionToolNotFoundInPath",
"UnknownTransitionTool",
)
Loading