From 83598af188cd75ec0ee30ef60a21e3d419229771 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 06:29:01 +0800 Subject: [PATCH] Add `test_new_justified_is_later_than_store_justified` and fix test cases - Fix `on_tick` calls - Refactor test cases --- .../eth2spec/test/helpers/attestations.py | 32 +++ .../unittests/fork_choice/test_on_block.py | 221 +++++++++++++++--- 2 files changed, 223 insertions(+), 30 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index c92860ffa..fd0e0d880 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -261,6 +261,38 @@ def next_epoch_with_attestations(spec, ) +def state_transition_with_signed_full_block(spec, state, fill_cur_epoch, fill_prev_epoch): + # Build a block with previous attestations + block = build_empty_block_for_next_slot(spec, state) + attestations = [] + + if fill_prev_epoch: + # current epoch + slots = state.slot % spec.SLOTS_PER_EPOCH + for slot_offset in range(slots): + target_slot = state.slot - slot_offset + attestations += _get_valid_attestation_at_slot( + state, + spec, + target_slot, + ) + + if fill_prev_epoch: + # attest previous epoch + slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH + for slot_offset in range(1, slots): + target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset + attestations += _get_valid_attestation_at_slot( + state, + spec, + target_slot, + ) + + block.body.attestations = attestations + signed_block = state_transition_and_sign_block(spec, state, block) + return signed_block + + def prepare_state_with_attestations(spec, state, participation_fn=None): """ Prepare state with attestations according to the ``participation_fn``. diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 195c53839..70b5a9706 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -1,8 +1,12 @@ from copy import deepcopy from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.context import MINIMAL, with_all_phases, spec_state_test, with_presets +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, + state_transition_with_signed_full_block, +) from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ build_empty_block, sign_block from eth2spec.test.helpers.fork_choice import ( @@ -10,7 +14,7 @@ from eth2spec.test.helpers.fork_choice import ( run_on_block, apply_next_epoch_with_attestations, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to, next_slots @with_all_phases @@ -48,7 +52,7 @@ def test_on_block_finalized_skip_slots(spec, state): # Build block that includes the skipped slots up to finality in chain block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) signed_block = state_transition_and_sign_block(spec, state, block) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, signed_block) @@ -81,7 +85,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) signed_block = sign_block(spec, pre_state, block) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, signed_block, valid=False) @@ -94,10 +98,10 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) last_block = last_signed_block.message last_block_root = last_block.hash_tree_root() @@ -137,7 +141,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) @@ -148,7 +152,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): ) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) # Create new higher justified checkpoint not in branch of store's justified checkpoint just_block = build_empty_block_for_next_slot(spec, state) @@ -201,7 +205,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) @@ -212,7 +216,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): ) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) # NOTE: Mock a new higher justified checkpoint not in branch of store's justified checkpoint just_block = build_empty_block_for_next_slot(spec, state) @@ -255,18 +259,166 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): assert store.justified_checkpoint == new_justified +@with_all_phases +@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") +@spec_state_test +def test_new_justified_is_later_than_store_justified(spec, state): + """ + J: Justified + F: Finalized + fork_1_state (forked from genesis): + epoch + [0] <- [1] <- [2] <- [3] <- [4] + F J + + fork_2_state (forked from fork_1_state's epoch 2): + epoch + └──── [3] <- [4] <- [5] <- [6] + F J + + fork_3_state (forked from genesis): + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + # The 1st fork, from genesis + fork_1_state = state.copy() + # The 3rd fork, from genesis + fork_3_state = state.copy() + + # Initialization + store = get_genesis_forkchoice_store(spec, fork_1_state) + time = 0 + spec.on_tick(store, time) + + # ----- Process fork_1_state + # Skip epoch 0 + next_epoch(spec, fork_1_state) + # Fill epoch 1 with previous epoch attestations + _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + # Fork `fork_2_state` at the start of epoch 2 + fork_2_state = fork_1_state.copy() + assert spec.get_current_epoch(fork_2_state) == 2 + + # Skip epoch 2 + next_epoch(spec, fork_1_state) + # # Fill epoch 3 & 4 with previous epoch attestations + for _ in range(2): + _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint.hash_tree_root() == fork_1_state.current_justified_checkpoint.hash_tree_root() + + # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint + # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` + all_blocks = [] + + # Proposed an empty block at epoch 2, 1st slot + block = build_empty_block_for_next_slot(spec, fork_2_state) + signed_block = state_transition_and_sign_block(spec, fork_2_state, block) + all_blocks.append(signed_block.copy()) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Skip to epoch 4 + for _ in range(2): + next_epoch(spec, fork_2_state) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 4, 5th slot + # Propose a block at epoch 5, 5th slot + for _ in range(2): + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) + signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_2_state.finalized_checkpoint.epoch == 0 + assert fork_2_state.current_justified_checkpoint.epoch == 5 + + # Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED + spec.on_tick(store, store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT) + assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + + # Apply blocks of `fork_3_state` to `store` + for block in all_blocks: + if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): + spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert store.finalized_checkpoint.epoch == 0 + assert store.justified_checkpoint.epoch == 3 + assert store.best_justified_checkpoint.epoch == 5 + + # ------ fork_3_state: Create another chain to test the + # "Update justified if new justified is later than store justified" case + all_blocks = [] + for _ in range(3): + next_epoch(spec, fork_3_state) + + # epoch 3 + _, signed_blocks, fork_3_state = next_epoch_with_attestations(spec, fork_3_state, True, True) + all_blocks += signed_blocks + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # epoch 4, attest the first 5 blocks + _, blocks, fork_3_state = next_slots_with_attestations(spec, fork_3_state, 5, True, True) + all_blocks += blocks.copy() + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 5, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 6, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 3 + assert fork_3_state.current_justified_checkpoint.epoch == 4 + + # Apply blocks of `fork_3_state` to `store` + for block in all_blocks: + if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): + spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert store.finalized_checkpoint.hash_tree_root() == fork_3_state.finalized_checkpoint.hash_tree_root() + assert (store.justified_checkpoint.hash_tree_root() + == fork_3_state.current_justified_checkpoint.hash_tree_root() + != store.best_justified_checkpoint.hash_tree_root()) + assert (store.best_justified_checkpoint.hash_tree_root() + == fork_2_state.current_justified_checkpoint.hash_tree_root()) + + @with_all_phases @spec_state_test def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): """ J: Justified F: Finalized - pre-store: + state (forked from genesis): epoch [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state: + another_state (forked from genesis): [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J """ @@ -276,17 +428,22 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): time = 0 spec.on_tick(store, time) - # Process state + # ----- Process state + # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` + # Skip epoch 0 next_epoch(spec, state) + # Fill epoch 1 with previous epoch attestations _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) + # Skip epoch 2 next_epoch(spec, state) + # Fill epoch 3 & 4 with previous epoch attestations for _ in range(2): _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 @@ -294,11 +451,11 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() # Create another chain - # another_state = store.block_states[store.justified_checkpoint.root].copy() - next_epoch(spec, another_state) - spec.on_tick(store, store.time + another_state.slot * spec.config.SECONDS_PER_SLOT) - + # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` all_blocks = [] + # Skip epoch 0 + next_epoch(spec, another_state) + # Fill epoch 1 & 2 with previous + current epoch attestations for _ in range(3): _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) all_blocks += signed_blocks @@ -310,9 +467,11 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): state.current_justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() ) - pre_store_justified_checkpoint_root = store.justified_checkpoint.root + + # Apply blocks of `another_state` to `store` for block in all_blocks: + # NOTE: Do not call `on_tick` here run_on_block(spec, store, block) finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) @@ -329,14 +488,14 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): """ J: Justified F: Finalized - pre-store: + state: epoch [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state: - <- [4] <- [5] - F+J + another_state (forked from state at epoch 3): + └──── [4] <- [5] + F J """ # Initialization store = get_genesis_forkchoice_store(spec, state) @@ -345,21 +504,21 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): # Process state next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) _, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) for _ in range(2): _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 @@ -381,6 +540,8 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): pre_store_justified_checkpoint_root = store.justified_checkpoint.root for block in all_blocks: + if store.time < spec.compute_time_at_slot(another_state, block.message.slot): + spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)