From b1aa2279838219b91c9c05a26da89b59bf0f8611 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 23 Sep 2021 22:22:34 +0300 Subject: [PATCH] Added `on_merge_block` client tests --- specs/merge/fork-choice.md | 3 +- .../merge/fork_choice/test_on_merge_block.py | 162 ++++++++++++++++++ tests/generators/fork_choice/main.py | 10 +- 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 8051ab3eb..77e279e67 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -70,8 +70,7 @@ def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool: ### `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/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..b13628bbd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py @@ -0,0 +1,162 @@ +from eth2spec.utils.ssz.ssz_typing import uint256 +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.block import ( + prepare_empty_pow_block +) + + +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 Exception("Block not found") + 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): + func() + flag.value = True + + try: + 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 + 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 + + 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 + pow_blocks = [block, parent_block] + yield 'pow_blocks', pow_blocks + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # valid + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + 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 + 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 + + + parent_block = prepare_empty_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_blocks = [parent_block] + yield 'pow_blocks', pow_blocks + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # invalid + assert spec.get_head(store) == anchor_block.state_root + + 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 + 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 + + 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) + pow_blocks = [block, parent_block] + yield 'pow_blocks', pow_blocks + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # invalid + assert spec.get_head(store) == anchor_block.state_root + + 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 + 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 + + 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) + pow_blocks = [block, parent_block] + yield 'pow_blocks', pow_blocks + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # invalid + assert spec.get_head(store) == anchor_block.state_root + + with_pow_block_patch(spec, pow_blocks, run_func) + yield 'steps', test_steps 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,