diff --git a/specs/merge/validator.md b/specs/merge/validator.md index 27fb263528..dcb5a986f9 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -125,7 +125,7 @@ def prepare_execution_payload(state: BeaconState, execution_engine: ExecutionEngine) -> Optional[PayloadId]: if not is_merge_complete(state): is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32() - is_activation_epoch_reached = get_current_epoch(state.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + is_activation_epoch_reached = get_current_epoch(state) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH if is_terminal_block_hash_set and not is_activation_epoch_reached: # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed return None diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py index 9b710d2ead..d4acf04a65 100644 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from eth2spec.test.helpers.pow_block import ( prepare_random_pow_chain, ) @@ -62,3 +64,99 @@ def test_get_pow_block_at_terminal_total_difficulty(spec, state): assert pow_block is None else: raise Exception('Something is wrong') + + +SAMPLE_PAYLOAD_ID = b'\x12' * 8 +# ('is_merge_complete', 'is_terminal_block_hash_set', 'is_activation_epoch_reached', +# 'terminal_pow_block_is_none', 'result_payload_id') +prepare_execution_payload_expected_results = [ + (False, False, False, False, SAMPLE_PAYLOAD_ID), + (False, False, False, True, None), + (False, False, True, False, SAMPLE_PAYLOAD_ID), + (False, False, True, True, None), + (False, True, False, False, None), + (False, True, False, True, None), + (False, True, True, False, SAMPLE_PAYLOAD_ID), + (False, True, True, True, None), + (True, False, False, False, SAMPLE_PAYLOAD_ID), + (True, False, False, True, SAMPLE_PAYLOAD_ID), + (True, False, True, False, SAMPLE_PAYLOAD_ID), + (True, False, True, True, SAMPLE_PAYLOAD_ID), + (True, True, False, False, SAMPLE_PAYLOAD_ID), + (True, True, False, True, SAMPLE_PAYLOAD_ID), + (True, True, True, False, SAMPLE_PAYLOAD_ID), + (True, True, True, True, SAMPLE_PAYLOAD_ID), +] + + +@with_merge_and_later +@spec_state_test +def test_prepare_execution_payload(spec, state): + for result in prepare_execution_payload_expected_results: + ( + is_merge_complete, + is_terminal_block_hash_set, + is_activation_epoch_reached, + terminal_pow_block_is_none, + result_payload_id, + ) = result + + # 1. Handle `is_merge_complete` + if is_merge_complete: + state.latest_execution_payload_header = spec.ExecutionPayloadHeader(random=b'\x12' * 32) + else: + state.latest_execution_payload_header = spec.ExecutionPayloadHeader() + + # 2. `is_terminal_block_hash_set` and `is_activation_epoch_reached` require mocking configs in runtime + config_overrides = {} + _mock_terminal_block_hash = b'\x34' * 32 + if is_terminal_block_hash_set: + config_overrides['TERMINAL_BLOCK_HASH'] = _mock_terminal_block_hash + else: + config_overrides['TERMINAL_BLOCK_HASH'] = spec.Hash32() + + # Default `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` is too big and too close to overflow + _mock_terminal_block_hash_activation_epoch = 3 + config_overrides['TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH'] = _mock_terminal_block_hash_activation_epoch + if is_activation_epoch_reached: + state.slot = _mock_terminal_block_hash_activation_epoch * spec.SLOTS_PER_EPOCH + else: + state.slot = (_mock_terminal_block_hash_activation_epoch - 1) * spec.SLOTS_PER_EPOCH + + # Logic from `with_config_overrides` + old_config = spec.config + tmp_config = deepcopy(old_config._asdict()) + tmp_config.update(config_overrides) + config_types = spec.Configuration.__annotations__ + test_config = {k: config_types[k](v) for k, v in tmp_config.items()} + spec.config = spec.Configuration(**test_config) + + # 3. Handle `terminal_pow_block_is_none` + pow_chain = prepare_random_pow_chain(spec, 2) + if terminal_pow_block_is_none: + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - 1 + else: + if is_terminal_block_hash_set: + pow_chain.head().block_hash = _mock_terminal_block_hash + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + + # Dummy arguments + finalized_block_hash = b'\x56' * 32 + suggested_fee_recipient = b'\x78' * 20 + + # Mock execution_engine + class TestEngine(spec.NoopExecutionEngine): + def notify_forkchoice_updated(self, parent_hash, finalized_block_hash, payload_attributes) -> bool: + return SAMPLE_PAYLOAD_ID + + payload_id = spec.prepare_execution_payload( + state=state, + pow_chain=pow_chain.to_dict(), + finalized_block_hash=finalized_block_hash, + suggested_fee_recipient=suggested_fee_recipient, + execution_engine=TestEngine(), + ) + assert payload_id == result_payload_id + + # Restore config + spec.config = old_config