diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index b8f7c4bcb..7daa51970 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -4,6 +4,7 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.utils.ssz.ssz_typing import uint256 def get_proposer_index_maybe(spec, state, slot, proposer_index=None): @@ -122,3 +123,14 @@ def get_state_and_beacon_parent_root_at_slot(spec, state, slot): previous_block_header.state_root = hash_tree_root(state) beacon_parent_root = hash_tree_root(previous_block_header) return state, beacon_parent_root + + +def prepare_empty_pow_block(spec): + return spec.PowBlock( + block_hash=spec.Hash32(), + parent_hash=spec.Hash32(), + is_processed=False, + is_valid=True, + total_difficulty=uint256(0), + difficulty=uint256(0) + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 43be965a5..d82a6a54d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,3 +1,6 @@ +from remerkleable.byte_arrays import ByteVector + + def build_empty_execution_payload(spec, state, randao_mix=None): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. @@ -64,3 +67,12 @@ def build_state_with_execution_payload_header(spec, state, execution_payload_hea pre_state.latest_execution_payload_header = execution_payload_header return pre_state + + +# damages last byte of the data by changing one bit +def screw_up_bytes(data: ByteVector): + assert len(data) > 0 + length = data.vector_length() + raw_data = data.encode_bytes() + raw_data = raw_data[0:len(raw_data) - 1] + bytes([(raw_data[len(raw_data) - 1] ^ 1)]) + return ByteVector[length](*raw_data) 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 4c68034d4..a2af7dc92 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,8 +1,12 @@ +from remerkleable.byte_arrays import Bytes32 +from eth2spec.utils.ssz.ssz_typing import uint64 + from eth2spec.test.helpers.execution_payload import ( build_empty_execution_payload, get_execution_payload_header, build_state_with_incomplete_transition, build_state_with_complete_transition, + screw_up_bytes ) from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later from eth2spec.test.helpers.state import next_slot @@ -199,3 +203,182 @@ 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_bad_randao_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) + good_randao: Bytes32 = execution_payload.random + bad_randao = screw_up_bytes(good_randao) + # still valid because randao is ignored on this stage + execution_payload.random = bad_randao + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_merge_and_later +@spec_state_test +def test_bad_randao_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) + good_randao: Bytes32 = execution_payload.random + bad_randao = screw_up_bytes(good_randao) + execution_payload.random = bad_randao + + 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_block.py b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_block.py new file mode 100644 index 000000000..f096fb138 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_block.py @@ -0,0 +1,174 @@ +from eth2spec.utils.ssz.ssz_typing import uint64, uint256 +from eth2spec.test.helpers.block import ( + prepare_empty_pow_block +) +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later + + +def create_transition_store(spec): + anchor_block = prepare_empty_pow_block(spec) + transition_store = spec.get_transition_store(anchor_block) + return transition_store + + +class BlockNotFoundException(Exception): + pass + + +def run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload, + valid=True, block_lookup_success=True): + """ + Run ``process_merge_execution_payload``, yielding: + - transition store ('transition_store') + - current block ('block') + - parent block ('parent_block') + - execution payload ('payload') + If ``valid == False``, run expecting ``AssertionError`` + If ``block_lookup_success == False``, run expecting ``BlockNotFoundException`` + """ + + yield 'transition_store', transition_store + yield 'block', block + yield 'parent_block', parent_block + yield 'payload', payload + + 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: + spec.process_merge_execution_payload(transition_store, 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_fail_invalid(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = transition_store.terminal_total_difficulty - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = transition_store.terminal_total_difficulty + + assert spec.is_valid_terminal_pow_block(transition_store, block, parent_block) + + block.is_valid = False + assert not spec.is_valid_terminal_pow_block(transition_store, block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_valid_terminal_pow_block_fail_before_terminal(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = transition_store.terminal_total_difficulty - uint256(2) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = transition_store.terminal_total_difficulty - uint256(1) + + assert not spec.is_valid_terminal_pow_block(transition_store, block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_valid_terminal_pow_block_fail_just_after_terminal(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = transition_store.terminal_total_difficulty + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = transition_store.terminal_total_difficulty + uint256(1) + + assert not spec.is_valid_terminal_pow_block(transition_store, block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_process_merge_execution_payload_success(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.block_hash = spec.Hash32(spec.hash(b'01')) + parent_block.total_difficulty = transition_store.terminal_total_difficulty - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.is_processed = True + block.total_difficulty = transition_store.terminal_total_difficulty + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + yield from run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload) + block.is_processed = False + yield from run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_process_merge_execution_payload_fail_block_lookup(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.block_hash = spec.Hash32(spec.hash(b'01')) + parent_block.total_difficulty = transition_store.terminal_total_difficulty - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.is_processed = True + block.total_difficulty = transition_store.terminal_total_difficulty + payload = spec.ExecutionPayload() + payload.parent_hash = spec.Hash32(spec.hash(b'02')) + yield from run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload, block_lookup_success=False) + + +@with_merge_and_later +@spec_state_test +def test_process_merge_execution_payload_fail_parent_block_lookup(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.block_hash = spec.Hash32(spec.hash(b'01')) + parent_block.total_difficulty = transition_store.terminal_total_difficulty - uint256(1) + block = prepare_empty_pow_block(spec) + block.parent_hash = spec.Hash32(spec.hash(b'00')) + block.is_processed = True + block.total_difficulty = transition_store.terminal_total_difficulty + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + yield from run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload, block_lookup_success=False) + + +@with_merge_and_later +@spec_state_test +def test_process_merge_execution_payload_fail_after_terminal(spec, state): + transition_store = create_transition_store(spec) + parent_block = prepare_empty_pow_block(spec) + parent_block.block_hash = spec.Hash32(spec.hash(b'01')) + parent_block.total_difficulty = transition_store.terminal_total_difficulty + block = prepare_empty_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.is_processed = True + block.total_difficulty = transition_store.terminal_total_difficulty + 1 + payload = spec.ExecutionPayload() + payload.parent_hash = block.block_hash + yield from run_process_merge_execution_payload(spec, transition_store, block, parent_block, payload, valid=False) \ No newline at end of file diff --git a/tests/core/pyspec/eth2spec/test/merge/unit/__init__.py b/tests/core/pyspec/eth2spec/test/merge/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/merge/unit/test_unit.py b/tests/core/pyspec/eth2spec/test/merge/unit/test_unit.py new file mode 100644 index 000000000..24caa03b1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unit/test_unit.py @@ -0,0 +1,121 @@ +from eth2spec.utils.ssz.ssz_typing import uint64, uint256 +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_state_with_incomplete_transition, + build_state_with_complete_transition, +) +from eth2spec.test.helpers.block import ( + prepare_empty_pow_block +) +from eth2spec.test.context import spec_state_test, expect_assertion_error, 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_merge_and_later +@spec_state_test +def test_fail_merge_block_false_false(spec, state): + state = build_state_with_complete_transition(spec, state) + execution_payload = spec.ExecutionPayload() + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert not spec.is_merge_block(state, body) + + +@with_merge_and_later +@spec_state_test +def test_fail_merge_block_false_true(spec, state): + state = build_state_with_complete_transition(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert not spec.is_merge_block(state, body) + + +@with_merge_and_later +@spec_state_test +def test_fail_merge_block_true_false(spec, state): + state = build_state_with_incomplete_transition(spec, state) + execution_payload = spec.ExecutionPayload() + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert not spec.is_merge_block(state, body) + + +@with_merge_and_later +@spec_state_test +def test_success_merge_block(spec, state): + state = build_state_with_incomplete_transition(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert spec.is_merge_block(state, body) + + +@with_merge_and_later +@spec_state_test +def test_fail_execution_enabled_false_false(spec, state): + state = build_state_with_incomplete_transition(spec, state) + execution_payload = spec.ExecutionPayload() + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert not spec.is_execution_enabled(state, body) + + +@with_merge_and_later +@spec_state_test +def test_success_execution_enabled_true_false(spec, state): + state = build_state_with_incomplete_transition(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert spec.is_execution_enabled(state, body) + + +@with_merge_and_later +@spec_state_test +def test_success_execution_enabled_false_true(spec, state): + state = build_state_with_complete_transition(spec, state) + execution_payload = spec.ExecutionPayload() + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert spec.is_execution_enabled(state, body) + + +@with_merge_and_later +@spec_state_test +def test_success_execution_enabled_true_true(spec, state): + state = build_state_with_complete_transition(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + body = spec.BeaconBlockBody() + body.execution_payload = execution_payload + assert spec.is_execution_enabled(state, body) + + +def compute_terminal_total_difficulty_reference(spec) -> uint256: + seconds_per_voting_period = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH * spec.config.SECONDS_PER_SLOT + pow_blocks_per_voting_period = seconds_per_voting_period // spec.config.SECONDS_PER_ETH1_BLOCK + pow_blocks_to_merge = spec.TARGET_SECONDS_TO_MERGE // spec.config.SECONDS_PER_ETH1_BLOCK + pow_blocks_after_anchor_block = spec.config.ETH1_FOLLOW_DISTANCE + pow_blocks_per_voting_period + pow_blocks_to_merge + return spec.config.MIN_ANCHOR_POW_BLOCK_DIFFICULTY * uint256(pow_blocks_after_anchor_block) + + +@with_merge_and_later +@spec_state_test +def test_zero_difficulty(spec, state): + anchor_pow_block = prepare_empty_pow_block(spec) + reference_td = compute_terminal_total_difficulty_reference(spec) + + assert spec.compute_terminal_total_difficulty(anchor_pow_block) == reference_td