diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f792a613ab..06e5d8f9fb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,7 @@ Test fixtures for use by clients are available for each release on the [Github r ### 🛠️ Framework - 🔀 Fixtures: Add a non-RLP format field (`rlp_decoded`) to invalid blocks ([#322](https://github.com/ethereum/execution-spec-tests/pull/322)). +- 🔀 Spec: Refactor state and blockchain spec ([#307](https://github.com/ethereum/execution-spec-tests/pull/307)). ### 🔧 EVM Tools diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index c2b6b93649..344b9be37e 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( @@ -2576,14 +2554,14 @@ class FixtureEngineNewPayload: cast_type=Hash, ), ) - version: int = field( - json_encoder=JSONEncoder.Field(), - ) valid: bool = field( json_encoder=JSONEncoder.Field( skip_string_convert=True, ), ) + version: int = field( + json_encoder=JSONEncoder.Field(), + ) error_code: Optional[EngineAPIError] = field( default=None, json_encoder=JSONEncoder.Field( @@ -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( @@ -2726,17 +2710,17 @@ class BaseFixture: to_json=True, ), ) - fork: str = field( - json_encoder=JSONEncoder.Field( - name="network", - ), - ) name: str = field( default="", json_encoder=JSONEncoder.Field( skip=True, ), ) + fork: str = field( + json_encoder=JSONEncoder.Field( + name="network", + ), + ) _json: Dict[str, Any] | None = field( default=None, json_encoder=JSONEncoder.Field( @@ -2795,7 +2779,7 @@ class Fixture(BaseFixture): to_json=True, ), ) - head: Hash = field( + last_block_hash: Hash = field( json_encoder=JSONEncoder.Field( name="lastblockhash", ), @@ -2816,6 +2800,7 @@ class Fixture(BaseFixture): ), ) seal_engine: str = field( + default="NoProof", json_encoder=JSONEncoder.Field( name="sealEngine", ), diff --git a/src/ethereum_test_tools/filling/fill.py b/src/ethereum_test_tools/filling/fill.py index 7eb07a777b..742c87de83 100644 --- a/src/ethereum_test_tools/filling/fill.py +++ b/src/ethereum_test_tools/filling/fill.py @@ -1,12 +1,12 @@ """ -Filler object definitions. +Test filler definitions. """ from typing import List, Optional, Union from ethereum_test_forks import Fork from evm_transition_tool import TransitionTool -from ..common import Fixture, HiveFixture, alloc_to_accounts +from ..common import Fixture, HiveFixture from ..reference_spec.reference_spec import ReferenceSpec from ..spec import BaseTest @@ -15,56 +15,19 @@ def fill_test( t8n: TransitionTool, test_spec: BaseTest, fork: Fork, - engine: str, spec: ReferenceSpec | None, 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() - - pre, genesis_rlp, genesis = test_spec.make_genesis(t8n, fork) - - (blocks, payloads, head, alloc, fcu_version) = test_spec.make_blocks( - t8n, - genesis, - pre, - fork, - eips=eips, - ) - - network_info = ( - "+".join([fork.name()] + [str(eip) for eip in eips]) if eips is not None else fork.name() - ) - 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 = HiveFixture( - payloads=payloads, - fcu_version=fcu_version, - genesis=genesis, - fork=network_info, - pre_state=pre, - post_state=alloc_to_accounts(alloc), - name=test_spec.tag, - ) - 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 = Fixture( - blocks=blocks, - genesis=genesis, - genesis_rlp=genesis_rlp, - head=head, - fork=network_info, - pre_state=pre, - post_state=alloc_to_accounts(alloc), - seal_engine=engine, - name=test_spec.tag, - ) + 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 10f410b971..e5965b8361 100644 --- a/src/ethereum_test_tools/spec/base_test.py +++ b/src/ethereum_test_tools/spec/base_test.py @@ -1,11 +1,11 @@ """ -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 from itertools import count from os import path -from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Optional, Tuple +from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Optional from ethereum_test_forks import Fork from evm_transition_tool import TransitionTool @@ -13,14 +13,9 @@ from ..common import ( Account, Address, - Alloc, - Bytes, Environment, - FixtureBlock, - FixtureEngineNewPayload, - FixtureHeader, - Hash, - InvalidFixtureBlock, + Fixture, + HiveFixture, Transaction, withdrawals_root, ) @@ -110,32 +105,24 @@ class BaseTest: t8n_call_counter: Iterator[int] = field(init=False, default_factory=count) @abstractmethod - def make_genesis( + def make_fixture( self, t8n: TransitionTool, fork: Fork, - ) -> Tuple[Alloc, Bytes, FixtureHeader]: + eips: Optional[List[int]] = None, + ) -> Fixture: """ - Create a genesis block from the test definition. + Generate blockchain that must be executed sequentially during test. """ pass @abstractmethod - def make_blocks( + def make_hive_fixture( self, t8n: TransitionTool, - genesis: FixtureHeader, - pre: Alloc, fork: Fork, - chain_id: int = 1, eips: Optional[List[int]] = None, - ) -> Tuple[ - Optional[List[FixtureBlock | InvalidFixtureBlock]], - Optional[List[Optional[FixtureEngineNewPayload]]], - Hash, - Dict[str, Any], - Optional[int], - ]: + ) -> HiveFixture: """ Generate the blockchain that must be executed sequentially during test. """ diff --git a/src/ethereum_test_tools/spec/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain_test.py index ec515bf692..0a4c32a509 100644 --- a/src/ethereum_test_tools/spec/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain_test.py @@ -1,5 +1,5 @@ """ -Blockchain test filler. +Ethereum blockchain test spec definition and filler. """ from dataclasses import dataclass, field @@ -17,14 +17,18 @@ Bytes, EmptyTrieRoot, Environment, + Fixture, FixtureBlock, FixtureEngineNewPayload, FixtureHeader, Hash, HeaderNonce, + HiveFixture, InvalidFixtureBlock, Number, + Transaction, ZeroPaddedHexNumber, + alloc_to_accounts, to_json, withdrawals_root, ) @@ -44,6 +48,7 @@ class BlockchainTest(BaseTest): blocks: List[Block] genesis_environment: Environment = field(default_factory=Environment) tag: str = "" + chain_id: int = 1 @classmethod def pytest_parameter_name(cls) -> str: @@ -65,7 +70,7 @@ def make_genesis( fork: Fork, ) -> Tuple[Alloc, Bytes, FixtureHeader]: """ - Create a genesis block from the state test definition. + Create a genesis block from the blockchain test definition. """ env = self.genesis_environment.set_fork_requirements(fork) if env.withdrawals is not None: @@ -73,7 +78,10 @@ def make_genesis( if env.beacon_root is not None: assert Hash(env.beacon_root) == Hash(0), "beacon_root must be empty at genesis" - pre_alloc = Alloc(fork.pre_allocation(block_number=0, timestamp=Number(env.timestamp))) + pre_alloc = Alloc( + fork.pre_allocation(block_number=0, timestamp=Number(env.timestamp)), + ) + new_alloc, state_root = t8n.calc_state_root( alloc=to_json(Alloc.merge(pre_alloc, Alloc(self.pre))), fork=fork, @@ -112,41 +120,17 @@ def make_genesis( return Alloc(new_alloc), genesis_rlp, genesis - def make_block( + def generate_block_data( self, t8n: TransitionTool, fork: Fork, block: Block, previous_env: Environment, previous_alloc: Dict[str, Any], - previous_head: Hash, - chain_id=1, eips: Optional[List[int]] = None, - ) -> Tuple[ - FixtureBlock | InvalidFixtureBlock, - Optional[FixtureEngineNewPayload], - Environment, - Dict[str, Any], - Hash, - ]: + ) -> Tuple[FixtureHeader, Bytes, List[Transaction], Dict[str, Any], Environment]: """ - Produces a block based on the previous environment and allocation. - If the block is an invalid block, the environment and allocation - returned are the same as passed as parameters. - Raises exception on invalid test behavior. - - Returns - ------- - FixtureBlock: Block to be appended to the fixture. - Environment: Environment for the next block to produce. - If the produced block is invalid, this is exactly the same - environment as the one passed as parameter. - Dict[str, Any]: Allocation for the next block to produce. - If the produced block is invalid, this is exactly the same - allocation as the one passed as parameter. - str: Hash of the head of the chain, only updated if the produced - block is not invalid. - + Generate common block data for both make_fixture and make_hive_fixture. """ if block.rlp and block.exception is not None: raise Exception( @@ -155,198 +139,199 @@ def make_block( + "to produce an exception" ) - if block.rlp is None: - # This is the most common case, the RLP needs to be constructed - # based on the transactions to be included in the block. - # Set the environment according to the block to execute. - env = block.set_environment(previous_env) - env = env.set_fork_requirements(fork) - - txs = ( - [tx.with_signature_and_sender() for tx in block.txs] - if block.txs is not None - else [] - ) + env = block.set_environment(previous_env) + env = env.set_fork_requirements(fork) - next_alloc, result = t8n.evaluate( - alloc=previous_alloc, - txs=to_json(txs), - env=to_json(env), - fork_name=fork.fork( - block_number=Number(env.number), timestamp=Number(env.timestamp) - ), - chain_id=chain_id, - reward=fork.get_reward(Number(env.number), Number(env.timestamp)), - eips=eips, - debug_output_path=self.get_next_transition_tool_output_path(), - ) - try: - rejected_txs = verify_transactions(txs, result) - verify_result(result, env) - except Exception as e: - print_traces(t8n.get_traces()) - pprint(result) - pprint(previous_alloc) - pprint(next_alloc) - raise e - - if len(rejected_txs) > 0 and block.exception is None: - print_traces(t8n.get_traces()) - raise Exception( - "one or more transactions in `BlockchainTest` are " - + "intrinsically invalid, but the block was not expected " - + "to be invalid. Please verify whether the transaction " - + "was indeed expected to fail and add the proper " - + "`block.exception`" - ) - env.extra_data = block.extra_data - header = FixtureHeader.collect( - fork=fork, - transition_tool_result=result, - environment=env, - ) + txs = [tx.with_signature_and_sender() for tx in block.txs] if block.txs is not None else [] - if block.header_verify is not None: - # Verify the header after transition tool processing. - header.verify(block.header_verify) + next_alloc, result = t8n.evaluate( + alloc=previous_alloc, + txs=to_json(txs), + env=to_json(env), + fork_name=fork.fork(block_number=Number(env.number), timestamp=Number(env.timestamp)), + 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(), + ) - if block.rlp_modifier is not None: - # Modify any parameter specified in the `rlp_modifier` after - # transition tool processing. - header = header.join(block.rlp_modifier) + try: + rejected_txs = verify_transactions(txs, result) + verify_result(result, env) + except Exception as e: + print_traces(t8n.get_traces()) + pprint(result) + pprint(previous_alloc) + pprint(next_alloc) + raise e - rlp, header.hash = header.build( - txs=txs, - ommers=[], - withdrawals=env.withdrawals, + if len(rejected_txs) > 0 and block.exception is None: + print_traces(t8n.get_traces()) + raise Exception( + "one or more transactions in `BlockchainTest` are " + + "intrinsically invalid, but the block was not expected " + + "to be invalid. Please verify whether the transaction " + + "was indeed expected to fail and add the proper " + + "`block.exception`" ) - fixture_payload = ( - FixtureEngineNewPayload.from_fixture_header( - fork=fork, - header=header, - transactions=txs, - withdrawals=env.withdrawals, - valid=block.exception is None, - error_code=block.engine_api_error_code, - ) - if self.hive_enabled - else None - ) + env.extra_data = block.extra_data + header = FixtureHeader.collect( + fork=fork, + transition_tool_result=result, + environment=env, + ) - if block.exception is None: - return ( - FixtureBlock( - rlp=rlp, - block_header=header, - block_number=Number(header.number), - txs=txs, - ommers=[], - withdrawals=env.withdrawals, - ), - fixture_payload, - env.apply_new_parent(header), - next_alloc, - header.hash, - ) - else: - return ( - InvalidFixtureBlock( - rlp=rlp, - expected_exception=block.exception, - rlp_decoded=FixtureBlock( + if block.header_verify is not None: + # Verify the header after transition tool processing. + header.verify(block.header_verify) + + if block.rlp_modifier is not None: + # Modify any parameter specified in the `rlp_modifier` after + # transition tool processing. + header = header.join(block.rlp_modifier) + + rlp, header.hash = header.build( + txs=txs, + ommers=[], + withdrawals=env.withdrawals, + ) + + return header, rlp, txs, next_alloc, env + + def network_info(self, fork, eips=None): + """ + Returns fixture network information for the fork & EIP/s. + """ + return "+".join([fork.name()] + [str(eip) for eip in eips]) if eips else fork.name() + + def verify_post_state(self, t8n, alloc): + """ + Verifies the post alloc after all block/s or payload/s are generated. + """ + try: + verify_post_alloc(self.post, alloc) + except Exception as e: + print_traces(t8n.get_traces()) + raise e + + def make_fixture( + self, + t8n: TransitionTool, + fork: Fork, + eips: Optional[List[int]] = None, + ) -> Fixture: + """ + Create a fixture from the blockchain test definition. + """ + fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] = [] + + pre, genesis_rlp, genesis = self.make_genesis(t8n, fork) + + alloc = to_json(pre) + env = Environment.from_parent_header(genesis) + head = genesis.hash if genesis.hash is not None else Hash(0) + + for block in self.blocks: + header, rlp, txs, new_alloc, new_env = self.generate_block_data( + t8n=t8n, fork=fork, block=block, previous_env=env, previous_alloc=alloc, eips=eips + ) + if block.rlp is None: + # This is the most common case, the RLP needs to be constructed + # based on the transactions to be included in the block. + # Set the environment according to the block to execute. + if block.exception is None: + fixture_blocks.append( + FixtureBlock( + rlp=rlp, block_header=header, + block_number=Number(header.number), txs=txs, ommers=[], - withdrawals=env.withdrawals, + withdrawals=new_env.withdrawals, ), + ) + # Update env, alloc and last block hash for the next block. + alloc = new_alloc + env = new_env.apply_new_parent(header) + head = header.hash if header.hash is not None else Hash(0) + else: + fixture_blocks.append( + InvalidFixtureBlock( + rlp=rlp, + expected_exception=block.exception, + rlp_decoded=FixtureBlock( + block_header=header, + txs=txs, + ommers=[], + withdrawals=new_env.withdrawals, + ), + ), + ) + else: + fixture_blocks.append( + InvalidFixtureBlock( + rlp=Bytes(block.rlp), + expected_exception=block.exception, ), - fixture_payload, - previous_env, - previous_alloc, - previous_head, ) - else: - return ( - InvalidFixtureBlock( - rlp=Bytes(block.rlp), - expected_exception=block.exception, - ), - None, - previous_env, - previous_alloc, - previous_head, - ) - def make_blocks( + self.verify_post_state(t8n, alloc) + return Fixture( + fork=self.network_info(fork, eips), + genesis=genesis, + genesis_rlp=genesis_rlp, + blocks=fixture_blocks, + last_block_hash=head, + pre_state=pre, + post_state=alloc_to_accounts(alloc), + name=self.tag, + ) + + def make_hive_fixture( self, t8n: TransitionTool, - genesis: FixtureHeader, - pre: Alloc, fork: Fork, - chain_id=1, eips: Optional[List[int]] = None, - ) -> Tuple[ - Optional[List[FixtureBlock | InvalidFixtureBlock]], - Optional[List[Optional[FixtureEngineNewPayload]]], - Hash, - Dict[str, Any], - Optional[int], - ]: + ) -> HiveFixture: """ - Create a block list from the blockchain test definition. - Performs checks against the expected behavior of the test. - Raises exception on invalid test behavior. + Create a hive fixture from the blocktest definition. """ + fixture_payloads: List[Optional[FixtureEngineNewPayload]] = [] + + pre, _, genesis = self.make_genesis(t8n, fork) alloc = to_json(pre) env = Environment.from_parent_header(genesis) - fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] | None = ( - [] if not self.hive_enabled else None - ) - fixture_payloads: List[Optional[FixtureEngineNewPayload]] | None = ( - [] if self.hive_enabled else None - ) - fcu_version: Optional[int] = None - last_valid: Optional[FixtureHeader] = None - - head = genesis.hash if genesis.hash is not None else Hash(0) for block in self.blocks: - fixture_block, fixture_payload, env, alloc, head = self.make_block( - t8n=t8n, - fork=fork, - block=block, - previous_env=env, - previous_alloc=alloc, - previous_head=head, - chain_id=chain_id, - eips=eips, + header, _, txs, new_alloc, new_env = self.generate_block_data( + t8n=t8n, fork=fork, block=block, previous_env=env, previous_alloc=alloc, eips=eips ) - if not self.hive_enabled and fixture_blocks is not None: - fixture_blocks.append(fixture_block) - if self.hive_enabled and fixture_payloads is not None: - fixture_payloads.append(fixture_payload) - if isinstance(fixture_block, FixtureBlock): - last_valid = fixture_block.block_header - - if self.hive_enabled and last_valid: - fcu_version = fork.engine_forkchoice_updated_version( - block_number=last_valid.number, - timestamp=last_valid.timestamp, - ) - - try: - verify_post_alloc(self.post, alloc) - except Exception as e: - print_traces(t8n.get_traces()) - raise e - - return ( - fixture_blocks, - fixture_payloads, - head, - alloc, - fcu_version, + if block.rlp is None: + fixture_payloads.append( + FixtureEngineNewPayload.from_fixture_header( + fork=fork, + header=header, + transactions=txs, + withdrawals=new_env.withdrawals, + valid=block.exception is None, + error_code=block.engine_api_error_code, + ) + ) + if block.exception is None: + alloc = new_alloc + env = env.apply_new_parent(header) + fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) + + self.verify_post_state(t8n, alloc) + return HiveFixture( + fork=self.network_info(fork, eips), + genesis=genesis, + payloads=fixture_payloads, + fcu_version=fcu_version, + pre_state=pre, + post_state=alloc_to_accounts(alloc), + name=self.tag, ) diff --git a/src/ethereum_test_tools/spec/state_test.py b/src/ethereum_test_tools/spec/state_test.py index 9a2cc26189..74321496b4 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -1,5 +1,5 @@ """ -State test filler. +Ethereum state test spec definition and filler. """ from copy import copy from dataclasses import dataclass @@ -15,15 +15,17 @@ Bytes, EmptyTrieRoot, Environment, + Fixture, FixtureBlock, FixtureEngineNewPayload, FixtureHeader, Hash, HeaderNonce, - InvalidFixtureBlock, + HiveFixture, Number, Transaction, ZeroPaddedHexNumber, + alloc_to_accounts, to_json, ) from ..common.constants import EmptyOmmersRoot, EngineAPIError @@ -43,6 +45,7 @@ class StateTest(BaseTest): txs: List[Transaction] engine_api_error_code: Optional[EngineAPIError] = None tag: str = "" + chain_id: int = 1 @classmethod def pytest_parameter_name(cls) -> str: @@ -59,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) @@ -71,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, @@ -120,43 +120,32 @@ def make_genesis( return Alloc(new_alloc), genesis_rlp, genesis - def make_blocks( - self, - t8n: TransitionTool, - genesis: FixtureHeader, - pre: Alloc, - fork: Fork, - chain_id=1, - eips: Optional[List[int]] = None, - ) -> Tuple[ - Optional[List[FixtureBlock | InvalidFixtureBlock]], - Optional[List[Optional[FixtureEngineNewPayload]]], - Hash, - Dict[str, Any], - Optional[int], - ]: + 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. """ - env = self.env.apply_new_parent(genesis) - env = env.set_fork_requirements(fork) + pre, genesis_rlp, genesis = self.make_genesis(t8n, fork) + network_info = ( + "+".join([fork.name()] + [str(eip) for eip in eips]) if eips else fork.name() + ) - txs = [tx.with_signature_and_sender() for tx in self.txs] if self.txs is not None else [] + 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 [] - alloc, result = t8n.evaluate( + t8n_alloc, t8n_result = t8n.evaluate( alloc=to_json(pre), txs=to_json(txs), - env=to_json(env), - fork_name=fork.fork(block_number=Number(env.number), timestamp=Number(env.timestamp)), - chain_id=chain_id, - reward=fork.get_reward(Number(env.number), Number(env.timestamp)), + env=to_json(self.env), + fork_name=network_info, + chain_id=self.chain_id, + 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 " @@ -164,55 +153,92 @@ def make_blocks( + "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" + return genesis, genesis_rlp, pre, txs, t8n_result, t8n_alloc, network_info + + 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=result, - environment=env, + fork=fork, transition_tool_result=t8n_result, environment=self.env ) - - block, header.hash = header.build( - txs=txs, - ommers=[], - withdrawals=env.withdrawals, + block, header.hash = header.build(txs=txs, ommers=[], withdrawals=self.env.withdrawals) + + return Fixture( + fork=network_info, + genesis=genesis, + genesis_rlp=genesis_rlp, + blocks=[ + FixtureBlock( + rlp=block, + block_header=header, + txs=txs, + ommers=[], + withdrawals=self.env.withdrawals, + ) + ], + last_block_hash=header.hash, + pre_state=pre, + post_state=alloc_to_accounts(t8n_alloc), + name=self.tag, ) - fcu_version: int | None = None - fixture_payload: FixtureEngineNewPayload | None = None - fixture_block: FixtureBlock | None = None - if self.base_test_config.enable_hive: - fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) - fixture_payload = FixtureEngineNewPayload.from_fixture_header( - fork=fork, - header=header, - transactions=txs, - withdrawals=env.withdrawals, - valid=True, - error_code=None, - ) - else: - fixture_block = FixtureBlock( - rlp=block, - block_header=header, - txs=txs, - ommers=[], - withdrawals=env.withdrawals, - ) + def make_hive_fixture( + self, t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None + ) -> HiveFixture: + """ + Create a hive fixture from the state test definition. + """ + ( + genesis, + _, + pre, + txs, + t8n_result, + t8n_alloc, + network_info, + ) = self.generate_fixture_data(t8n, fork, eips) - return ( - [fixture_block] if fixture_block is not None else None, - [fixture_payload] if fixture_payload is not None else None, - header.hash, - alloc, - fcu_version, + header = FixtureHeader.collect( + 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=self.env.withdrawals, + valid=True, + error_code=None, + ) + fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) + + return HiveFixture( + fork=network_info, + genesis=genesis, + payloads=[fixture_payload], + fcu_version=fcu_version, + pre_state=pre, + post_state=alloc_to_accounts(t8n_alloc), + name=self.tag, ) diff --git a/src/ethereum_test_tools/tests/test_code.py b/src/ethereum_test_tools/tests/test_code.py index 37fab373a1..74019f8332 100644 --- a/src/ethereum_test_tools/tests/test_code.py +++ b/src/ethereum_test_tools/tests/test_code.py @@ -652,6 +652,5 @@ def test_switch(tx_data: bytes, switch_bytecode: bytes, expected_storage: Mappin t8n=GethTransitionTool(), test_spec=state_test, fork=Shanghai, - engine="NoProof", spec=None, ) diff --git a/src/ethereum_test_tools/tests/test_filler.py b/src/ethereum_test_tools/tests/test_filler.py index 645845b144..f98a18ef5e 100644 --- a/src/ethereum_test_tools/tests/test_filler.py +++ b/src/ethereum_test_tools/tests/test_filler.py @@ -143,7 +143,6 @@ def test_fill_state_test(fork: Fork, expected_json_file: str, enable_hive: bool) t8n=t8n, test_spec=state_test, fork=fork, - engine="NoProof", spec=None, ), } @@ -441,7 +440,6 @@ def test_fill_blockchain_valid_txs( t8n=t8n, test_spec=blockchain_test, fork=fork, - engine="NoProof", spec=None, ) } @@ -790,7 +788,6 @@ def test_fill_blockchain_invalid_txs( t8n=t8n, test_spec=blockchain_test, fork=fork, - engine="NoProof", spec=None, ) } diff --git a/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_invalid_filled_hive.json b/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_invalid_filled_hive.json index 9405abc6b0..fe81c071a0 100644 --- a/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_invalid_filled_hive.json +++ b/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_invalid_filled_hive.json @@ -296,7 +296,7 @@ "storage": {} } } - } + } }, "solc=padding_version": { "000/my_blockchain_test/Shanghai": { @@ -596,5 +596,5 @@ } } } - } -} \ No newline at end of file + } +} diff --git a/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_valid_filled_hive.json b/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_valid_filled_hive.json index bd6225d20b..b66dbbc879 100644 --- a/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_valid_filled_hive.json +++ b/src/ethereum_test_tools/tests/test_fixtures/blockchain_shanghai_valid_filled_hive.json @@ -500,5 +500,5 @@ } } } - } -} \ No newline at end of file + } +} diff --git a/src/ethereum_test_tools/tests/test_fixtures/chainid_merge_filled_hive.json b/src/ethereum_test_tools/tests/test_fixtures/chainid_merge_filled_hive.json index 0c37c27f0f..ab96ff9a91 100644 --- a/src/ethereum_test_tools/tests/test_fixtures/chainid_merge_filled_hive.json +++ b/src/ethereum_test_tools/tests/test_fixtures/chainid_merge_filled_hive.json @@ -40,8 +40,8 @@ "0xf861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b" ] }, - "valid": true, - "version": "1" + "version": "1", + "valid": true } ], "engineFcuVersion": "1", @@ -82,4 +82,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/ethereum_test_tools/tests/test_fixtures/chainid_shanghai_filled_hive.json b/src/ethereum_test_tools/tests/test_fixtures/chainid_shanghai_filled_hive.json index 368fc51cb7..b0cc9bf987 100644 --- a/src/ethereum_test_tools/tests/test_fixtures/chainid_shanghai_filled_hive.json +++ b/src/ethereum_test_tools/tests/test_fixtures/chainid_shanghai_filled_hive.json @@ -42,8 +42,8 @@ ], "withdrawals": [] }, - "valid": true, - "version": "2" + "version": "2", + "valid": true } ], "engineFcuVersion": "2", @@ -84,4 +84,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/pytest_plugins/test_filler/test_filler.py b/src/pytest_plugins/test_filler/test_filler.py index 01a5de5727..551bb7b23f 100644 --- a/src/pytest_plugins/test_filler/test_filler.py +++ b/src/pytest_plugins/test_filler/test_filler.py @@ -300,14 +300,6 @@ def fixture_collector(request): fixture_collector.dump_fixtures() -@pytest.fixture(autouse=True, scope="session") -def engine(): - """ - Returns the sealEngine used in the generated test fixtures. - """ - return "NoProof" - - @pytest.fixture(autouse=True, scope="session") def filler_path(request): """ @@ -363,7 +355,7 @@ def __init__(self, *args, **kwargs): @pytest.fixture(scope="function") def state_test( - request, t8n, fork, engine, reference_spec, eips, fixture_collector, base_test_config + request, t8n, fork, reference_spec, eips, fixture_collector, base_test_config ) -> StateTestFiller: """ Fixture used to instantiate an auto-fillable StateTest object from within @@ -391,7 +383,6 @@ def __init__(self, *args, **kwargs): t8n, self, fork, - engine, reference_spec, eips=eips, ), @@ -402,7 +393,7 @@ def __init__(self, *args, **kwargs): @pytest.fixture(scope="function") def blockchain_test( - request, t8n, fork, engine, reference_spec, eips, fixture_collector, base_test_config + request, t8n, fork, reference_spec, eips, fixture_collector, base_test_config ) -> BlockchainTestFiller: """ Fixture used to define an auto-fillable BlockchainTest analogous to the @@ -424,7 +415,6 @@ def __init__(self, *args, **kwargs): t8n, self, fork, - engine, reference_spec, eips=eips, ),