diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 04843078f6..7c6ac89a54 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,7 +1,5 @@ -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, @@ -247,15 +245,6 @@ def apply_next_slots_with_attestations(spec, 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)}" diff --git a/tests/core/pyspec/eth2spec/test/helpers/pow_block.py b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py new file mode 100644 index 0000000000..58989a420b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py @@ -0,0 +1,34 @@ +from random import Random +from eth2spec.utils.ssz.ssz_typing import uint256 + + +class PowChain: + blocks = [] + + def __init__(self, blocks): + self.blocks = blocks + + def __iter__(self): + return iter(self.blocks) + + def head(self, offset=0): + assert offset <= 0 + return self.blocks[offset - 1] + + +def prepare_random_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 prepare_random_pow_chain(spec, length, rng=Random(3131)) -> PowChain: + assert length > 0 + chain = [prepare_random_pow_block(spec, rng)] + for i in range(1, length): + chain.append(prepare_random_pow_block(spec, rng)) + chain[i].parent_hash = chain[i - 1].block_hash + return PowChain(chain) 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 index 830f20f23d..e0703fdf7f 100644 --- 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 @@ -13,9 +13,11 @@ state_transition_and_sign_block, ) from eth2spec.test.helpers.fork_choice import ( - prepare_empty_pow_block, add_pow_block, ) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) from eth2spec.test.helpers.execution_payload import ( build_state_with_incomplete_transition, ) @@ -58,9 +60,9 @@ def test_all_valid(spec, state): 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 = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_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] @@ -92,7 +94,7 @@ def test_block_lookup_failed(spec, state): 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 = prepare_random_pow_block(spec) pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) pow_blocks = [pow_block] for pb in pow_blocks: @@ -122,9 +124,9 @@ def test_too_early_for_merge(spec, state): 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 = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_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] @@ -154,9 +156,9 @@ def test_too_late_for_merge(spec, state): 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 = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_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] diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py new file mode 100644 index 0000000000..f20c15e357 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py @@ -0,0 +1,44 @@ +from eth2spec.utils.ssz.ssz_typing import uint256 +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) +from eth2spec.test.context import ( + spec_state_test, + with_merge_and_later, +) + + +@with_merge_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_success_valid(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_random_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_is_valid_terminal_pow_block_fail_before_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + block = prepare_random_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_is_valid_terminal_pow_block_fail_just_after_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = prepare_random_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) 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 deleted file mode 100644 index cfd5ea0910..0000000000 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py +++ /dev/null @@ -1,143 +0,0 @@ -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_validate_merge_block.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py new file mode 100644 index 0000000000..cf4c2234a4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py @@ -0,0 +1,153 @@ +from typing import Optional +from eth2spec.utils.ssz.ssz_typing import uint256, Bytes32 +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_chain, +) +from eth2spec.test.context import ( + spec_state_test, + with_merge_and_later, + spec_configured_state_test +) + + +TERMINAL_BLOCK_HASH_CONFIG_VAR = '0x0000000000000000000000000000000000000000000000000000000000000001' +TERMINAL_BLOCK_HASH = Bytes32(TERMINAL_BLOCK_HASH_CONFIG_VAR) + + +def run_validate_merge_block(spec, pow_chain, beacon_block, valid=True): + """ + Run ``validate_merge_block`` + If ``valid == False``, run expecting ``AssertionError`` + """ + + def get_pow_block(hash: spec.Bytes32) -> Optional[spec.PowBlock]: + for block in pow_chain: + if block.block_hash == hash: + return block + return None + + get_pow_block_backup = spec.get_pow_block + + # Guido authorized everyone to do this + spec.get_pow_block = get_pow_block + assertion_error_caught = False + try: + spec.validate_merge_block(beacon_block) + except AssertionError: + assertion_error_caught = True + except Exception as e: + spec.get_pow_block = get_pow_block_backup + raise e + spec.get_pow_block = get_pow_block_backup + + if valid: + assert not assertion_error_caught + else: + assert assertion_error_caught + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_parent_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_after_terminal(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' +}) +def test_validate_merge_block_tbh_override_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # should fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' +}) +def test_validate_merge_block_fail_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' +}) +def test_validate_merge_block_terminal_block_hash_fail_activation_not_reached(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' +}) +def test_validate_merge_block_fail_activation_not_reached_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False)