diff --git a/eth/tools/_utils/normalization.py b/eth/tools/_utils/normalization.py index 72f40a1e99..d70f5eedb8 100644 --- a/eth/tools/_utils/normalization.py +++ b/eth/tools/_utils/normalization.py @@ -542,6 +542,8 @@ def normalize_block_header(header: Dict[str, Any]) -> Dict[str, Any]: } if 'blocknumber' in header: normalized_header['blocknumber'] = to_int(header['blocknumber']) + if 'baseFeePerGas' in header: + normalized_header['baseFeePerGas'] = to_int(header['baseFeePerGas']) if 'chainname' in header: normalized_header['chainname'] = header['chainname'] if 'chainnetwork' in header: diff --git a/eth/tools/fixtures/helpers.py b/eth/tools/fixtures/helpers.py index bbc4a8b4a7..e82700842d 100644 --- a/eth/tools/fixtures/helpers.py +++ b/eth/tools/fixtures/helpers.py @@ -11,7 +11,10 @@ Type, ) -from eth_utils.toolz import first +from eth_utils.toolz import ( + assoc, + first, +) from eth_utils import ( to_normalized_address, @@ -133,6 +136,10 @@ def chain_vm_configuration(fixture: Dict[str, Any]) -> Iterable[Tuple[int, Type[ return ( (0, BerlinVM), ) + elif network == 'London': + return ( + (0, LondonVM), + ) elif network == 'FrontierToHomesteadAt5': HomesteadVM = BaseHomesteadVM.configure(support_dao_fork=False) return ( @@ -177,23 +184,28 @@ def genesis_fields_from_fixture(fixture: Dict[str, Any]) -> Dict[str, Any]: Convert all genesis fields in a fixture to a dictionary of header fields and values. """ - return { - 'parent_hash': fixture['genesisBlockHeader']['parentHash'], - 'uncles_hash': fixture['genesisBlockHeader']['uncleHash'], - 'coinbase': fixture['genesisBlockHeader']['coinbase'], - 'state_root': fixture['genesisBlockHeader']['stateRoot'], - 'transaction_root': fixture['genesisBlockHeader']['transactionsTrie'], - 'receipt_root': fixture['genesisBlockHeader']['receiptTrie'], - 'bloom': fixture['genesisBlockHeader']['bloom'], - 'difficulty': fixture['genesisBlockHeader']['difficulty'], - 'block_number': fixture['genesisBlockHeader']['number'], - 'gas_limit': fixture['genesisBlockHeader']['gasLimit'], - 'gas_used': fixture['genesisBlockHeader']['gasUsed'], - 'timestamp': fixture['genesisBlockHeader']['timestamp'], - 'extra_data': fixture['genesisBlockHeader']['extraData'], - 'mix_hash': fixture['genesisBlockHeader']['mixHash'], - 'nonce': fixture['genesisBlockHeader']['nonce'], + header_fields = fixture['genesisBlockHeader'] + base_fields = { + 'parent_hash': header_fields['parentHash'], + 'uncles_hash': header_fields['uncleHash'], + 'coinbase': header_fields['coinbase'], + 'state_root': header_fields['stateRoot'], + 'transaction_root': header_fields['transactionsTrie'], + 'receipt_root': header_fields['receiptTrie'], + 'bloom': header_fields['bloom'], + 'difficulty': header_fields['difficulty'], + 'block_number': header_fields['number'], + 'gas_limit': header_fields['gasLimit'], + 'gas_used': header_fields['gasUsed'], + 'timestamp': header_fields['timestamp'], + 'extra_data': header_fields['extraData'], + 'mix_hash': header_fields['mixHash'], + 'nonce': header_fields['nonce'], } + if 'baseFeePerGas' in header_fields: + return assoc(base_fields, 'base_fee_per_gas', header_fields['baseFeePerGas']) + else: + return base_fields def genesis_params_from_fixture(fixture: Dict[str, Any]) -> Dict[str, Any]: diff --git a/eth/vm/forks/london/headers.py b/eth/vm/forks/london/headers.py index ea99db2727..ba2eb4d144 100644 --- a/eth/vm/forks/london/headers.py +++ b/eth/vm/forks/london/headers.py @@ -108,17 +108,25 @@ def create_header_from_parent(difficulty_fn: Callable[[BlockHeaderAPI, int], int header_params['timestamp'], ) + # The general fill function doesn't recognize this custom field, so remove it + configured_fee_per_gas = header_params.pop('base_fee_per_gas', None) + all_fields = fill_header_params_from_parent(parent_header, **header_params) - # must add the new field *after* filling, because the general fill function doesn't recognize it - base_fee_per_gas = calculate_expected_base_fee_per_gas(parent_header) - if 'base_fee_per_gas' in header_params and all_fields['base_fee_per_gas'] != base_fee_per_gas: - raise ValidationError( - f"Cannot select an invalid base_fee_per_gas of:" - f" {all_fields['base_fee_per_gas']!r}, expected: {base_fee_per_gas}" - ) + calculated_fee_per_gas = calculate_expected_base_fee_per_gas(parent_header) + if configured_fee_per_gas is None: + all_fields['base_fee_per_gas'] = calculated_fee_per_gas else: - all_fields['base_fee_per_gas'] = base_fee_per_gas + # Must not configure an invalid base fee. So verify that either: + # 1. This is the genesis header, or + # 2. The configured value matches the calculated value from the parent + if parent_header is None or configured_fee_per_gas == calculated_fee_per_gas: + all_fields['base_fee_per_gas'] = configured_fee_per_gas + else: + raise ValidationError( + f"Cannot select an invalid base_fee_per_gas of:" + f" {configured_fee_per_gas}, expected: {calculated_fee_per_gas}" + ) new_header = LondonBlockHeader(**all_fields) # type:ignore return new_header diff --git a/newsfragments/2022.internal.rst b/newsfragments/2022.internal.rst new file mode 100644 index 0000000000..1c0726fbfc --- /dev/null +++ b/newsfragments/2022.internal.rst @@ -0,0 +1 @@ +During fixture tests, verify that the generated genesis block matches the fixture's RLP-encoding. diff --git a/tests/json-fixtures/blockchain/test_blockchain.py b/tests/json-fixtures/blockchain/test_blockchain.py index 150a5a77c2..0162611ffc 100644 --- a/tests/json-fixtures/blockchain/test_blockchain.py +++ b/tests/json-fixtures/blockchain/test_blockchain.py @@ -10,6 +10,7 @@ from eth.tools.rlp import ( assert_mined_block_unchanged, + assert_headers_eq, ) from eth.tools._utils.normalization import ( normalize_blockchain_fixtures, @@ -24,6 +25,7 @@ should_run_slow_tests, verify_state, ) +from eth.vm.header import HeaderSedes ROOT_PROJECT_DIR = Path(__file__).parents[3] @@ -325,15 +327,22 @@ def test_blockchain_fixtures(fixture_data, fixture): except ValueError as e: raise AssertionError(f"could not load chain for {fixture_data}") from e - # TODO: find out if this is supposed to pass? - # if 'genesisRLP' in fixture: - # assert rlp.encode(genesis_header) == fixture['genesisRLP'] - genesis_fields = genesis_fields_from_fixture(fixture) genesis_block = chain.get_canonical_block_by_number(0) genesis_header = genesis_block.header + # Validate the genesis header RLP against the generated header + if 'genesisRLP' in fixture: + # Super hacky, but better than nothing: extract the header, then re-decode it + fixture_decoded_block = rlp.decode(fixture['genesisRLP']) + fixture_encoded_header = rlp.encode(fixture_decoded_block[0]) + fixture_header = rlp.decode(fixture_encoded_header, sedes=HeaderSedes) + # Error message with pretty output if header doesn't match + assert_headers_eq(fixture_header, genesis_header) + # Last gut check that transactions & receipts are valid, too + assert rlp.encode(genesis_block) == fixture['genesisRLP'] + assert_imported_genesis_header_unchanged(genesis_fields, genesis_header) # 1 - mine the genesis block