diff --git a/eth/_utils/headers.py b/eth/_utils/headers.py index 98b8cb94bb..c9bfa21bc9 100644 --- a/eth/_utils/headers.py +++ b/eth/_utils/headers.py @@ -4,10 +4,7 @@ Tuple, ) -from eth_typing import ( - Address, -) - +from eth_typing import Address from eth.abc import BlockHeaderAPI from eth.constants import ( BLANK_ROOT_HASH, @@ -21,6 +18,7 @@ GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR, ZERO_ADDRESS, ) +from eth.exceptions import PyEVMError from eth.typing import ( BlockNumber, HeaderParams, @@ -57,6 +55,7 @@ def fill_header_params_from_parent( gas_limit: int, difficulty: int, timestamp: int, + block_number: int = None, coinbase: Address = ZERO_ADDRESS, nonce: bytes = None, extra_data: bytes = None, @@ -67,11 +66,13 @@ def fill_header_params_from_parent( if parent is None: parent_hash = GENESIS_PARENT_HASH - block_number = GENESIS_BLOCK_NUMBER + block_number = block_number if block_number else GENESIS_BLOCK_NUMBER if state_root is None: state_root = BLANK_ROOT_HASH else: parent_hash = parent.hash + if block_number: + raise PyEVMError("block_number cannot be configured if a parent header exists.") block_number = BlockNumber(parent.block_number + 1) if state_root is None: diff --git a/tests/core/vm/test_vm.py b/tests/core/vm/test_vm.py index 951c557fc6..efee5becf8 100644 --- a/tests/core/vm/test_vm.py +++ b/tests/core/vm/test_vm.py @@ -11,10 +11,13 @@ MiningChain, ) from eth.chains.mainnet import MAINNET_VMS +from eth.exceptions import PyEVMError from eth.tools.builder.chain import api from eth.tools.factories.transaction import ( new_transaction ) +from eth_typing import BlockNumber +from eth._utils.headers import fill_header_params_from_parent @pytest.fixture(params=MAINNET_VMS) @@ -191,3 +194,55 @@ def test_validate_gas_limit_too_high(noproof_consensus_chain): with pytest.raises(ValidationError, match="[Gg]as limit"): vm.validate_header(invalid_header, block1.header) + + +@pytest.mark.parametrize( + "custom_header_params,null_parent", + ( + ({ + 'gas_limit': 1, + 'difficulty': 10, + 'timestamp': 100, + 'block_number': 1000 + }, True), + ({ + 'gas_limit': 1, + 'difficulty': 10, + 'timestamp': 100, + }, False), + ({ + 'gas_limit': 1, + 'difficulty': 10, + 'timestamp': 100, + 'block_number': 1000, + }, False) + ) +) +def test_fill_header_params_from_parent(custom_header_params, null_parent, + noproof_consensus_chain): + # Handle cases which want to specify a null parent. + block = noproof_consensus_chain.mine_block() + header = block.header if null_parent is False else None + + # Cannot specify block number and a parent. + if 'block_number' in custom_header_params and header is not None: + with pytest.raises(PyEVMError, match="block_number cannot be configured if a parent header exists."): + new_header_params = fill_header_params_from_parent(header, **custom_header_params) + return + new_header_params = fill_header_params_from_parent(header, **custom_header_params) + + # Compare fields which are copied no matter what. + trivial_fields = ['gas_limit', 'difficulty', 'timestamp'] + for field in trivial_fields: + assert new_header_params[field] == custom_header_params[field] + + # Check `block_number` and `parent_hash` cases. + if 'block_number' in custom_header_params: + assert new_header_params['block_number'] == custom_header_params['block_number'] + assert new_header_params['parent_hash'] == constants.GENESIS_PARENT_HASH + elif header is None: + assert new_header_params['block_number'] == constants.GENESIS_BLOCK_NUMBER + assert new_header_params['parent_hash'] == constants.GENESIS_PARENT_HASH + else: + assert new_header_params['block_number'] == BlockNumber(header['block_number'] + 1) + assert new_header_params['parent_hash'] == header.hash