From 33ef8277a145b24fa8e4c9cec4b46c741c81c157 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Thu, 12 Sep 2024 11:33:29 +0200 Subject: [PATCH 1/9] feat(exception mapper): class to verify exception strings --- src/ethereum_clis/clis/evmone.py | 3 +- src/ethereum_clis/transition_tool.py | 3 ++ src/ethereum_test_exceptions/__init__.py | 2 ++ .../evmone_exceptions.py | 17 +++++++---- .../exception_mapper.py | 28 +++++++++++++++++++ src/ethereum_test_exceptions/exceptions.py | 4 +++ src/ethereum_test_specs/base.py | 12 +++++++- src/ethereum_test_specs/blockchain.py | 4 ++- .../eip7702_set_code_tx/test_set_code_txs.py | 24 ++++++++++++++++ 9 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/ethereum_test_exceptions/exception_mapper.py diff --git a/src/ethereum_clis/clis/evmone.py b/src/ethereum_clis/clis/evmone.py index c0c834c0ec..2921b7612c 100644 --- a/src/ethereum_clis/clis/evmone.py +++ b/src/ethereum_clis/clis/evmone.py @@ -6,6 +6,7 @@ from re import compile from typing import Optional +from ethereum_test_exceptions import EvmoneExceptionMapper from ethereum_test_forks import Fork from ..transition_tool import TransitionTool @@ -30,7 +31,7 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__(exception_mapper=EvmoneExceptionMapper(), binary=binary, trace=trace) def is_fork_supported(self, fork: Fork) -> bool: """ diff --git a/src/ethereum_clis/transition_tool.py b/src/ethereum_clis/transition_tool.py index 58ec2a429a..fbe8c186e5 100644 --- a/src/ethereum_clis/transition_tool.py +++ b/src/ethereum_clis/transition_tool.py @@ -15,6 +15,7 @@ from requests_unixsocket import Session # type: ignore +from ethereum_test_exceptions import ExceptionMapper from ethereum_test_fixtures import FixtureFormat, FixtureVerifier from ethereum_test_forks import Fork from ethereum_test_types import Alloc, Environment, Transaction @@ -51,12 +52,14 @@ class TransitionTool(EthereumCLI, FixtureVerifier): def __init__( self, *, + exception_mapper: ExceptionMapper, binary: Optional[Path] = None, trace: bool = False, ): """ Abstract initialization method that all subclasses must implement. """ + self.exception_mapper = exception_mapper super().__init__(binary=binary) self.trace = trace diff --git a/src/ethereum_test_exceptions/__init__.py b/src/ethereum_test_exceptions/__init__.py index 33cdb29f51..3c7c542e81 100644 --- a/src/ethereum_test_exceptions/__init__.py +++ b/src/ethereum_test_exceptions/__init__.py @@ -4,6 +4,7 @@ from .engine_api import EngineAPIError from .evmone_exceptions import EvmoneExceptionMapper +from .exception_mapper import ExceptionMapper from .exceptions import ( BlockException, BlockExceptionInstanceOrList, @@ -20,6 +21,7 @@ "EOFException", "EOFExceptionInstanceOrList", "EngineAPIError", + "ExceptionMapper", "EvmoneExceptionMapper", "ExceptionInstanceOrList", "TransactionException", diff --git a/src/ethereum_test_exceptions/evmone_exceptions.py b/src/ethereum_test_exceptions/evmone_exceptions.py index a23e27bf85..4403860f12 100644 --- a/src/ethereum_test_exceptions/evmone_exceptions.py +++ b/src/ethereum_test_exceptions/evmone_exceptions.py @@ -6,23 +6,28 @@ from bidict import frozenbidict -from .exceptions import EOFException +from .exception_mapper import ExceptionMapper +from .exceptions import EOFException, ExceptionBase, TransactionException @dataclass class ExceptionMessage: """Defines a mapping between an exception and a message.""" - exception: EOFException + exception: ExceptionBase message: str -class EvmoneExceptionMapper: +class EvmoneExceptionMapper(ExceptionMapper): """ Translate between EEST exceptions and error strings returned by evmone. """ _mapping_data = ( + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), # TODO EVMONE needs to differentiate when the section is missing in the header or body ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), @@ -101,15 +106,15 @@ def __init__(self) -> None: {entry.exception: entry.message for entry in self._mapping_data} ) - def exception_to_message(self, exception: EOFException) -> str: - """Takes an EOFException and returns a formatted string.""" + def exception_to_message(self, exception: ExceptionBase) -> str: + """Takes an exception and returns a formatted string.""" message = self.exception_to_message_map.get( exception, f"No message defined for {exception}; please add it to {self.__class__.__name__}", ) return message - def message_to_exception(self, exception_string: str) -> EOFException: + def message_to_exception(self, exception_string: str) -> ExceptionBase: """Takes a string and tries to find matching exception""" # TODO inform tester where to add the missing exception if get uses default exception = self.exception_to_message_map.inverse.get( diff --git a/src/ethereum_test_exceptions/exception_mapper.py b/src/ethereum_test_exceptions/exception_mapper.py new file mode 100644 index 0000000000..d5a3122155 --- /dev/null +++ b/src/ethereum_test_exceptions/exception_mapper.py @@ -0,0 +1,28 @@ +""" +EEST Exception mapper +""" +from abc import ABC, abstractmethod + +from .exceptions import ExceptionBase + + +class ExceptionMapper(ABC): + """ + Translate between EEST exceptions and error strings returned by client's t8n or other tools. + """ + + @abstractmethod + def exception_to_message(self, exception: ExceptionBase) -> str: + """ + Translate an ExceptionBase instance to a string message. + Must be implemented by subclasses. + """ + pass + + @abstractmethod + def message_to_exception(self, exception_string: str) -> ExceptionBase: + """ + Translate a string message to an ExceptionBase instance. + Must be implemented by subclasses. + """ + pass diff --git a/src/ethereum_test_exceptions/exceptions.py b/src/ethereum_test_exceptions/exceptions.py index f2c19f2937..afaae909f8 100644 --- a/src/ethereum_test_exceptions/exceptions.py +++ b/src/ethereum_test_exceptions/exceptions.py @@ -362,6 +362,10 @@ class TransactionException(ExceptionBase): """ Transaction is type 4, but has an empty authorization list. """ + TYPE_4_TX_CONTRACT_CREATION = auto() + """ + Transaction is a type 4 transaction and has an empty `to`. + """ @unique diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index 8c020e1e27..a334891264 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -14,6 +14,7 @@ from ethereum_clis import Result, TransitionTool from ethereum_test_base_types import to_hex +from ethereum_test_exceptions import ExceptionMapper, TransactionException from ethereum_test_fixtures import BaseFixture, FixtureFormat from ethereum_test_forks import Fork from ethereum_test_types import Environment, Transaction, Withdrawal @@ -32,7 +33,9 @@ def __str__(self): # noqa: D105 return f"{self.message}: Expected {self.expected_hash}, got {self.actual_hash}" -def verify_transactions(txs: List[Transaction], result: Result) -> List[int]: +def verify_transactions( + exception_mapper: ExceptionMapper, txs: List[Transaction], result: Result +) -> List[int]: """ Verify rejected transactions (if any) against the expected outcome. Raises exception on unexpected rejections or unexpected successful txs. @@ -47,6 +50,13 @@ def verify_transactions(txs: List[Transaction], result: Result) -> List[int]: raise Exception(f"tx expected to fail succeeded: pos={i}, nonce={tx.nonce}") elif not tx.error and error: raise Exception(f"tx unexpectedly failed: {error}") + elif isinstance(tx.error, TransactionException) and error: + translated = exception_mapper.exception_to_message(tx.error) + error_code = exception_mapper.message_to_exception(error) + if translated != error: + raise Exception( + f"tx exception: want={translated} ({tx.error}), got={error} ({error_code})" + ) # TODO: Also we need a way to check we actually got the # correct error diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index 3d732bda43..54378ea59a 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -440,7 +440,9 @@ def generate_block_data( ) try: - rejected_txs = verify_transactions(txs, transition_tool_output.result) + rejected_txs = verify_transactions( + t8n.exception_mapper, txs, transition_tool_output.result + ) verify_result(transition_tool_output.result, env) except Exception as e: print_traces(t8n.get_traces()) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 1f84924ebd..39a221b1c2 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2786,3 +2786,27 @@ def test_reset_code( ), }, ) + + +def test_contract_create( + state_test: StateTestFiller, + pre: Alloc, +): + """ + Test sending type-4 tx as a create transaction + """ + tx = Transaction( + gas_limit=100_000, + to=None, + value=0, + authorization_list=[], + error=TransactionException.TYPE_4_TX_CONTRACT_CREATION, + sender=pre.fund_eoa(), + ) + + state_test( + env=Environment(), + pre=pre, + tx=tx, + post={}, + ) From 7d0c914533cbbfd6fca331879440eab5bc8cd351 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Wed, 18 Sep 2024 15:36:33 +0200 Subject: [PATCH 2/9] add GethExceptionMapper --- src/ethereum_clis/clis/geth.py | 3 +- src/ethereum_test_exceptions/__init__.py | 2 + .../evmone_exceptions.py | 202 ++++++++---------- .../exception_mapper.py | 86 ++++++-- src/ethereum_test_exceptions/exceptions.py | 12 ++ .../geth_exceptions.py | 140 ++++++++++++ src/ethereum_test_specs/base.py | 18 +- 7 files changed, 322 insertions(+), 141 deletions(-) create mode 100644 src/ethereum_test_exceptions/geth_exceptions.py diff --git a/src/ethereum_clis/clis/geth.py b/src/ethereum_clis/clis/geth.py index 905a782f90..337a3f64c0 100644 --- a/src/ethereum_clis/clis/geth.py +++ b/src/ethereum_clis/clis/geth.py @@ -10,6 +10,7 @@ from re import compile from typing import Optional +from ethereum_test_exceptions import GethExceptionMapper from ethereum_test_fixtures import BlockchainFixture, StateFixture from ethereum_test_forks import Fork @@ -37,7 +38,7 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__(exception_mapper=GethExceptionMapper(), binary=binary, trace=trace) args = [str(self.binary), str(self.t8n_subcommand), "--help"] try: result = subprocess.run(args, capture_output=True, text=True) diff --git a/src/ethereum_test_exceptions/__init__.py b/src/ethereum_test_exceptions/__init__.py index 3c7c542e81..bc96cfc553 100644 --- a/src/ethereum_test_exceptions/__init__.py +++ b/src/ethereum_test_exceptions/__init__.py @@ -14,6 +14,7 @@ TransactionException, TransactionExceptionInstanceOrList, ) +from .geth_exceptions import GethExceptionMapper __all__ = [ "BlockException", @@ -23,6 +24,7 @@ "EngineAPIError", "ExceptionMapper", "EvmoneExceptionMapper", + "GethExceptionMapper", "ExceptionInstanceOrList", "TransactionException", "TransactionExceptionInstanceOrList", diff --git a/src/ethereum_test_exceptions/evmone_exceptions.py b/src/ethereum_test_exceptions/evmone_exceptions.py index 4403860f12..5fc7f27b86 100644 --- a/src/ethereum_test_exceptions/evmone_exceptions.py +++ b/src/ethereum_test_exceptions/evmone_exceptions.py @@ -1,21 +1,9 @@ """ -Evmone eof exceptions ENUM -> str mapper +Evmone exceptions ENUM -> str mapper """ -from dataclasses import dataclass - -from bidict import frozenbidict - -from .exception_mapper import ExceptionMapper -from .exceptions import EOFException, ExceptionBase, TransactionException - - -@dataclass -class ExceptionMessage: - """Defines a mapping between an exception and a message.""" - - exception: ExceptionBase - message: str +from .exception_mapper import ExceptionMapper, ExceptionMessage +from .exceptions import EOFException, TransactionException class EvmoneExceptionMapper(ExceptionMapper): @@ -23,101 +11,89 @@ class EvmoneExceptionMapper(ExceptionMapper): Translate between EEST exceptions and error strings returned by evmone. """ - _mapping_data = ( - ExceptionMessage( - TransactionException.TYPE_4_TX_CONTRACT_CREATION, - "set code transaction must not be a create transaction", - ), - # TODO EVMONE needs to differentiate when the section is missing in the header or body - ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), - ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), - ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), - # TODO EVMONE these exceptions are too similar, this leeds to ambiguity - ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), - ExceptionMessage( - EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" - ), - ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), - ExceptionMessage( - EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" - ), - ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), - ExceptionMessage( - EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" - ), - ExceptionMessage( - EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" - ), - ExceptionMessage(EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size"), - ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), - ExceptionMessage(EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number"), - ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), - ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), - ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), - ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), - ExceptionMessage( - EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" - ), - ExceptionMessage(EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions"), - ExceptionMessage(EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination"), - ExceptionMessage(EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections"), - ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), - ExceptionMessage( - EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" - ), - ExceptionMessage( - EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" - ), - ExceptionMessage( - EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, - "err: jumpf_destination_incompatible_outputs", - ), - ExceptionMessage(EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"), - ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), - ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), - ExceptionMessage( - EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" - ), - ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), - ExceptionMessage( - EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" - ), - ExceptionMessage( - EOFException.INVALID_CONTAINER_SECTION_INDEX, "err: invalid_container_section_index" - ), - ExceptionMessage( - EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" - ), - ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), - ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), - ExceptionMessage( - EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" - ), - ) - - def __init__(self) -> None: - assert len(set(entry.exception for entry in self._mapping_data)) == len( - self._mapping_data - ), "Duplicate exception in _mapping_data" - assert len(set(entry.message for entry in self._mapping_data)) == len( - self._mapping_data - ), "Duplicate message in _mapping_data" - self.exception_to_message_map: frozenbidict = frozenbidict( - {entry.exception: entry.message for entry in self._mapping_data} - ) - - def exception_to_message(self, exception: ExceptionBase) -> str: - """Takes an exception and returns a formatted string.""" - message = self.exception_to_message_map.get( - exception, - f"No message defined for {exception}; please add it to {self.__class__.__name__}", - ) - return message - - def message_to_exception(self, exception_string: str) -> ExceptionBase: - """Takes a string and tries to find matching exception""" - # TODO inform tester where to add the missing exception if get uses default - exception = self.exception_to_message_map.inverse.get( - exception_string, EOFException.UNDEFINED_EXCEPTION - ) - return exception + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must ", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_test_exceptions/exception_mapper.py b/src/ethereum_test_exceptions/exception_mapper.py index d5a3122155..9be9ef82ce 100644 --- a/src/ethereum_test_exceptions/exception_mapper.py +++ b/src/ethereum_test_exceptions/exception_mapper.py @@ -2,8 +2,19 @@ EEST Exception mapper """ from abc import ABC, abstractmethod +from dataclasses import dataclass -from .exceptions import ExceptionBase +from bidict import frozenbidict + +from .exceptions import ExceptionBase, TransactionException, UndefinedException + + +@dataclass +class ExceptionMessage: + """Defines a mapping between an exception and a message.""" + + exception: ExceptionBase + message: str class ExceptionMapper(ABC): @@ -11,18 +22,69 @@ class ExceptionMapper(ABC): Translate between EEST exceptions and error strings returned by client's t8n or other tools. """ + def __init__(self) -> None: + # Ensure that the subclass has properly defined _mapping_data before accessing it + assert self._mapping_data is not None, "_mapping_data must be defined in subclass" + + assert len(set(entry.exception for entry in self._mapping_data)) == len( + self._mapping_data + ), "Duplicate exception in _mapping_data" + assert len(set(entry.message for entry in self._mapping_data)) == len( + self._mapping_data + ), "Duplicate message in _mapping_data" + self.exception_to_message_map: frozenbidict = frozenbidict( + {entry.exception: entry.message for entry in self._mapping_data} + ) + + @property @abstractmethod - def exception_to_message(self, exception: ExceptionBase) -> str: - """ - Translate an ExceptionBase instance to a string message. - Must be implemented by subclasses. - """ + def _mapping_data(self): + """This method should be overridden in the subclass to provide mapping data.""" pass - @abstractmethod + def exception_to_message(self, exception: ExceptionBase) -> str: + """Takes an exception and returns a formatted string.""" + message = self.exception_to_message_map.get(exception, "Undefined") + return message + def message_to_exception(self, exception_string: str) -> ExceptionBase: - """ - Translate a string message to an ExceptionBase instance. - Must be implemented by subclasses. - """ - pass + """Takes a string and tries to find matching exception""" + # TODO inform tester where to add the missing exception if get uses default + exception = self.exception_to_message_map.inverse.get( + exception_string, UndefinedException.UNDEFINED_EXCEPTION + ) + return exception + + def check_transaction(self, tx_error_message: str | None, tx, tx_pos: int): + """Verify transaction exception""" + exception_info = f"TransactionException (pos={tx_pos}, nonce={tx.nonce})\n" + + if tx.error and not tx_error_message: + raise Exception(f"{exception_info} Error: tx expected to fail succeeded") + elif not tx.error and tx_error_message: + raise Exception(f"{exception_info} Error: tx unexpectedly failed: {tx_error_message}") + # TODO check exception list case + elif isinstance(tx.error, TransactionException) and tx_error_message: + expected_error_message = self.exception_to_message(tx.error) + error_exception = self.message_to_exception(tx_error_message) + + define_message_hint = ( + f"No message defined for {tx.error}, please add it to {self.__class__.__name__}" + if expected_error_message == "Undefined" + else "" + ) + define_exception_hint = ( + f"No exception defined for error message got, " + f"please add it to {self.__class__.__name__}" + if error_exception == UndefinedException.UNDEFINED_EXCEPTION + else "" + ) + + if expected_error_message not in tx_error_message: + raise Exception( + f"{exception_info}" + f"Error: exception mismatch:\n want = '{expected_error_message}' ({tx.error})," + f"\n got = '{tx_error_message}' ({error_exception})" + f"\n {define_message_hint}" + f"\n {define_exception_hint}" + ) diff --git a/src/ethereum_test_exceptions/exceptions.py b/src/ethereum_test_exceptions/exceptions.py index afaae909f8..4a86654476 100644 --- a/src/ethereum_test_exceptions/exceptions.py +++ b/src/ethereum_test_exceptions/exceptions.py @@ -102,6 +102,18 @@ def from_pipe_str(value: Any) -> str | List[str]: return value +@unique +class UndefinedException(ExceptionBase): + """ + Default Exception + """ + + UNDEFINED_EXCEPTION = auto() + """ + Exception to alert to define a proper exception + """ + + @unique class TransactionException(ExceptionBase): """ diff --git a/src/ethereum_test_exceptions/geth_exceptions.py b/src/ethereum_test_exceptions/geth_exceptions.py new file mode 100644 index 0000000000..5f79176465 --- /dev/null +++ b/src/ethereum_test_exceptions/geth_exceptions.py @@ -0,0 +1,140 @@ +""" +Geth exceptions ENUM -> str mapper +""" + +from .exception_mapper import ExceptionMapper, ExceptionMessage +from .exceptions import EOFException, TransactionException + + +class GethExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by geth. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "insufficient funds for gas * price + value", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "would exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "max fee per blob gas less than block blob gas fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "max fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "blob tx used but field env.ExcessBlobGas missing", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "has invalid hash version", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "blob transaction missing blob hashes", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "intrinsic gas too low", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index a334891264..4e5bcb4a95 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -45,21 +45,9 @@ def verify_transactions( } for i, tx in enumerate(txs): - error = rejected_txs[i] if i in rejected_txs else None - if tx.error and not error: - raise Exception(f"tx expected to fail succeeded: pos={i}, nonce={tx.nonce}") - elif not tx.error and error: - raise Exception(f"tx unexpectedly failed: {error}") - elif isinstance(tx.error, TransactionException) and error: - translated = exception_mapper.exception_to_message(tx.error) - error_code = exception_mapper.message_to_exception(error) - if translated != error: - raise Exception( - f"tx exception: want={translated} ({tx.error}), got={error} ({error_code})" - ) - - # TODO: Also we need a way to check we actually got the - # correct error + error_message = rejected_txs[i] if i in rejected_txs else None + exception_mapper.check_transaction(error_message, tx, i) + return list(rejected_txs.keys()) From 8252f00d87deea75f179051f5391160f81594a1d Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Mon, 7 Oct 2024 14:34:14 +0200 Subject: [PATCH 3/9] refactor exception mapper parse expected exception lists --- src/ethereum_clis/__init__.py | 3 +- src/ethereum_clis/clis/evmone.py | 152 +++++++++++++++++- src/ethereum_clis/clis/execution_specs.py | 144 ++++++++++++++++- src/ethereum_clis/clis/geth.py | 141 +++++++++++++++- src/ethereum_test_exceptions/__init__.py | 9 +- .../evmone_exceptions.py | 99 ------------ .../exception_mapper.py | 37 +---- src/ethereum_test_exceptions/exceptions.py | 6 +- .../geth_exceptions.py | 140 ---------------- src/ethereum_test_specs/base.py | 18 --- src/ethereum_test_specs/blockchain.py | 3 +- src/ethereum_test_specs/helpers.py | 94 +++++++++++ .../eip7702_set_code_tx/test_set_code_txs.py | 9 +- 13 files changed, 548 insertions(+), 307 deletions(-) delete mode 100644 src/ethereum_test_exceptions/evmone_exceptions.py delete mode 100644 src/ethereum_test_exceptions/geth_exceptions.py create mode 100644 src/ethereum_test_specs/helpers.py diff --git a/src/ethereum_clis/__init__.py b/src/ethereum_clis/__init__.py index fdf9f80ae9..1e2acd4368 100644 --- a/src/ethereum_clis/__init__.py +++ b/src/ethereum_clis/__init__.py @@ -4,7 +4,7 @@ from .clis.besu import BesuTransitionTool from .clis.ethereumjs import EthereumJSTransitionTool -from .clis.evmone import EvmOneTransitionTool +from .clis.evmone import EvmoneExceptionMapper, EvmOneTransitionTool from .clis.execution_specs import ExecutionSpecsTransitionTool from .clis.geth import GethTransitionTool from .clis.nimbus import NimbusTransitionTool @@ -20,6 +20,7 @@ "EvmOneTransitionTool", "ExecutionSpecsTransitionTool", "GethTransitionTool", + "EvmoneExceptionMapper", "NimbusTransitionTool", "Result", "TransitionTool", diff --git a/src/ethereum_clis/clis/evmone.py b/src/ethereum_clis/clis/evmone.py index 2921b7612c..542eac83de 100644 --- a/src/ethereum_clis/clis/evmone.py +++ b/src/ethereum_clis/clis/evmone.py @@ -6,7 +6,12 @@ from re import compile from typing import Optional -from ethereum_test_exceptions import EvmoneExceptionMapper +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_forks import Fork from ..transition_tool import TransitionTool @@ -39,3 +44,148 @@ def is_fork_supported(self, fork: Fork) -> bool: Currently, evmone-t8n provides no way to determine supported forks. """ return True + + +class EvmoneExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by evmone. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must ", + ), + ExceptionMessage( + TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE, + "invalid authorization signature", + ), + ExceptionMessage( + TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE_S_TOO_HIGH, + "authorization signature s value too high", + ), + ExceptionMessage( + TransactionException.TYPE_4_EMPTY_AUTHORIZATION_LIST, + "empty authorization list", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "intrinsic gas too low", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "lob gas limit exceeded", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "insufficient funds for gas * price + value", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "max fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "transaction type not supported", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "invalid blob hash version", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "blob gas limit exceeded", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "empty blob hashes list", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_clis/clis/execution_specs.py b/src/ethereum_clis/clis/execution_specs.py index 1cebd53dfa..816592a6c5 100644 --- a/src/ethereum_clis/clis/execution_specs.py +++ b/src/ethereum_clis/clis/execution_specs.py @@ -11,6 +11,12 @@ from tempfile import TemporaryDirectory from typing import Optional +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_forks import Fork from ..transition_tool import TransitionTool @@ -46,7 +52,9 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__( + exception_mapper=ExecutionSpecsExceptionMapper(), binary=binary, trace=trace + ) args = [str(self.binary), "--help"] try: result = subprocess.run(args, capture_output=True, text=True) @@ -102,3 +110,137 @@ def is_fork_supported(self, fork: Fork) -> bool: `ethereum-spec-evm` appends newlines to forks in the help string. """ return (fork.transition_tool_name() + "\n") in self.help_string + + +class ExecutionSpecsExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by execution specs t8n. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "Failed transaction: ", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "ailed transaction: ", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "iled transaction: ", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "led transaction: ", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "ed transaction: ", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "Failed to parse transaction 0: module 'ethereum.shanghai.transactions' has no attribute 'BlobTransaction'", # noqa: E501 + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "d transaction: ", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + " transaction: ", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "transaction: ", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "ransaction: ", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "ansaction: ", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_clis/clis/geth.py b/src/ethereum_clis/clis/geth.py index 337a3f64c0..993690affa 100644 --- a/src/ethereum_clis/clis/geth.py +++ b/src/ethereum_clis/clis/geth.py @@ -10,7 +10,12 @@ from re import compile from typing import Optional -from ethereum_test_exceptions import GethExceptionMapper +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_fixtures import BlockchainFixture, StateFixture from ethereum_test_forks import Fork @@ -148,3 +153,137 @@ def verify_fixture( else: result_json = [] # there is no parseable format for blocktest output return result_json + + +class GethExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by geth. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "insufficient funds for gas * price + value", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "would exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "max fee per blob gas less than block blob gas fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "max fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "blob tx used but field env.ExcessBlobGas missing", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "has invalid hash version", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "blob transaction missing blob hashes", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "intrinsic gas too low", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_test_exceptions/__init__.py b/src/ethereum_test_exceptions/__init__.py index bc96cfc553..d547d50a37 100644 --- a/src/ethereum_test_exceptions/__init__.py +++ b/src/ethereum_test_exceptions/__init__.py @@ -3,8 +3,7 @@ """ from .engine_api import EngineAPIError -from .evmone_exceptions import EvmoneExceptionMapper -from .exception_mapper import ExceptionMapper +from .exception_mapper import ExceptionMapper, ExceptionMessage from .exceptions import ( BlockException, BlockExceptionInstanceOrList, @@ -13,8 +12,8 @@ ExceptionInstanceOrList, TransactionException, TransactionExceptionInstanceOrList, + UndefinedException, ) -from .geth_exceptions import GethExceptionMapper __all__ = [ "BlockException", @@ -23,9 +22,9 @@ "EOFExceptionInstanceOrList", "EngineAPIError", "ExceptionMapper", - "EvmoneExceptionMapper", - "GethExceptionMapper", + "ExceptionMessage", "ExceptionInstanceOrList", "TransactionException", + "UndefinedException", "TransactionExceptionInstanceOrList", ] diff --git a/src/ethereum_test_exceptions/evmone_exceptions.py b/src/ethereum_test_exceptions/evmone_exceptions.py deleted file mode 100644 index 5fc7f27b86..0000000000 --- a/src/ethereum_test_exceptions/evmone_exceptions.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Evmone exceptions ENUM -> str mapper -""" - -from .exception_mapper import ExceptionMapper, ExceptionMessage -from .exceptions import EOFException, TransactionException - - -class EvmoneExceptionMapper(ExceptionMapper): - """ - Translate between EEST exceptions and error strings returned by evmone. - """ - - @property - def _mapping_data(self): - return [ - ExceptionMessage( - TransactionException.TYPE_4_TX_CONTRACT_CREATION, - "set code transaction must ", - ), - # TODO EVMONE needs to differentiate when the section is missing in the header or body - ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), - ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), - ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), - # TODO EVMONE these exceptions are too similar, this leeds to ambiguity - ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), - ExceptionMessage( - EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" - ), - ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), - ExceptionMessage( - EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" - ), - ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), - ExceptionMessage( - EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" - ), - ExceptionMessage( - EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" - ), - ExceptionMessage( - EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" - ), - ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), - ExceptionMessage( - EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" - ), - ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), - ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), - ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), - ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), - ExceptionMessage( - EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" - ), - ExceptionMessage( - EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" - ), - ExceptionMessage( - EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" - ), - ExceptionMessage( - EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" - ), - ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), - ExceptionMessage( - EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" - ), - ExceptionMessage( - EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" - ), - ExceptionMessage( - EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, - "err: jumpf_destination_incompatible_outputs", - ), - ExceptionMessage( - EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" - ), - ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), - ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), - ExceptionMessage( - EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" - ), - ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), - ExceptionMessage( - EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" - ), - ExceptionMessage( - EOFException.INVALID_CONTAINER_SECTION_INDEX, - "err: invalid_container_section_index", - ), - ExceptionMessage( - EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" - ), - ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), - ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), - ExceptionMessage( - EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" - ), - ] diff --git a/src/ethereum_test_exceptions/exception_mapper.py b/src/ethereum_test_exceptions/exception_mapper.py index 9be9ef82ce..a7dfc14d6c 100644 --- a/src/ethereum_test_exceptions/exception_mapper.py +++ b/src/ethereum_test_exceptions/exception_mapper.py @@ -1,12 +1,13 @@ """ EEST Exception mapper """ + from abc import ABC, abstractmethod from dataclasses import dataclass from bidict import frozenbidict -from .exceptions import ExceptionBase, TransactionException, UndefinedException +from .exceptions import ExceptionBase, UndefinedException @dataclass @@ -54,37 +55,3 @@ def message_to_exception(self, exception_string: str) -> ExceptionBase: exception_string, UndefinedException.UNDEFINED_EXCEPTION ) return exception - - def check_transaction(self, tx_error_message: str | None, tx, tx_pos: int): - """Verify transaction exception""" - exception_info = f"TransactionException (pos={tx_pos}, nonce={tx.nonce})\n" - - if tx.error and not tx_error_message: - raise Exception(f"{exception_info} Error: tx expected to fail succeeded") - elif not tx.error and tx_error_message: - raise Exception(f"{exception_info} Error: tx unexpectedly failed: {tx_error_message}") - # TODO check exception list case - elif isinstance(tx.error, TransactionException) and tx_error_message: - expected_error_message = self.exception_to_message(tx.error) - error_exception = self.message_to_exception(tx_error_message) - - define_message_hint = ( - f"No message defined for {tx.error}, please add it to {self.__class__.__name__}" - if expected_error_message == "Undefined" - else "" - ) - define_exception_hint = ( - f"No exception defined for error message got, " - f"please add it to {self.__class__.__name__}" - if error_exception == UndefinedException.UNDEFINED_EXCEPTION - else "" - ) - - if expected_error_message not in tx_error_message: - raise Exception( - f"{exception_info}" - f"Error: exception mismatch:\n want = '{expected_error_message}' ({tx.error})," - f"\n got = '{tx_error_message}' ({error_exception})" - f"\n {define_message_hint}" - f"\n {define_exception_hint}" - ) diff --git a/src/ethereum_test_exceptions/exceptions.py b/src/ethereum_test_exceptions/exceptions.py index 4a86654476..94e02c3b8e 100644 --- a/src/ethereum_test_exceptions/exceptions.py +++ b/src/ethereum_test_exceptions/exceptions.py @@ -372,7 +372,11 @@ class TransactionException(ExceptionBase): """ TYPE_4_INVALID_AUTHORITY_SIGNATURE = auto() """ - Transaction is type 4, but has an empty authorization list. + Transaction authority signature is invalid + """ + TYPE_4_INVALID_AUTHORITY_SIGNATURE_S_TOO_HIGH = auto() + """ + Transaction authority signature is invalid """ TYPE_4_TX_CONTRACT_CREATION = auto() """ diff --git a/src/ethereum_test_exceptions/geth_exceptions.py b/src/ethereum_test_exceptions/geth_exceptions.py deleted file mode 100644 index 5f79176465..0000000000 --- a/src/ethereum_test_exceptions/geth_exceptions.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Geth exceptions ENUM -> str mapper -""" - -from .exception_mapper import ExceptionMapper, ExceptionMessage -from .exceptions import EOFException, TransactionException - - -class GethExceptionMapper(ExceptionMapper): - """ - Translate between EEST exceptions and error strings returned by geth. - """ - - @property - def _mapping_data(self): - return [ - ExceptionMessage( - TransactionException.TYPE_4_TX_CONTRACT_CREATION, - "set code transaction must not be a create transaction", - ), - ExceptionMessage( - TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, - "insufficient funds for gas * price + value", - ), - ExceptionMessage( - TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, - "would exceed maximum allowance", - ), - ExceptionMessage( - TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, - "max fee per blob gas less than block blob gas fee", - ), - ExceptionMessage( - TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, - "max fee per gas less than block base fee", - ), - ExceptionMessage( - TransactionException.TYPE_3_TX_PRE_FORK, - "blob tx used but field env.ExcessBlobGas missing", - ), - ExceptionMessage( - TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, - "has invalid hash version", - ), - # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED - ExceptionMessage( - TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, - "exceed maximum allowance", - ), - ExceptionMessage( - TransactionException.TYPE_3_TX_ZERO_BLOBS, - "blob transaction missing blob hashes", - ), - ExceptionMessage( - TransactionException.INTRINSIC_GAS_TOO_LOW, - "intrinsic gas too low", - ), - ExceptionMessage( - TransactionException.INITCODE_SIZE_EXCEEDED, - "max initcode size exceeded", - ), - # TODO EVMONE needs to differentiate when the section is missing in the header or body - ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), - ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), - ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), - # TODO EVMONE these exceptions are too similar, this leeds to ambiguity - ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), - ExceptionMessage( - EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" - ), - ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), - ExceptionMessage( - EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" - ), - ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), - ExceptionMessage( - EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" - ), - ExceptionMessage( - EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" - ), - ExceptionMessage( - EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" - ), - ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), - ExceptionMessage( - EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" - ), - ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), - ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), - ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), - ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), - ExceptionMessage( - EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" - ), - ExceptionMessage( - EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" - ), - ExceptionMessage( - EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" - ), - ExceptionMessage( - EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" - ), - ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), - ExceptionMessage( - EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" - ), - ExceptionMessage( - EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" - ), - ExceptionMessage( - EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, - "err: jumpf_destination_incompatible_outputs", - ), - ExceptionMessage( - EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" - ), - ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), - ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), - ExceptionMessage( - EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" - ), - ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), - ExceptionMessage( - EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" - ), - ExceptionMessage( - EOFException.INVALID_CONTAINER_SECTION_INDEX, - "err: invalid_container_section_index", - ), - ExceptionMessage( - EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" - ), - ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), - ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), - ExceptionMessage( - EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" - ), - ] diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index 4e5bcb4a95..5c3ec2da9f 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -33,24 +33,6 @@ def __str__(self): # noqa: D105 return f"{self.message}: Expected {self.expected_hash}, got {self.actual_hash}" -def verify_transactions( - exception_mapper: ExceptionMapper, txs: List[Transaction], result: Result -) -> List[int]: - """ - Verify rejected transactions (if any) against the expected outcome. - Raises exception on unexpected rejections or unexpected successful txs. - """ - rejected_txs: Dict[int, str] = { - rejected_tx.index: rejected_tx.error for rejected_tx in result.rejected_transactions - } - - for i, tx in enumerate(txs): - error_message = rejected_txs[i] if i in rejected_txs else None - exception_mapper.check_transaction(error_message, tx, i) - - return list(rejected_txs.keys()) - - def verify_result(result: Result, env: Environment): """ Verify that values in the t8n result match the expected values. diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index 54378ea59a..7501ae048b 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -55,8 +55,9 @@ WithdrawalRequest, ) -from .base import BaseTest, verify_result, verify_transactions +from .base import BaseTest, verify_result from .debugging import print_traces +from .helpers import verify_transactions def environment_from_parent_header(parent: "FixtureHeader") -> "Environment": diff --git a/src/ethereum_test_specs/helpers.py b/src/ethereum_test_specs/helpers.py new file mode 100644 index 0000000000..2cc3dccb11 --- /dev/null +++ b/src/ethereum_test_specs/helpers.py @@ -0,0 +1,94 @@ +""" +Helper functions +""" + +from dataclasses import dataclass +from typing import Dict, List + +from ethereum_test_exceptions import ExceptionMapper, TransactionException, UndefinedException +from ethereum_test_types import Transaction +from evm_transition_tool import Result + + +@dataclass +class TransactionExceptionInfo: + """Info to print transaction exception error messages""" + + error_message: str | None + transaction_ind: int + tx: Transaction + + +def verify_transaction_exception( + exception_mapper: ExceptionMapper, info: TransactionExceptionInfo +): + """Verify transaction exception""" + exception_info = f"TransactionException (pos={info.transaction_ind}, nonce={info.tx.nonce})\n" + expected_error: bool = info.tx.error is not None or ( + isinstance(info.tx.error, list) and len(info.tx.error) != 0 + ) + + # info.tx.error is expected error code defined in .py test + if expected_error and not info.error_message: + raise Exception(f"{exception_info} Error: tx expected to fail succeeded") + elif not expected_error and info.error_message: + raise Exception(f"{exception_info} Error: tx unexpectedly failed: {info.error_message}") + elif expected_error and info.error_message: + + if isinstance(info.tx.error, List): + for expected_exception in info.tx.error: + expected_error_msg = exception_mapper.exception_to_message(expected_exception) + if expected_error_msg in info.error_message: + # One of expected exceptions is found in tx error string, no error + return + + if isinstance(info.tx.error, List): + expected_exception = info.tx.error[0] + elif info.tx.error is None: + return # will never happen but removes python logic check + else: + expected_exception = info.tx.error + + expected_error_msg = exception_mapper.exception_to_message(expected_exception) + error_exception = exception_mapper.message_to_exception(info.error_message) + exception_mapper_name = exception_mapper.__class__.__name__ + + define_message_hint = ( + f"No message defined for {expected_exception}, please add it to {exception_mapper_name}" + if expected_error_msg == "Undefined" + else "" + ) + define_exception_hint = ( + f"No exception defined for error message got, " + f"please add it to {exception_mapper.__class__.__name__}" + if error_exception == UndefinedException.UNDEFINED_EXCEPTION + else "" + ) + + if expected_error_msg not in info.error_message: + raise Exception( + f"{exception_info}" + f"Error: exception mismatch:\n want = '{expected_error_msg}' ({expected_exception})," + f"\n got = '{info.error_message}' ({error_exception})" + f"\n {define_message_hint}" + f"\n {define_exception_hint}" + ) + + +def verify_transactions( + exception_mapper: ExceptionMapper, txs: List[Transaction], result: Result +) -> List[int]: + """ + Verify rejected transactions (if any) against the expected outcome. + Raises exception on unexpected rejections or unexpected successful txs. + """ + rejected_txs: Dict[int, str] = { + rejected_tx.index: rejected_tx.error for rejected_tx in result.rejected_transactions + } + + for i, tx in enumerate(txs): + error_message = rejected_txs[i] if i in rejected_txs else None + info = TransactionExceptionInfo(error_message=error_message, transaction_ind=i, tx=tx) + verify_transaction_exception(exception_mapper=exception_mapper, info=info) + + return list(rejected_txs.keys()) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 39a221b1c2..c64c5a926c 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2021,9 +2021,7 @@ def test_set_code_invalid_authorization_tuple( nonce=( 1 if invalidity_reason == InvalidityReason.NONCE - else [0, 1] - if invalidity_reason == InvalidityReason.MULTIPLE_NONCE - else 0 + else [0, 1] if invalidity_reason == InvalidityReason.MULTIPLE_NONCE else 0 ), chain_id=2 if invalidity_reason == InvalidityReason.CHAIN_ID else 0, signer=auth_signer, @@ -2243,7 +2241,10 @@ def test_invalid_tx_invalid_auth_signature( to=callee_address, value=0, authorization_list=[authorization_tuple], - error=TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE, + error=[ + TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE, + TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE_S_TOO_HIGH, + ], sender=pre.fund_eoa(), ) From bcddd8b12bd990068d15a1941c78aa864a0042f8 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Tue, 8 Oct 2024 14:34:34 +0200 Subject: [PATCH 4/9] integrate other t8n's exception maps --- src/ethereum_clis/clis/besu.py | 142 +++++++++++++++++++++- src/ethereum_clis/clis/ethereumjs.py | 142 +++++++++++++++++++++- src/ethereum_clis/clis/execution_specs.py | 2 +- src/ethereum_clis/clis/nimbus.py | 142 +++++++++++++++++++++- 4 files changed, 424 insertions(+), 4 deletions(-) diff --git a/src/ethereum_clis/clis/besu.py b/src/ethereum_clis/clis/besu.py index 12fb502e72..708d5b949f 100644 --- a/src/ethereum_clis/clis/besu.py +++ b/src/ethereum_clis/clis/besu.py @@ -14,6 +14,12 @@ import requests +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_forks import Fork from ethereum_test_types import Alloc, Environment, Transaction @@ -41,7 +47,7 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__(exception_mapper=BesuExceptionMapper(), binary=binary, trace=trace) args = [str(self.binary), "t8n", "--help"] try: result = subprocess.run(args, capture_output=True, text=True) @@ -201,3 +207,137 @@ def is_fork_supported(self, fork: Fork) -> bool: Returns True if the fork is supported by the tool """ return fork.transition_tool_name() in self.help_string + + +class BesuExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by nimbus. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "exceeds transaction sender account balance", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "would exceed block maximum", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "max fee per blob gas less than block blob gas fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "gasPrice is less than the current BaseFee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "Transaction type BLOB is invalid, accepted transaction types are [EIP1559, ACCESS_LIST, FRONTIER]", # noqa: E501 + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "Only supported hash version is 0x01, sha256 hash.", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "exceed block maximum", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "Blob transaction must have at least one versioned hash", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "intrinsic gas too low", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_clis/clis/ethereumjs.py b/src/ethereum_clis/clis/ethereumjs.py index ec3370fee6..cb461e7c16 100644 --- a/src/ethereum_clis/clis/ethereumjs.py +++ b/src/ethereum_clis/clis/ethereumjs.py @@ -6,6 +6,12 @@ from re import compile from typing import Optional +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_forks import Fork from ..transition_tool import TransitionTool @@ -31,7 +37,7 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__(exception_mapper=EthereumJSExceptionMapper(), binary=binary, trace=trace) def is_fork_supported(self, fork: Fork) -> bool: """ @@ -39,3 +45,137 @@ def is_fork_supported(self, fork: Fork) -> bool: Currently, EthereumJS-t8n provides no way to determine supported forks. """ return True + + +class EthereumJSExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by ethereum-js. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "insufficient funds for gas * price + value", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "would exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "max fee per blob gas less than block blob gas fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "max fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "blob tx used but field env.ExcessBlobGas missing", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "has invalid hash version", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "blob transaction missing blob hashes", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "is lower than the minimum gas limit of", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] diff --git a/src/ethereum_clis/clis/execution_specs.py b/src/ethereum_clis/clis/execution_specs.py index 816592a6c5..ff4020a39d 100644 --- a/src/ethereum_clis/clis/execution_specs.py +++ b/src/ethereum_clis/clis/execution_specs.py @@ -142,7 +142,7 @@ def _mapping_data(self): ), ExceptionMessage( TransactionException.TYPE_3_TX_PRE_FORK, - "Failed to parse transaction 0: module 'ethereum.shanghai.transactions' has no attribute 'BlobTransaction'", # noqa: E501 + "module 'ethereum.shanghai.transactions' has no attribute 'BlobTransaction'", ), ExceptionMessage( TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, diff --git a/src/ethereum_clis/clis/nimbus.py b/src/ethereum_clis/clis/nimbus.py index 318a3df18e..aae58d9523 100644 --- a/src/ethereum_clis/clis/nimbus.py +++ b/src/ethereum_clis/clis/nimbus.py @@ -8,6 +8,12 @@ from re import compile from typing import Optional +from ethereum_test_exceptions import ( + EOFException, + ExceptionMapper, + ExceptionMessage, + TransactionException, +) from ethereum_test_forks import Fork from ..transition_tool import TransitionTool @@ -32,7 +38,7 @@ def __init__( binary: Optional[Path] = None, trace: bool = False, ): - super().__init__(binary=binary, trace=trace) + super().__init__(exception_mapper=NimbusExceptionMapper(), binary=binary, trace=trace) args = [str(self.binary), "--help"] try: result = subprocess.run(args, capture_output=True, text=True) @@ -58,3 +64,137 @@ def is_fork_supported(self, fork: Fork) -> bool: If the fork is a transition fork, we want to check the fork it transitions to. """ return fork.transition_tool_name() in self.help_string + + +class NimbusExceptionMapper(ExceptionMapper): + """ + Translate between EEST exceptions and error strings returned by nimbus. + """ + + @property + def _mapping_data(self): + return [ + ExceptionMessage( + TransactionException.TYPE_4_TX_CONTRACT_CREATION, + "set code transaction must not be a create transaction", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS, + "invalid tx: not enough cash to send", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + "would exceed maximum allowance", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + "max fee per blob gas less than block blob gas fee", + ), + ExceptionMessage( + TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS, + "max fee per gas less than block base fee", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_PRE_FORK, + "blob tx used but field env.ExcessBlobGas missing", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH, + "invalid tx: one of blobVersionedHash has invalid version", + ), + # This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + ExceptionMessage( + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + "exceeds maximum allowance", + ), + ExceptionMessage( + TransactionException.TYPE_3_TX_ZERO_BLOBS, + "blob transaction missing blob hashes", + ), + ExceptionMessage( + TransactionException.INTRINSIC_GAS_TOO_LOW, + "intrinsic gas too low", + ), + ExceptionMessage( + TransactionException.INITCODE_SIZE_EXCEEDED, + "max initcode size exceeded", + ), + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage( + EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size" + ), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage( + EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number" + ), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage( + EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions" + ), + ExceptionMessage( + EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination" + ), + ExceptionMessage( + EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections" + ), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage( + EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height" + ), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), + ExceptionMessage( + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" + ), + ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), + ExceptionMessage( + EOFException.INVALID_CONTAINER_SECTION_INDEX, + "err: invalid_container_section_index", + ), + ExceptionMessage( + EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind" + ), + ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"), + ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"), + ExceptionMessage( + EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index" + ), + ] From 6fd60c22dcee7c9db50d2f9c77d4c28baf221e9c Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Thu, 10 Oct 2024 11:23:29 +0200 Subject: [PATCH 5/9] implement None ins exception_to_message --- .../exception_mapper.py | 7 ++-- src/ethereum_test_specs/helpers.py | 40 +++++++++++-------- whitelist.txt | 1 + 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/ethereum_test_exceptions/exception_mapper.py b/src/ethereum_test_exceptions/exception_mapper.py index a7dfc14d6c..f9401206bb 100644 --- a/src/ethereum_test_exceptions/exception_mapper.py +++ b/src/ethereum_test_exceptions/exception_mapper.py @@ -43,14 +43,15 @@ def _mapping_data(self): """This method should be overridden in the subclass to provide mapping data.""" pass - def exception_to_message(self, exception: ExceptionBase) -> str: + def exception_to_message(self, exception: ExceptionBase) -> str | None: """Takes an exception and returns a formatted string.""" - message = self.exception_to_message_map.get(exception, "Undefined") + message = self.exception_to_message_map.get(exception, None) return message def message_to_exception(self, exception_string: str) -> ExceptionBase: """Takes a string and tries to find matching exception""" - # TODO inform tester where to add the missing exception if get uses default + # TODO exception strings are not matching the exception perfectly + # Use Levenshtein distance to try to find the best matching exception exception = self.exception_to_message_map.inverse.get( exception_string, UndefinedException.UNDEFINED_EXCEPTION ) diff --git a/src/ethereum_test_specs/helpers.py b/src/ethereum_test_specs/helpers.py index 2cc3dccb11..915aef83ba 100644 --- a/src/ethereum_test_specs/helpers.py +++ b/src/ethereum_test_specs/helpers.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Dict, List -from ethereum_test_exceptions import ExceptionMapper, TransactionException, UndefinedException +from ethereum_test_exceptions import ExceptionMapper, UndefinedException from ethereum_test_types import Transaction from evm_transition_tool import Result @@ -14,7 +14,7 @@ class TransactionExceptionInfo: """Info to print transaction exception error messages""" - error_message: str | None + t8n_error_message: str | None transaction_ind: int tx: Transaction @@ -29,16 +29,20 @@ def verify_transaction_exception( ) # info.tx.error is expected error code defined in .py test - if expected_error and not info.error_message: + if expected_error and not info.t8n_error_message: raise Exception(f"{exception_info} Error: tx expected to fail succeeded") - elif not expected_error and info.error_message: - raise Exception(f"{exception_info} Error: tx unexpectedly failed: {info.error_message}") - elif expected_error and info.error_message: + elif not expected_error and info.t8n_error_message: + raise Exception( + f'{exception_info} Error: tx unexpectedly failed: "{info.t8n_error_message}"' + ) + elif expected_error and info.t8n_error_message: if isinstance(info.tx.error, List): for expected_exception in info.tx.error: expected_error_msg = exception_mapper.exception_to_message(expected_exception) - if expected_error_msg in info.error_message: + if expected_error_msg is None: + continue + if expected_error_msg in info.t8n_error_message: # One of expected exceptions is found in tx error string, no error return @@ -50,26 +54,28 @@ def verify_transaction_exception( expected_exception = info.tx.error expected_error_msg = exception_mapper.exception_to_message(expected_exception) - error_exception = exception_mapper.message_to_exception(info.error_message) + t8n_error_exception = exception_mapper.message_to_exception(info.t8n_error_message) exception_mapper_name = exception_mapper.__class__.__name__ define_message_hint = ( - f"No message defined for {expected_exception}, please add it to {exception_mapper_name}" - if expected_error_msg == "Undefined" + f"No message defined for {expected_exception}, " + f"please add it to {exception_mapper_name}" + if expected_error_msg is None else "" ) define_exception_hint = ( - f"No exception defined for error message got, " - f"please add it to {exception_mapper.__class__.__name__}" - if error_exception == UndefinedException.UNDEFINED_EXCEPTION + "No exception defined for error message got, " + f"please add it to {exception_mapper_name}" + if t8n_error_exception == UndefinedException.UNDEFINED_EXCEPTION else "" ) - if expected_error_msg not in info.error_message: + if expected_error_msg is None or expected_error_msg not in info.t8n_error_message: raise Exception( f"{exception_info}" - f"Error: exception mismatch:\n want = '{expected_error_msg}' ({expected_exception})," - f"\n got = '{info.error_message}' ({error_exception})" + f"Error: exception mismatch:" + f"\n want = '{expected_error_msg}' ({expected_exception})," + f"\n got = '{info.t8n_error_message}' ({t8n_error_exception})" f"\n {define_message_hint}" f"\n {define_exception_hint}" ) @@ -88,7 +94,7 @@ def verify_transactions( for i, tx in enumerate(txs): error_message = rejected_txs[i] if i in rejected_txs else None - info = TransactionExceptionInfo(error_message=error_message, transaction_ind=i, tx=tx) + info = TransactionExceptionInfo(t8n_error_message=error_message, transaction_ind=i, tx=tx) verify_transaction_exception(exception_mapper=exception_mapper, info=info) return list(rejected_txs.keys()) diff --git a/whitelist.txt b/whitelist.txt index b3b0a689a7..0e69332479 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -249,6 +249,7 @@ kzg kzgs lastblockhash len +Levenshtein linux listdir lll From cb477eeaeb1357ad84cd514531a7ade21e1b8cb9 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Tue, 15 Oct 2024 11:07:43 +0200 Subject: [PATCH 6/9] fix rebase --- src/ethereum_test_specs/eof.py | 3 +-- src/ethereum_test_specs/helpers.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ethereum_test_specs/eof.py b/src/ethereum_test_specs/eof.py index 924e667050..3096dfff69 100644 --- a/src/ethereum_test_specs/eof.py +++ b/src/ethereum_test_specs/eof.py @@ -12,9 +12,8 @@ import pytest from pydantic import Field, model_validator -from ethereum_clis import TransitionTool +from ethereum_clis import EvmoneExceptionMapper, TransitionTool from ethereum_test_base_types import Account, Bytes -from ethereum_test_exceptions import EvmoneExceptionMapper from ethereum_test_exceptions.exceptions import EOFExceptionInstanceOrList, to_pipe_str from ethereum_test_fixtures import ( BaseFixture, diff --git a/src/ethereum_test_specs/helpers.py b/src/ethereum_test_specs/helpers.py index 915aef83ba..c811d0a161 100644 --- a/src/ethereum_test_specs/helpers.py +++ b/src/ethereum_test_specs/helpers.py @@ -5,9 +5,9 @@ from dataclasses import dataclass from typing import Dict, List +from ethereum_clis import Result from ethereum_test_exceptions import ExceptionMapper, UndefinedException from ethereum_test_types import Transaction -from evm_transition_tool import Result @dataclass From 5d8d350470f45948c7813fdba21eb8c54a035b67 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Tue, 15 Oct 2024 11:50:32 +0200 Subject: [PATCH 7/9] fix tox --- src/ethereum_clis/clis/execution_specs.py | 4 ++++ src/ethereum_test_specs/base.py | 5 ++--- tests/prague/eip7702_set_code_tx/test_set_code_txs.py | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ethereum_clis/clis/execution_specs.py b/src/ethereum_clis/clis/execution_specs.py index ff4020a39d..85ff426d64 100644 --- a/src/ethereum_clis/clis/execution_specs.py +++ b/src/ethereum_clis/clis/execution_specs.py @@ -165,6 +165,10 @@ def _mapping_data(self): TransactionException.INITCODE_SIZE_EXCEEDED, "ansaction: ", ), + ExceptionMessage( + TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS, + "nsaction: ", + ), # TODO EVMONE needs to differentiate when the section is missing in the header or body ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index 5c3ec2da9f..3dc4e24dc3 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -7,17 +7,16 @@ from itertools import count from os import path from pathlib import Path -from typing import Callable, ClassVar, Dict, Generator, Iterator, List, Optional +from typing import Callable, ClassVar, Generator, Iterator, List, Optional import pytest from pydantic import BaseModel, Field from ethereum_clis import Result, TransitionTool from ethereum_test_base_types import to_hex -from ethereum_test_exceptions import ExceptionMapper, TransactionException from ethereum_test_fixtures import BaseFixture, FixtureFormat from ethereum_test_forks import Fork -from ethereum_test_types import Environment, Transaction, Withdrawal +from ethereum_test_types import Environment, Withdrawal class HashMismatchException(Exception): diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index c64c5a926c..052314731c 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2021,7 +2021,9 @@ def test_set_code_invalid_authorization_tuple( nonce=( 1 if invalidity_reason == InvalidityReason.NONCE - else [0, 1] if invalidity_reason == InvalidityReason.MULTIPLE_NONCE else 0 + else [0, 1] + if invalidity_reason == InvalidityReason.MULTIPLE_NONCE + else 0 ), chain_id=2 if invalidity_reason == InvalidityReason.CHAIN_ID else 0, signer=auth_signer, From 5ed444e519cbf862cd5f7ffb431b9c7accf91a4f Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Thu, 17 Oct 2024 22:42:52 +0200 Subject: [PATCH 8/9] add exception classes, add exception verification into state tests --- src/ethereum_test_exceptions/__init__.py | 2 + .../exception_mapper.py | 10 +- src/ethereum_test_specs/helpers.py | 104 +++++++++++++----- src/ethereum_test_specs/state.py | 10 ++ 4 files changed, 94 insertions(+), 32 deletions(-) diff --git a/src/ethereum_test_exceptions/__init__.py b/src/ethereum_test_exceptions/__init__.py index d547d50a37..22c1c9fec1 100644 --- a/src/ethereum_test_exceptions/__init__.py +++ b/src/ethereum_test_exceptions/__init__.py @@ -9,6 +9,7 @@ BlockExceptionInstanceOrList, EOFException, EOFExceptionInstanceOrList, + ExceptionBase, ExceptionInstanceOrList, TransactionException, TransactionExceptionInstanceOrList, @@ -20,6 +21,7 @@ "BlockExceptionInstanceOrList", "EOFException", "EOFExceptionInstanceOrList", + "ExceptionBase", "EngineAPIError", "ExceptionMapper", "ExceptionMessage", diff --git a/src/ethereum_test_exceptions/exception_mapper.py b/src/ethereum_test_exceptions/exception_mapper.py index f9401206bb..dadafecdf6 100644 --- a/src/ethereum_test_exceptions/exception_mapper.py +++ b/src/ethereum_test_exceptions/exception_mapper.py @@ -50,9 +50,7 @@ def exception_to_message(self, exception: ExceptionBase) -> str | None: def message_to_exception(self, exception_string: str) -> ExceptionBase: """Takes a string and tries to find matching exception""" - # TODO exception strings are not matching the exception perfectly - # Use Levenshtein distance to try to find the best matching exception - exception = self.exception_to_message_map.inverse.get( - exception_string, UndefinedException.UNDEFINED_EXCEPTION - ) - return exception + for entry in self._mapping_data: + if entry.message in exception_string: + return entry.exception + return UndefinedException.UNDEFINED_EXCEPTION diff --git a/src/ethereum_test_specs/helpers.py b/src/ethereum_test_specs/helpers.py index c811d0a161..83195efcfa 100644 --- a/src/ethereum_test_specs/helpers.py +++ b/src/ethereum_test_specs/helpers.py @@ -6,10 +6,73 @@ from typing import Dict, List from ethereum_clis import Result -from ethereum_test_exceptions import ExceptionMapper, UndefinedException +from ethereum_test_exceptions import ExceptionBase, ExceptionMapper, UndefinedException from ethereum_test_types import Transaction +class TransactionExpectedToFailSucceed(Exception): + """ + Exception used when the transaction expected to return an error, did succeed + """ + + def __init__(self, index: int, nonce: int): + message = ( + f"\nTransactionException (pos={index}, nonce={nonce}):" + f"\n What: tx expected to fail succeeded!" + ) + super().__init__(message) + + +class TransactionUnexpectedFail(Exception): + """ + Exception used when the transaction expected to succeed, did fail + """ + + def __init__(self, index: int, nonce: int, message: str, exception: ExceptionBase): + message = ( + f"\nTransactionException (pos={index}, nonce={nonce}):" + f"\n What: tx unexpectedly failed!" + f'\n Error: "{message}" ({exception})' + ) + super().__init__(message) + + +class TransactionExceptionMismatch(Exception): + """ + Exception used when the actual transaction error string differs from the expected one. + """ + + def __init__( + self, + index: int, + nonce: int, + expected_message: str | None, + expected_exception: ExceptionBase, + got_message: str, + got_exception: ExceptionBase, + mapper_name: str, + ): + define_message_hint = ( + f"No message defined for {expected_exception}, please add it to {mapper_name}" + if expected_message is None + else "" + ) + define_exception_hint = ( + f"No exception defined for error message got, please add it to {mapper_name}" + if got_exception == UndefinedException.UNDEFINED_EXCEPTION + else "" + ) + message = ( + f"\nTransactionException (pos={index}, nonce={nonce}):" + f"\n What: exception mismatch!" + f'\n Want: "{expected_message}" ({expected_exception})' + f'\n Got: "{got_message}" ({got_exception})' + f"\n {define_message_hint}" + f"\n {define_exception_hint}" + ) + super().__init__(message) + + @dataclass class TransactionExceptionInfo: """Info to print transaction exception error messages""" @@ -23,20 +86,21 @@ def verify_transaction_exception( exception_mapper: ExceptionMapper, info: TransactionExceptionInfo ): """Verify transaction exception""" - exception_info = f"TransactionException (pos={info.transaction_ind}, nonce={info.tx.nonce})\n" expected_error: bool = info.tx.error is not None or ( isinstance(info.tx.error, list) and len(info.tx.error) != 0 ) # info.tx.error is expected error code defined in .py test if expected_error and not info.t8n_error_message: - raise Exception(f"{exception_info} Error: tx expected to fail succeeded") + raise TransactionExpectedToFailSucceed(index=info.transaction_ind, nonce=info.tx.nonce) elif not expected_error and info.t8n_error_message: - raise Exception( - f'{exception_info} Error: tx unexpectedly failed: "{info.t8n_error_message}"' + raise TransactionUnexpectedFail( + index=info.transaction_ind, + nonce=info.tx.nonce, + message=info.t8n_error_message, + exception=exception_mapper.message_to_exception(info.t8n_error_message), ) elif expected_error and info.t8n_error_message: - if isinstance(info.tx.error, List): for expected_exception in info.tx.error: expected_error_msg = exception_mapper.exception_to_message(expected_exception) @@ -57,27 +121,15 @@ def verify_transaction_exception( t8n_error_exception = exception_mapper.message_to_exception(info.t8n_error_message) exception_mapper_name = exception_mapper.__class__.__name__ - define_message_hint = ( - f"No message defined for {expected_exception}, " - f"please add it to {exception_mapper_name}" - if expected_error_msg is None - else "" - ) - define_exception_hint = ( - "No exception defined for error message got, " - f"please add it to {exception_mapper_name}" - if t8n_error_exception == UndefinedException.UNDEFINED_EXCEPTION - else "" - ) - if expected_error_msg is None or expected_error_msg not in info.t8n_error_message: - raise Exception( - f"{exception_info}" - f"Error: exception mismatch:" - f"\n want = '{expected_error_msg}' ({expected_exception})," - f"\n got = '{info.t8n_error_message}' ({t8n_error_exception})" - f"\n {define_message_hint}" - f"\n {define_exception_hint}" + raise TransactionExceptionMismatch( + index=info.transaction_ind, + nonce=info.tx.nonce, + expected_exception=expected_exception, + expected_message=expected_error_msg, + got_exception=t8n_error_exception, + got_message=info.t8n_error_message, + mapper_name=exception_mapper_name, ) diff --git a/src/ethereum_test_specs/state.py b/src/ethereum_test_specs/state.py index 84f30dda74..80c7f5333e 100644 --- a/src/ethereum_test_specs/state.py +++ b/src/ethereum_test_specs/state.py @@ -2,6 +2,7 @@ Ethereum state test spec definition and filler. """ +from pprint import pprint from typing import Any, Callable, ClassVar, Dict, Generator, List, Optional, Type import pytest @@ -27,6 +28,7 @@ from .base import BaseTest from .blockchain import Block, BlockchainTest, Header from .debugging import print_traces +from .helpers import verify_transactions TARGET_BLOB_GAS_PER_BLOCK = 393216 @@ -151,6 +153,14 @@ def make_state_test_fixture( print_traces(t8n.get_traces()) raise e + try: + verify_transactions(t8n.exception_mapper, [tx], transition_tool_output.result) + except Exception as e: + print_traces(t8n.get_traces()) + pprint(transition_tool_output.result) + pprint(transition_tool_output.alloc) + raise e + return Fixture( env=FixtureEnvironment(**env.model_dump(exclude_none=True)), pre=pre_alloc, From 9ee05d29e88bbb615abd98c1bc969eea7a618ae3 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 23 Oct 2024 18:48:33 +0000 Subject: [PATCH 9/9] fix(specs): tests --- src/ethereum_test_specs/tests/test_expect.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ethereum_test_specs/tests/test_expect.py b/src/ethereum_test_specs/tests/test_expect.py index 2e530da2c6..c9a574cd93 100644 --- a/src/ethereum_test_specs/tests/test_expect.py +++ b/src/ethereum_test_specs/tests/test_expect.py @@ -7,7 +7,7 @@ import pytest from ethereum_clis import ExecutionSpecsTransitionTool -from ethereum_test_base_types import Account, Address +from ethereum_test_base_types import Account, Address, TestAddress, TestPrivateKey from ethereum_test_fixtures import StateFixture from ethereum_test_forks import Fork, get_deployed_forks from ethereum_test_types import Alloc, Environment, Storage, Transaction @@ -17,12 +17,20 @@ ADDRESS_UNDER_TEST = Address(0x01) +@pytest.fixture +def tx() -> Transaction: + """ + The transaction: Set from the test's indirectly parametrized `tx` parameter. + """ + return Transaction(secret_key=TestPrivateKey) + + @pytest.fixture def pre(request) -> Alloc: """ The pre state: Set from the test's indirectly parametrized `pre` parameter. """ - return Alloc(request.param) + return Alloc(request.param | {TestAddress: Account(balance=(10**18))}) @pytest.fixture @@ -40,14 +48,13 @@ def fork() -> Fork: # noqa: D103 @pytest.fixture def state_test( # noqa: D103 - fork: Fork, pre: Mapping[Any, Any], post: Mapping[Any, Any] + pre: Mapping[Any, Any], post: Mapping[Any, Any], tx: Transaction ) -> StateTest: return StateTest( env=Environment(), pre=pre, post=post, - tx=Transaction(), - tag="post_value_mismatch", + tx=tx, )