From a035e3d500aa63d56fab0c56aba0070919dd445c Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 3 Oct 2023 20:02:57 -0600 Subject: [PATCH] src/tools: Refactor & clean up of state_test. --- src/ethereum_test_tools/common/types.py | 40 ++--- src/ethereum_test_tools/filling/fill.py | 15 +- src/ethereum_test_tools/spec/base_test.py | 2 +- src/ethereum_test_tools/spec/state_test.py | 161 +++++++-------------- 4 files changed, 73 insertions(+), 145 deletions(-) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index e9813bd5b7..68dc53d417 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -946,7 +946,6 @@ class Environment: to_json=True, ), ) - base_fee: Optional[NumberConvertible] = field( default=None, json_encoder=JSONEncoder.Field( @@ -961,7 +960,6 @@ class Environment: cast_type=Number, ), ) - parent_timestamp: Optional[NumberConvertible] = field( default=None, json_encoder=JSONEncoder.Field( @@ -1033,7 +1031,7 @@ class Environment: ), ) extra_data: Optional[BytesConvertible] = field( - default=None, + default=b"\x00", json_encoder=JSONEncoder.Field( skip=True, ), @@ -2028,7 +2026,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="parentHash"), ) - ommers_hash: Hash = header_field( source=HeaderFieldSource( parse_type=Hash, @@ -2037,7 +2034,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="uncleHash"), ) - coinbase: Address = header_field( source=HeaderFieldSource( parse_type=Address, @@ -2045,7 +2041,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(), ) - state_root: Hash = header_field( source=HeaderFieldSource( parse_type=Hash, @@ -2053,7 +2048,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="stateRoot"), ) - transactions_root: Hash = header_field( source=HeaderFieldSource( parse_type=Hash, @@ -2061,7 +2055,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="transactionsTrie"), ) - receipt_root: Hash = header_field( source=HeaderFieldSource( parse_type=Hash, @@ -2069,7 +2062,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="receiptTrie"), ) - bloom: Bloom = header_field( source=HeaderFieldSource( parse_type=Bloom, @@ -2077,7 +2069,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(), ) - difficulty: int = header_field( source=HeaderFieldSource( parse_type=Number, @@ -2087,7 +2078,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(cast_type=ZeroPaddedHexNumber), ) - number: int = header_field( source=HeaderFieldSource( parse_type=Number, @@ -2095,7 +2085,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(cast_type=ZeroPaddedHexNumber), ) - gas_limit: int = header_field( source=HeaderFieldSource( parse_type=Number, @@ -2103,7 +2092,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="gasLimit", cast_type=ZeroPaddedHexNumber), ) - gas_used: int = header_field( source=HeaderFieldSource( parse_type=Number, @@ -2111,7 +2099,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="gasUsed", cast_type=ZeroPaddedHexNumber), ) - timestamp: int = header_field( source=HeaderFieldSource( parse_type=Number, @@ -2119,7 +2106,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(cast_type=ZeroPaddedHexNumber), ) - extra_data: Bytes = header_field( source=HeaderFieldSource( parse_type=Bytes, @@ -2128,7 +2114,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="extraData"), ) - mix_digest: Hash = header_field( source=HeaderFieldSource( parse_type=Hash, @@ -2137,7 +2122,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="mixHash"), ) - nonce: HeaderNonce = header_field( source=HeaderFieldSource( parse_type=HeaderNonce, @@ -2145,7 +2129,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(), ) - base_fee: Optional[int] = header_field( default=None, source=HeaderFieldSource( @@ -2156,7 +2139,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="baseFeePerGas", cast_type=ZeroPaddedHexNumber), ) - withdrawals_root: Optional[Hash] = header_field( default=None, source=HeaderFieldSource( @@ -2166,7 +2148,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="withdrawalsRoot"), ) - blob_gas_used: Optional[int] = header_field( default=None, source=HeaderFieldSource( @@ -2176,7 +2157,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="blobGasUsed", cast_type=ZeroPaddedHexNumber), ) - excess_blob_gas: Optional[int] = header_field( default=None, source=HeaderFieldSource( @@ -2186,7 +2166,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="excessBlobGas", cast_type=ZeroPaddedHexNumber), ) - beacon_root: Optional[Hash] = header_field( default=None, source=HeaderFieldSource( @@ -2196,7 +2175,6 @@ class FixtureHeader: ), json_encoder=JSONEncoder.Field(name="parentBeaconBlockRoot"), ) - hash: Optional[Hash] = header_field( default=None, source=HeaderFieldSource( @@ -2638,6 +2616,14 @@ class FixtureBlock: Representation of an Ethereum block within a test Fixture. """ + @staticmethod + def _txs_encoder(txs: List[Transaction]) -> List[FixtureTransaction]: + return [FixtureTransaction.from_transaction(tx) for tx in txs] + + @staticmethod + def _withdrawals_encoder(withdrawals: List[Withdrawal]) -> List[FixtureWithdrawal]: + return [FixtureWithdrawal.from_withdrawal(w) for w in withdrawals] + rlp: Bytes = field( default=None, json_encoder=JSONEncoder.Field(), @@ -2666,7 +2652,7 @@ class FixtureBlock: default=None, json_encoder=JSONEncoder.Field( name="transactions", - cast_type=lambda txs: [FixtureTransaction.from_transaction(tx) for tx in txs], + cast_type=_txs_encoder, to_json=True, ), ) @@ -2681,9 +2667,7 @@ class FixtureBlock: default=None, json_encoder=JSONEncoder.Field( name="withdrawals", - cast_type=lambda withdrawals: [ - FixtureWithdrawal.from_withdrawal(w) for w in withdrawals - ], + cast_type=_withdrawals_encoder, to_json=True, ), ) @@ -2716,7 +2700,7 @@ class InvalidFixtureBlock: @dataclass(kw_only=True) class BaseFixture: """ - Base Ethereum test fixture class. + Base Ethereum test fixture fields class. """ info: Dict[str, str] = field( diff --git a/src/ethereum_test_tools/filling/fill.py b/src/ethereum_test_tools/filling/fill.py index 8697d425eb..742c87de83 100644 --- a/src/ethereum_test_tools/filling/fill.py +++ b/src/ethereum_test_tools/filling/fill.py @@ -1,5 +1,5 @@ """ -Filler object definitions. +Test filler definitions. """ from typing import List, Optional, Union @@ -19,18 +19,15 @@ def fill_test( eips: Optional[List[int]] = None, ) -> Optional[Union[Fixture, HiveFixture]]: """ - Fills fixtures for the specified fork. + Fills default/hive fixture for the specified fork and test spec. """ - t8n.reset_traces() fixture: Union[Fixture, HiveFixture] + t8n.reset_traces() if test_spec.base_test_config.enable_hive: - if fork.engine_new_payload_version() is not None: - fixture = test_spec.make_hive_fixture(t8n, fork, eips) - else: # pre Merge tests are not supported in Hive - # TODO: remove this logic. if hive enabled set --from to Merge - return None + if fork.engine_new_payload_version() is None: + return None # pre Merge tests are not supported in Hive + fixture = test_spec.make_hive_fixture(t8n, fork, eips) else: fixture = test_spec.make_fixture(t8n, fork, eips) fixture.fill_info(t8n, spec) - return fixture diff --git a/src/ethereum_test_tools/spec/base_test.py b/src/ethereum_test_tools/spec/base_test.py index 764874990b..e5965b8361 100644 --- a/src/ethereum_test_tools/spec/base_test.py +++ b/src/ethereum_test_tools/spec/base_test.py @@ -1,5 +1,5 @@ """ -Generic Ethereum test base class +Base test class and helper functions for Ethereum state and blockchain tests. """ from abc import abstractmethod from dataclasses import dataclass, field diff --git a/src/ethereum_test_tools/spec/state_test.py b/src/ethereum_test_tools/spec/state_test.py index e2b216e23e..845a60ea99 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -1,9 +1,9 @@ """ -State test filler. +Ethereum state test spec definition and filler. """ from copy import copy from dataclasses import dataclass -from typing import Callable, Generator, List, Mapping, Optional, Tuple, Type +from typing import Any, Callable, Dict, Generator, List, Mapping, Optional, Tuple, Type from ethereum_test_forks import Fork from evm_transition_tool import TransitionTool @@ -45,7 +45,6 @@ class StateTest(BaseTest): txs: List[Transaction] engine_api_error_code: Optional[EngineAPIError] = None tag: str = "" - chain_id: int = 1 @classmethod @@ -63,7 +62,7 @@ def make_genesis( """ Create a genesis block from the state test definition. """ - # The genesis environment is similar to the block 1 environment specified by the test + # Similar to the block 1 environment specified by the test # with some slight differences, so make a copy here genesis_env = copy(self.env) @@ -75,15 +74,12 @@ def make_genesis( genesis_env.number >= 0 ), "genesis block number cannot be negative, set state test env.number to 1" - # Set the fork requirements to the genesis environment in-place genesis_env.set_fork_requirements(fork, in_place=True) - pre_alloc = Alloc( fork.pre_allocation( block_number=genesis_env.number, timestamp=Number(genesis_env.timestamp) ) ) - new_alloc, state_root = t8n.calc_state_root( alloc=to_json(Alloc.merge(pre_alloc, Alloc(self.pre))), fork=fork, @@ -124,42 +120,32 @@ def make_genesis( return Alloc(new_alloc), genesis_rlp, genesis - def make_fixture( - self, - t8n: TransitionTool, - fork: Fork, - eips: Optional[List[int]] = None, - ) -> Fixture: + def generate_fixture_data( + self, t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None + ) -> Tuple[FixtureHeader, Bytes, Alloc, List[Transaction], Dict, Dict[str, Any], str]: """ - Create a block from the state test definition. - Performs checks against the expected behavior of the test. - Raises exception on invalid test behavior. + Generate common fixture data for both make_fixture and make_hive_fixture. """ pre, genesis_rlp, genesis = self.make_genesis(t8n, fork) - network_info = ( - "+".join([fork.name()] + [str(eip) for eip in eips]) - if eips is not None - else fork.name() + "+".join([fork.name()] + [str(eip) for eip in eips]) if eips else fork.name() ) - env = self.env.apply_new_parent(genesis) - env = env.set_fork_requirements(fork) + self.env = self.env.apply_new_parent(genesis).set_fork_requirements(fork) + txs = [tx.with_signature_and_sender() for tx in self.txs] if self.txs else [] - txs = [tx.with_signature_and_sender() for tx in self.txs] if self.txs is not None else [] - - alloc, result = t8n.evaluate( + t8n_alloc, t8n_result = t8n.evaluate( alloc=to_json(pre), txs=to_json(txs), - env=to_json(env), + env=to_json(self.env), fork_name=network_info, chain_id=self.chain_id, - reward=fork.get_reward(Number(env.number), Number(env.timestamp)), + reward=fork.get_reward(Number(self.env.number), Number(self.env.timestamp)), eips=eips, debug_output_path=self.get_next_transition_tool_output_path(), ) - rejected_txs = verify_transactions(txs, result) + rejected_txs = verify_transactions(txs, t8n_result) if len(rejected_txs) > 0: raise Exception( "one or more transactions in `StateTest` are " @@ -167,28 +153,36 @@ def make_fixture( + "Use `BlockchainTest` to verify rejection of blocks " + "that include invalid transactions." ) - try: - verify_post_alloc(self.post, alloc) - verify_result(result, env) + verify_post_alloc(self.post, t8n_alloc) + verify_result(t8n_result, self.env) except Exception as e: print_traces(traces=t8n.get_traces()) raise e - env.extra_data = b"\x00" - header = FixtureHeader.collect( - fork=fork, - transition_tool_result=result, - environment=env, - ) + return genesis, genesis_rlp, pre, txs, t8n_result, t8n_alloc, network_info - block, header.hash = header.build( - txs=txs, - ommers=[], - withdrawals=env.withdrawals, + def make_fixture( + self, t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None + ) -> Fixture: + """ + Create a fixture from the state test definition. + """ + ( + genesis, + genesis_rlp, + pre, + txs, + t8n_result, + t8n_alloc, + network_info, + ) = self.generate_fixture_data(t8n, fork, eips) + header = FixtureHeader.collect( + fork=fork, transition_tool_result=t8n_result, environment=self.env ) + block, header.hash = header.build(txs=txs, ommers=[], withdrawals=self.env.withdrawals) - fixture = Fixture( + return Fixture( fork=network_info, genesis=genesis, genesis_rlp=genesis_rlp, @@ -198,100 +192,53 @@ def make_fixture( block_header=header, txs=txs, ommers=[], - withdrawals=env.withdrawals, + withdrawals=self.env.withdrawals, ) ], last_block_hash=header.hash, pre_state=pre, - post_state=alloc_to_accounts(alloc), + post_state=alloc_to_accounts(t8n_alloc), ) - return fixture def make_hive_fixture( - self, - t8n: TransitionTool, - fork: Fork, - eips: Optional[List[int]] = None, + self, t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None ) -> HiveFixture: """ - Create a block from the state test definition. - Performs checks against the expected behavior of the test. - Raises exception on invalid test behavior. + Create a hive fixture from the state test definition. """ - pre, _, genesis = self.make_genesis(t8n, fork) - - network_info = ( - "+".join([fork.name()] + [str(eip) for eip in eips]) - if eips is not None - else fork.name() - ) - - env = self.env.apply_new_parent(genesis) - env = env.set_fork_requirements(fork) - - txs = [tx.with_signature_and_sender() for tx in self.txs] if self.txs is not None else [] + ( + genesis, + _, + pre, + txs, + t8n_result, + t8n_alloc, + network_info, + ) = self.generate_fixture_data(t8n, fork, eips) - alloc, result = t8n.evaluate( - alloc=to_json(pre), - txs=to_json(txs), - env=to_json(env), - fork_name=network_info, - chain_id=self.chain_id, - reward=fork.get_reward(Number(env.number), Number(env.timestamp)), - eips=eips, - debug_output_path=self.get_next_transition_tool_output_path(), - ) - - rejected_txs = verify_transactions(txs, result) - if len(rejected_txs) > 0: - raise Exception( - "one or more transactions in `StateTest` are " - + "intrinsically invalid, which are not allowed. " - + "Use `BlockchainTest` to verify rejection of blocks " - + "that include invalid transactions." - ) - - try: - verify_post_alloc(self.post, alloc) - verify_result(result, env) - except Exception as e: - print_traces(traces=t8n.get_traces()) - raise e - - env.extra_data = b"\x00" header = FixtureHeader.collect( - fork=fork, - transition_tool_result=result, - environment=env, - ) - - _, header.hash = header.build( - txs=txs, - ommers=[], - withdrawals=env.withdrawals, + fork=fork, transition_tool_result=t8n_result, environment=self.env ) - + _, header.hash = header.build(txs=txs, ommers=[], withdrawals=self.env.withdrawals) fixture_payload = FixtureEngineNewPayload.from_fixture_header( fork=fork, header=header, transactions=txs, - withdrawals=env.withdrawals, + withdrawals=self.env.withdrawals, valid=True, error_code=None, ) fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) - hive_fixture = HiveFixture( + return HiveFixture( fork=network_info, genesis=genesis, payloads=[fixture_payload], fcu_version=fcu_version, pre_state=pre, - post_state=alloc_to_accounts(alloc), + post_state=alloc_to_accounts(t8n_alloc), ) - return hive_fixture - StateTestSpec = Callable[[str], Generator[StateTest, None, None]] StateTestFiller = Type[StateTest]