From c8d26ba0597db8cfc33cba42f0d3231e1dc3d3dd Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Fri, 22 Sep 2023 13:29:16 -0400 Subject: [PATCH] src/ethereum_test_tools: Initial refactor for blockchain test filling. --- docs/CHANGELOG.md | 2 + src/ethereum_test_tools/common/types.py | 2 +- .../spec/blockchain_test.py | 356 +++++++++--------- src/ethereum_test_tools/spec/state_test.py | 2 +- src/ethereum_test_tools/tests/test_code.py | 1 - src/ethereum_test_tools/tests/test_filler.py | 3 - 6 files changed, 180 insertions(+), 186 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 34e4fad3d6..63d048d00a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,8 @@ Test fixtures for use by clients are available for each release on the [Github r ### 🔧 Tools +- 🔀 Spec: Refactor state and blockchain spec ([#307](https://github.com/ethereum/execution-spec-tests/pull/307)). + ### 📋 Misc - ✨ Docs: Changelog updated post release ([#321](https://github.com/ethereum/execution-spec-tests/pull/321)). diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index b612156eb9..6fd9d11b82 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2770,7 +2770,7 @@ class Fixture(BaseFixture): to_json=True, ), ) - head: Hash = field( + last_block_hash: Hash = field( json_encoder=JSONEncoder.Field( name="lastblockhash", ), diff --git a/src/ethereum_test_tools/spec/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain_test.py index 39a8cb3adc..e08241e6d2 100644 --- a/src/ethereum_test_tools/spec/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain_test.py @@ -17,13 +17,17 @@ Bytes, EmptyTrieRoot, Environment, + Fixture, FixtureBlock, FixtureEngineNewPayload, FixtureHeader, Hash, HeaderNonce, + HiveFixture, Number, + Transaction, ZeroPaddedHexNumber, + alloc_to_accounts, to_json, withdrawals_root, ) @@ -66,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: @@ -74,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, @@ -113,212 +120,199 @@ def make_genesis( return Alloc(new_alloc), genesis_rlp, genesis - def make_block( + def get_block_data( self, t8n: TransitionTool, fork: Fork, + alloc: Dict[str, Any], + env: Environment, block: Block, - previous_env: Environment, - previous_alloc: Dict[str, Any], - previous_head: Hash, eips: Optional[List[int]] = None, - ) -> Tuple[FixtureBlock, Optional[FixtureEngineNewPayload], Environment, Dict[str, Any], Hash]: + ) -> Tuple[FixtureHeader, Bytes, List[Transaction], Dict[str, Any], Environment, Hash]: """ - 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. - + TODO """ - if block.rlp and block.exception is not None: + env = block.set_environment(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 [] + + next_alloc, result = t8n.evaluate( + alloc=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(), + ) + try: + rejected_txs = verify_transactions(txs, result) + verify_result(result, env) + except Exception as e: + print_traces(t8n.get_traces()) + pprint(result) + pprint(alloc) + pprint(next_alloc) + raise e + + if len(rejected_txs) > 0 and block.exception is None: + print_traces(t8n.get_traces()) raise Exception( - "test correctness: post-state cannot be verified if the " - + "block's rlp is supplied and the block is not supposed " - + "to produce an 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, + ) - 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 [] - ) + if block.header_verify is not None: + # Verify the header after transition tool processing. + print(header) + 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(), - ) - 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, - ) + 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) - if block.header_verify is not None: - # Verify the header after transition tool processing. - header.verify(block.header_verify) + rlp, header.hash = header.build( + txs=txs, + ommers=[], + withdrawals=env.withdrawals, + ) + return header, rlp, txs, next_alloc, env, header.hash - 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) + def make_fixture( + self, + t8n: TransitionTool, + fork: Fork, + eips: Optional[List[int]] = None, + ) -> Fixture: + """ + TODO + """ + fixture_blocks: List[FixtureBlock] = [] - rlp, header.hash = header.build( - txs=txs, - ommers=[], - withdrawals=env.withdrawals, - ) + 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) - 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, + for block in self.blocks: + if block.rlp and block.exception is not None: + raise Exception( + "test correctness: post-state cannot be verified if the " + + "block's rlp is supplied and the block is not supposed " + + "to produce an exception" ) - if self.hive_enabled - else None + header, rlp, txs, alloc, env, hash = self.get_block_data( + t8n, fork, alloc, env, block, eips ) - - 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, - ) + 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, + ), + ) + # Update env, alloc and last block hash for the next block. + alloc = alloc + env = env + head = hash + else: + fixture_blocks.append( + FixtureBlock( + rlp=rlp, + block_number=Number(header.number), + expected_exception=block.exception, + ), + ) else: - return ( + fixture_blocks.append( FixtureBlock( - rlp=rlp, - block_number=Number(header.number), + rlp=Bytes(block.rlp), expected_exception=block.exception, ), - fixture_payload, - previous_env, - previous_alloc, - previous_head, ) - else: - return ( - FixtureBlock( - rlp=Bytes(block.rlp), - expected_exception=block.exception, - ), - None, - previous_env, - previous_alloc, - previous_head, - ) - def make_blocks( + try: + verify_post_alloc(self.post, alloc) + except Exception as e: + print_traces(t8n.get_traces()) + raise e + + network_info = ( + "+".join([fork.name()] + [str(eip) for eip in eips]) + if eips is not None + else fork.name() + ) + + return Fixture( + fork=network_info, + 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, eips: Optional[List[int]] = None, - ) -> Tuple[ - Optional[List[FixtureBlock]], - 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. + TODO """ + pre, _, genesis = self.make_genesis(t8n, fork) + fixture_payloads: List[Optional[FixtureEngineNewPayload]] = [] + alloc = to_json(pre) env = Environment.from_parent_header(genesis) - fixture_blocks: List[FixtureBlock] | 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, - 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 block.exception is None: - 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, + if block.rlp and block.exception is not None: + raise Exception( + "test correctness: post-state cannot be verified if the " + + "block's rlp is supplied and the block is not supposed " + + "to produce an exception" + ) + header, _, txs, alloc, env, _ = self.get_block_data(t8n, fork, alloc, env, block, eips) + fixture_payloads.append( + 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, + ) ) + fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) + + network_info = ( + "+".join([fork.name()] + [str(eip) for eip in eips]) + if eips is not None + else fork.name() + ) try: verify_post_alloc(self.post, alloc) @@ -326,12 +320,14 @@ def make_blocks( print_traces(t8n.get_traces()) raise e - return ( - fixture_blocks, - fixture_payloads, - head, - alloc, - fcu_version, + return HiveFixture( + fork=network_info, + 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 4d6ecc4407..a43c90b272 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -201,7 +201,7 @@ def make_fixture( withdrawals=env.withdrawals, ) ], - head=header.hash, + last_block_hash=header.hash, pre_state=pre, post_state=alloc_to_accounts(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 cef80179c0..1e0b1575a1 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, ), } @@ -443,7 +442,6 @@ def test_fill_blockchain_valid_txs( t8n=t8n, test_spec=blockchain_test, fork=fork, - engine="NoProof", spec=None, ) } @@ -792,7 +790,6 @@ def test_fill_blockchain_invalid_txs( t8n=t8n, test_spec=blockchain_test, fork=fork, - engine="NoProof", spec=None, ) }