diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index be61d070c..58b664d0c 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -5,8 +5,8 @@ PRESET_BASE: 'mainnet' # Transition # --------------------------------------------------------------- -# TBD, 2**256-1 is a placeholder -TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935 +# TBD, 2**256-2**10 is a placeholder +TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 # Genesis diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 8da3260f5..d164dc2e2 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -5,8 +5,8 @@ PRESET_BASE: 'minimal' # Transition # --------------------------------------------------------------- -# TBD, 2**256-1 is a placeholder -TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935 +# TBD, 2**256-2**10 is a placeholder +TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 # Genesis diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 4cf413b10..1a333d678 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -55,8 +55,7 @@ def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, fi ### `PowBlock` ```python -@dataclass -class PowBlock(object): +class PowBlock(Container): block_hash: Hash32 parent_hash: Hash32 total_difficulty: uint256 diff --git a/tests/core/pyspec/eth2spec/test/exceptions.py b/tests/core/pyspec/eth2spec/test/exceptions.py index c553ec374..fcabd88f3 100644 --- a/tests/core/pyspec/eth2spec/test/exceptions.py +++ b/tests/core/pyspec/eth2spec/test/exceptions.py @@ -1,2 +1,6 @@ class SkippedTest(Exception): ... + + +class BlockNotFoundException(Exception): + ... diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 65d6975f2..04843078f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,4 +1,7 @@ +from random import Random from eth_utils import encode_hex +from eth2spec.test.exceptions import BlockNotFoundException +from eth2spec.utils.ssz.ssz_typing import uint256 from eth2spec.test.helpers.attestations import ( next_epoch_with_attestations, next_slots_with_attestations, @@ -22,15 +25,22 @@ def add_block_to_store(spec, store, signed_block): spec.on_block(store, signed_block) -def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): +def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False, + merge_block=False, block_not_found=False): pre_state = store.block_states[signed_block.message.parent_root] block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT + if merge_block: + assert spec.is_merge_block(pre_state, signed_block.message.body) if store.time < block_time: on_tick_and_append_step(spec, store, block_time, test_steps) post_state = yield from add_block( - spec, store, signed_block, test_steps, valid=valid, allow_invalid_attestations=allow_invalid_attestations) + spec, store, signed_block, test_steps, + valid=valid, + allow_invalid_attestations=allow_invalid_attestations, + block_not_found=block_not_found, + ) return post_state @@ -118,7 +128,13 @@ def run_on_block(spec, store, signed_block, valid=True): assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message -def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): +def add_block(spec, + store, + signed_block, + test_steps, + valid=True, + allow_invalid_attestations=False, + block_not_found=False): """ Run on_block and on_attestation """ @@ -127,7 +143,9 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a if not valid: try: run_on_block(spec, store, signed_block, valid=True) - except AssertionError: + except (AssertionError, BlockNotFoundException) as e: + if isinstance(e, BlockNotFoundException) and not block_not_found: + assert False test_steps.append({ 'block': get_block_file_name(signed_block), 'valid': False, @@ -227,3 +245,21 @@ def apply_next_slots_with_attestations(spec, assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() return post_state, store, last_signed_block + + +def prepare_empty_pow_block(spec, rng=Random(3131)): + return spec.PowBlock( + block_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + parent_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + total_difficulty=uint256(0), + difficulty=uint256(0) + ) + + +def get_pow_block_file_name(pow_block): + return f"pow_block_{encode_hex(pow_block.block_hash)}" + + +def add_pow_block(spec, store, pow_block, test_steps): + yield get_pow_block_file_name(pow_block), pow_block + test_steps.append({'pow_block': get_pow_block_file_name(pow_block)}) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index d44bad58c..26a56d150 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -1,3 +1,4 @@ +from eth2spec.utils.ssz.ssz_typing import uint64 from eth2spec.test.helpers.execution_payload import ( build_empty_execution_payload, get_execution_payload_header, @@ -227,3 +228,157 @@ def test_bad_timestamp_regular_payload(spec, state): execution_payload.timestamp = execution_payload.timestamp + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_zero_first_payload(spec, state): + # pre-state + state = build_state_with_incomplete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = uint64(0) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_max_first_payload(spec, state): + # pre-state + state = build_state_with_incomplete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = uint64(2**64 - 1) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_upper_plus_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = ( + execution_payload.gas_limit + + execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR + ) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_upper_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = ( + execution_payload.gas_limit + + execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR - uint64(1) + ) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_lower_minus_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = ( + execution_payload.gas_limit - + execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR + ) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_lower_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = ( + execution_payload.gas_limit - + execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR + uint64(1) + ) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_minimum_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = execution_payload.gas_limit + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gaslimit_minimum_minus_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_limit = execution_payload.gas_limit - uint64(1) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_gasused_gaslimit_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_used = execution_payload.gas_limit + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_gasused_gaslimit_plus_regular_payload(spec, state): + # pre-state + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.gas_used = execution_payload.gas_limit + uint64(1) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/merge/fork_choice/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py new file mode 100644 index 000000000..830f20f23 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py @@ -0,0 +1,173 @@ +from eth2spec.utils.ssz.ssz_typing import uint256 +from eth2spec.test.exceptions import BlockNotFoundException +from eth2spec.test.context import spec_state_test, with_phases, MERGE +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.test.helpers.fork_choice import ( + prepare_empty_pow_block, + add_pow_block, +) +from eth2spec.test.helpers.execution_payload import ( + build_state_with_incomplete_transition, +) + + +def with_pow_block_patch(spec, blocks, func): + def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock: + for block in blocks: + if block.block_hash == hash: + return block + raise BlockNotFoundException() + get_pow_block_backup = spec.get_pow_block + spec.get_pow_block = get_pow_block + + class AtomicBoolean(): + value = False + is_called = AtomicBoolean() + + def wrap(flag: AtomicBoolean): + yield from func() + flag.value = True + + try: + yield from wrap(is_called) + finally: + spec.get_pow_block = get_pow_block_backup + assert is_called.value + + +@with_phases([MERGE]) +@spec_state_test +def test_all_valid(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_block = prepare_empty_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, merge_block=True) + # valid + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield 'steps', test_steps + + +@with_phases([MERGE]) +@spec_state_test +def test_block_lookup_failed(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block = prepare_empty_pow_block(spec) + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_blocks = [pow_block] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True, + block_not_found=True) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield 'steps', test_steps + + +@with_phases([MERGE]) +@spec_state_test +def test_too_early_for_merge(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + pow_block = prepare_empty_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield 'steps', test_steps + + +@with_phases([MERGE]) +@spec_state_test +def test_too_late_for_merge(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_block = prepare_empty_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/merge/unittests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py new file mode 100644 index 000000000..cfd5ea091 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py @@ -0,0 +1,143 @@ +from eth2spec.test.exceptions import BlockNotFoundException +from eth2spec.utils.ssz.ssz_typing import uint256 +from eth2spec.test.helpers.fork_choice import ( + prepare_empty_pow_block, +) +from eth2spec.test.context import spec_state_test, with_merge_and_later + + +# Copy of conditional merge part of `on_block(store: Store, signed_block: SignedBeaconBlock)` handler +def validate_transition_execution_payload(spec, execution_payload): + pow_block = spec.get_pow_block(execution_payload.parent_hash) + pow_parent = spec.get_pow_block(pow_block.parent_hash) + assert spec.is_valid_terminal_pow_block(pow_block, pow_parent) + + +def run_validate_transition_execution_payload(spec, block, parent_block, payload, + valid=True, block_lookup_success=True): + """ + Run ``validate_transition_execution_payload`` + If ``valid == False``, run expecting ``AssertionError`` + If ``block_lookup_success == False``, run expecting ``BlockNotFoundException`` + """ + + def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock: + if hash == block.block_hash: + return block + elif hash == parent_block.block_hash: + return parent_block + else: + raise BlockNotFoundException() + save_pow_block = spec.get_pow_block + + # Guido authorized everyone to do this + spec.get_pow_block = get_pow_block + exception_caught = False + block_not_found_exception_caught = False + try: + validate_transition_execution_payload(spec, payload) + except BlockNotFoundException: + block_not_found_exception_caught = True + except AssertionError: + exception_caught = True + except Exception as e: + spec.get_pow_block = save_pow_block + raise e + spec.get_pow_block = save_pow_block + + if block_lookup_success: + assert not block_not_found_exception_caught + else: + assert block_not_found_exception_caught + if valid: + assert not exception_caught + else: + assert exception_caught + + +@with_merge_and_later +@spec_state_test +def test_valid_terminal_pow_block_success_valid(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + + assert spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_valid_terminal_pow_block_fail_before_terminal(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_valid_terminal_pow_block_fail_just_after_terminal(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_validate_transition_execution_payload_success(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + run_validate_transition_execution_payload(spec, block, parent_block, payload) + + +@with_merge_and_later +@spec_state_test +def test_validate_transition_execution_payload_fail_block_lookup(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + payload = spec.ExecutionPayload() + run_validate_transition_execution_payload(spec, block, parent_block, payload, + block_lookup_success=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_transition_execution_payload_fail_parent_block_lookup(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_empty_pow_block(spec) + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + run_validate_transition_execution_payload(spec, block, parent_block, payload, + block_lookup_success=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_transition_execution_payload_fail_after_terminal(spec, state): + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + 1 + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + run_validate_transition_execution_payload(spec, block, parent_block, payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py new file mode 100644 index 000000000..05d3888da --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py @@ -0,0 +1,55 @@ +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_state_with_incomplete_transition, + build_state_with_complete_transition, +) +from eth2spec.test.context import ( + spec_state_test, + with_merge_and_later +) + + +@with_merge_and_later +@spec_state_test +def test_fail_merge_complete(spec, state): + state = build_state_with_incomplete_transition(spec, state) + assert not spec.is_merge_complete(state) + + +@with_merge_and_later +@spec_state_test +def test_success_merge_complete(spec, state): + state = build_state_with_complete_transition(spec, state) + assert spec.is_merge_complete(state) + + +# with_complete_transition', 'with_execution_payload', 'is_merge_block', 'is_execution_enabled' +expected_results = [ + (True, True, False, True), + (True, False, False, True), + (False, True, True, True), + (False, False, False, False) +] + + +@with_merge_and_later +@spec_state_test +def test_is_merge_block_and_is_execution_enabled(spec, state): + for result in expected_results: + ( + with_complete_transition, + with_execution_payload, + is_merge_block, + is_execution_enabled + ) = result + if with_complete_transition: + state = build_state_with_complete_transition(spec, state) + else: + state = build_state_with_incomplete_transition(spec, state) + + body = spec.BeaconBlockBody() + if with_execution_payload: + body.execution_payload = build_empty_execution_payload(spec, state) + + assert spec.is_merge_block(state, body) == is_merge_block + assert spec.is_execution_enabled(state, body) == is_execution_enabled diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index cfc86776d..48dde2fb1 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -69,6 +69,18 @@ The file is located in the same folder (see below). After this step, the `store` object may have been updated. +#### `on_merge_block` execution + +Adds `PowBlock` data which is required for executing `on_block(store, block)`. +```yaml +{ + pow_block: string -- the name of the `pow_block_<32-byte-root>.ssz_snappy` file. + To be used in `get_pow_block` lookup +} +``` +The file is located in the same folder (see below). +PowBlocks should be used as return values for `get_pow_block(hash: Hash32) -> PowBlock` function if hashes match. + #### Checks step The checks to verify the current status of `store`. diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 148174120..94516a39c 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -9,8 +9,14 @@ if __name__ == "__main__": ]} # No additional Altair specific finality tests, yet. altair_mods = phase_0_mods - # No specific Merge tests yet. - merge_mods = altair_mods + # For merge `on_merge_block` test kind added with `pow_block_N.ssz` files with several + # PowBlock's which should be resolved by `get_pow_block(hash: Hash32) -> PowBlock` function + merge_mods = { + **{key: 'eth2spec.test.merge.fork_choice.test_' + key for key in [ + 'on_merge_block', + ]}, + **altair_mods, + } all_mods = { PHASE0: phase_0_mods, diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index 8e0294bf0..1f36afd4b 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE if __name__ == "__main__": @@ -8,9 +8,12 @@ if __name__ == "__main__": 'validity', ]} altair_mods = phase_0_mods + # we have new unconditional lines in `initialize_beacon_state_from_eth1` and we want to test it + merge_mods = altair_mods all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, + MERGE: merge_mods, } run_state_test_generators(runner_name="genesis", all_mods=all_mods)