From 42eae81013074ee191eaf0429e5575a61c714f60 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 15 Jun 2021 21:52:25 +0800 Subject: [PATCH 01/10] WIP. Rework on_block tests --- .../eth2spec/test/helpers/fork_choice.py | 7 +- .../test/phase0/fork_choice/test_on_block.py | 339 ++++++++++++++++++ tests/formats/fork_choice/README.md | 20 +- tests/generators/fork_choice/main.py | 3 +- 4 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index f6b007894..48248089b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -74,17 +74,20 @@ def on_tick_and_append_step(spec, store, time, test_steps): def run_on_block(spec, store, signed_block, test_steps, valid=True): + yield get_block_file_name(signed_block), signed_block if not valid: try: spec.on_block(store, signed_block) - except AssertionError: + test_steps.append({ + 'block': get_block_file_name(signed_block), + 'valid': True, + }) return else: assert False spec.on_block(store, signed_block) - yield get_block_file_name(signed_block), signed_block test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py new file mode 100644 index 000000000..e33c32e58 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -0,0 +1,339 @@ +from eth2spec.utils.ssz.ssz_impl import hash_tree_root + +from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets +from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + run_on_block, + tick_and_run_on_block, +) +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block + + +def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): + if test_steps is None: + test_steps = [] + + _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + for signed_block in new_signed_blocks: + block = signed_block.message + block_root = hash_tree_root(block) + store.blocks[block_root] = block + store.block_states[block_root] = post_state + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + last_signed_block = signed_block + + return post_state, store, last_signed_block + + +@with_all_phases +@spec_state_test +def test_basic(spec, state): + # Initialization + test_steps = [] + 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 + + # On receiving a block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # On receiving a block of next epoch + store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield 'steps', test_steps + + # TODO: add tests for justified_root and finalized_root + + +@with_all_phases +@with_presets([MINIMAL], reason="too slow") +@spec_state_test +def test_on_block_checkpoints(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 + + # Run for 1 epoch with full attestations + next_epoch(spec, state) + on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + last_block_root = hash_tree_root(last_signed_block.message) + assert spec.get_head(store) == last_block_root + + # Forward 1 epoch + next_epoch(spec, state) + on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + + # Mock the finalized_checkpoint and build a block on it + fin_state = store.block_states[last_block_root] + fin_state.finalized_checkpoint = ( + store.block_states[last_block_root].current_justified_checkpoint + ) + + block = build_empty_block_for_next_slot(spec, fin_state) + signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_on_block_future_block(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 + + # do not tick time + + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + run_on_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps + + +# @with_all_phases +# @spec_state_test +# def test_on_block_bad_parent_root(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# # Fail receiving block of `GENESIS_SLOT + 1` slot +# block = build_empty_block_for_next_slot(spec, state) +# transition_unsigned_block(spec, state, block) +# block.state_root = state.hash_tree_root() + +# block.parent_root = b'\x45' * 32 + +# signed_block = sign_block(spec, state, block) + +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_before_finalized(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# store.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # Fail receiving block of `GENESIS_SLOT + 1` slot +# block = build_empty_block_for_next_slot(spec, state) +# signed_block = state_transition_and_sign_block(spec, state, block) +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_finalized_skip_slots(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# store.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # 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) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# run_on_block(spec, store, signed_block, test_steps) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): +# test_steps = [] +# # Initialization +# transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1) +# block = build_empty_block_for_next_slot(spec, state) +# transition_unsigned_block(spec, state, block) +# block.state_root = state.hash_tree_root() +# store = spec.get_forkchoice_store(state, block) +# store.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # First transition through the epoch to ensure no skipped slots +# state, store, _ = apply_next_epoch_with_attestations(spec, state, store) + +# # Now build a block at later slot than finalized epoch +# # Includes finalized block in chain, but not at appropriate skip slot +# 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) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 0 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock the justified checkpoint +# just_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=just_state.current_justified_checkpoint.epoch + 1, +# root=b'\x77' * 32, +# ) +# just_state.current_justified_checkpoint = new_justified + +# block = build_empty_block_for_next_slot(spec, just_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.justified_checkpoint == new_justified + + +# @with_all_phases +# @spec_state_test +# def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 0 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock justified block in store +# just_block = build_empty_block_for_next_slot(spec, state) +# # Slot is same as justified checkpoint so does not trigger an override in the store +# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) +# store.blocks[just_block.hash_tree_root()] = just_block + +# # Step time past safe slots +# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT +# on_tick_and_append_step(spec, store, time, test_steps) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + +# previously_justified = store.justified_checkpoint + +# # Add a series of new blocks with "better" justifications +# best_justified_checkpoint = spec.Checkpoint(epoch=0) +# for i in range(3, 0, -1): +# just_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=previously_justified.epoch + i, +# root=just_block.hash_tree_root(), +# ) +# if new_justified.epoch > best_justified_checkpoint.epoch: +# best_justified_checkpoint = new_justified + +# just_state.current_justified_checkpoint = new_justified + +# block = build_empty_block_for_next_slot(spec, just_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.justified_checkpoint == previously_justified +# # ensure the best from the series was stored +# assert store.best_justified_checkpoint == best_justified_checkpoint + + +# @with_all_phases +# @spec_state_test +# def test_on_block_outside_safe_slots_but_finality(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock justified block in store +# just_block = build_empty_block_for_next_slot(spec, state) +# # Slot is same as justified checkpoint so does not trigger an override in the store +# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) +# store.blocks[just_block.hash_tree_root()] = just_block + +# # Step time past safe slots +# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT +# on_tick_and_append_step(spec, store, time, test_steps) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + +# # Mock justified and finalized update in state +# just_fin_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=store.justified_checkpoint.epoch + 1, +# root=just_block.hash_tree_root(), +# ) +# new_finalized = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 1, +# root=just_block.parent_root, +# ) +# just_fin_state.current_justified_checkpoint = new_justified +# just_fin_state.finalized_checkpoint = new_finalized + +# # Build and add block that includes the new justified/finalized info +# block = build_empty_block_for_next_slot(spec, just_fin_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) + +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.finalized_checkpoint == new_finalized +# assert store.justified_checkpoint == new_justified diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 832ce9dd1..199b93784 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -28,7 +28,11 @@ The steps to execute in sequence. There may be multiple items of the following t The parameter that is required for executing `on_tick(store, time)`. ```yaml -{ tick: int } -- to execute `on_tick(store, time)` +{ + tick: int -- to execute `on_tick(store, time)`. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` After this step, the `store` object may have been updated. @@ -38,7 +42,12 @@ After this step, the `store` object may have been updated. The parameter that is required for executing `on_attestation(store, attestation)`. ```yaml -{ attestation: string } -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. To execute `on_attestation(store, attestation)` with the given attestation. +{ + attestation: string -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. + To execute `on_attestation(store, attestation)` with the given attestation. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` The file is located in the same folder (see below). @@ -49,7 +58,12 @@ After this step, the `store` object may have been updated. The parameter that is required for executing `on_block(store, block)`. ```yaml -{ block: string } -- the name of the `block_<32-byte-root>.ssz_snappy` file. To execute `on_block(store, block)` with the given attestation. +{ + block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. + To execute `on_block(store, block)` with the given attestation. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` The file is located in the same folder (see below). diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index f162d9564..9b6325ccf 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -4,7 +4,8 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ - 'get_head', + # 'get_head', + 'on_block', ]} # No additional Altair specific finality tests, yet. altair_mods = phase_0_mods From fb2465db45161d468aa5ffd6d8cbee5f664fc36b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 18 Jun 2021 17:39:46 +0800 Subject: [PATCH 02/10] Rework `on_block` unit tests --- .../eth2spec/test/helpers/fork_choice.py | 55 +++- .../test/phase0/fork_choice/test_get_head.py | 20 +- .../test/phase0/fork_choice/test_on_block.py | 282 +++--------------- .../unittests/fork_choice/test_on_block.py | 205 +++++-------- tests/generators/fork_choice/main.py | 2 +- 5 files changed, 164 insertions(+), 400 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 48248089b..bac3d1ff5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,4 +1,5 @@ from eth_utils import encode_hex +from eth2spec.test.helpers.attestations import next_epoch_with_attestations def get_anchor_root(spec, state): @@ -18,7 +19,7 @@ def add_block_to_store(spec, store, signed_block): spec.on_block(store, signed_block) -def tick_and_run_on_block(spec, store, signed_block, test_steps=None): +def tick_and_add_block(spec, store, signed_block, test_steps=None, valid=True): if test_steps is None: test_steps = [] @@ -28,7 +29,7 @@ def tick_and_run_on_block(spec, store, signed_block, test_steps=None): if store.time < block_time: on_tick_and_append_step(spec, store, block_time, test_steps) - yield from run_on_block(spec, store, signed_block, test_steps) + yield from add_block(spec, store, signed_block, test_steps, valid=valid) def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): @@ -73,28 +74,47 @@ def on_tick_and_append_step(spec, store, time, test_steps): test_steps.append({'tick': int(time)}) -def run_on_block(spec, store, signed_block, test_steps, valid=True): - yield get_block_file_name(signed_block), signed_block +def run_on_block(spec, store, signed_block, valid=True): if not valid: try: spec.on_block(store, signed_block) except AssertionError: - test_steps.append({ - 'block': get_block_file_name(signed_block), - 'valid': True, - }) return else: assert False spec.on_block(store, signed_block) + assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message + + +def add_block(spec, store, signed_block, test_steps=None, valid=True): + if test_steps is None: + test_steps = [] + + yield get_block_file_name(signed_block), signed_block + + if not valid: + try: + run_on_block(spec, store, signed_block, valid=True) + except AssertionError: + test_steps.append({ + 'block': get_block_file_name(signed_block), + 'valid': False, + }) + return + else: + assert False + + run_on_block(spec, store, signed_block, valid=True) test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations for attestation in signed_block.message.body.attestations: spec.on_attestation(store, attestation) - assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message + block_root = signed_block.message.hash_tree_root() + assert store.blocks[block_root] == signed_block.message + assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root test_steps.append({ 'checks': { 'time': int(store.time), @@ -113,3 +133,20 @@ def get_formatted_head_output(spec, store): 'slot': int(slot), 'root': encode_hex(head), } + + +def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): + if test_steps is None: + test_steps = [] + + _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + for signed_block in new_signed_blocks: + block = signed_block.message + yield from tick_and_add_block(spec, store, signed_block, test_steps) + block_root = block.hash_tree_root() + assert store.blocks[block_root] == block + last_signed_block = signed_block + + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + + return post_state, store, last_signed_block diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index bce886931..12b261e4e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -11,12 +11,12 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.fork_choice import ( tick_and_run_on_attestation, - tick_and_run_on_block, + tick_and_add_block, get_anchor_root, get_genesis_forkchoice_store_and_block, get_formatted_head_output, on_tick_and_append_step, - run_on_block, + add_block, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -68,12 +68,12 @@ def test_chain_no_attestations(spec, state): # On receiving a block of `GENESIS_SLOT + 1` slot block_1 = build_empty_block_for_next_slot(spec, state) signed_block_1 = state_transition_and_sign_block(spec, state, block_1) - yield from tick_and_run_on_block(spec, store, signed_block_1, test_steps) + yield from tick_and_add_block(spec, store, signed_block_1, test_steps) # On receiving a block of next epoch block_2 = build_empty_block_for_next_slot(spec, state) signed_block_2 = state_transition_and_sign_block(spec, state, block_2) - yield from tick_and_run_on_block(spec, store, signed_block_2, test_steps) + yield from tick_and_add_block(spec, store, signed_block_2, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_2) test_steps.append({ @@ -107,14 +107,14 @@ def test_split_tie_breaker_no_attestations(spec, state): block_1_state = genesis_state.copy() block_1 = build_empty_block_for_next_slot(spec, block_1_state) signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1) - yield from tick_and_run_on_block(spec, store, signed_block_1, test_steps) + yield from tick_and_add_block(spec, store, signed_block_1, test_steps) # additional block at slot 1 block_2_state = genesis_state.copy() block_2 = build_empty_block_for_next_slot(spec, block_2_state) block_2.body.graffiti = b'\x42' * 32 signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2) - yield from tick_and_run_on_block(spec, store, signed_block_2, test_steps) + yield from tick_and_add_block(spec, store, signed_block_2, test_steps) highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2)) assert spec.get_head(store) == highest_root @@ -150,14 +150,14 @@ def test_shorter_chain_but_heavier_weight(spec, state): for _ in range(3): long_block = build_empty_block_for_next_slot(spec, long_state) signed_long_block = state_transition_and_sign_block(spec, long_state, long_block) - yield from tick_and_run_on_block(spec, store, signed_long_block, test_steps) + yield from tick_and_add_block(spec, store, signed_long_block, test_steps) # build short tree short_state = genesis_state.copy() short_block = build_empty_block_for_next_slot(spec, short_state) short_block.body.graffiti = b'\x42' * 32 signed_short_block = state_transition_and_sign_block(spec, short_state, short_block) - yield from tick_and_run_on_block(spec, store, signed_short_block, test_steps) + yield from tick_and_add_block(spec, store, signed_short_block, test_steps) short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True) yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps) @@ -200,7 +200,7 @@ def test_filtered_block_tree(spec, state): current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) for signed_block in signed_blocks: - yield from run_on_block(spec, store, signed_block, test_steps) + yield from add_block(spec, store, signed_block, test_steps) assert store.justified_checkpoint == state.current_justified_checkpoint @@ -247,7 +247,7 @@ def test_filtered_block_tree(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) # include rogue block and associated attestations in the store - yield from run_on_block(spec, store, signed_rogue_block, test_steps) + yield from add_block(spec, store, signed_rogue_block, test_steps) for attestation in attestations: yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index e33c32e58..e9f2fae63 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -1,38 +1,30 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets -from eth2spec.test.helpers.attestations import next_epoch_with_attestations -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + build_empty_block, + transition_unsigned_block, + sign_block, +) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store_and_block, on_tick_and_append_step, - run_on_block, - tick_and_run_on_block, + add_block, + tick_and_add_block, + apply_next_epoch_with_attestations, +) +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block - - -def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): - if test_steps is None: - test_steps = [] - - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) - for signed_block in new_signed_blocks: - block = signed_block.message - block_root = hash_tree_root(block) - store.blocks[block_root] = block - store.block_states[block_root] = post_state - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) - last_signed_block = signed_block - - return post_state, store, last_signed_block @with_all_phases @spec_state_test def test_basic(spec, state): - # Initialization test_steps = [] + # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block @@ -43,14 +35,14 @@ def test_basic(spec, state): # On receiving a block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() # On receiving a block of next epoch store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() yield 'steps', test_steps @@ -74,6 +66,7 @@ def test_on_block_checkpoints(spec, state): # Run for 1 epoch with full attestations next_epoch(spec, state) on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) last_block_root = hash_tree_root(last_signed_block.message) assert spec.get_head(store) == last_block_root @@ -90,7 +83,7 @@ def test_on_block_checkpoints(spec, state): block = build_empty_block_for_next_slot(spec, fin_state) signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() yield 'steps', test_steps @@ -107,233 +100,36 @@ def test_on_block_future_block(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - # do not tick time - + # Do NOT tick time to `GENESIS_SLOT + 1` slot # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, test_steps, valid=False) + yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps -# @with_all_phases -# @spec_state_test -# def test_on_block_bad_parent_root(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) +@with_all_phases +@spec_state_test +def test_on_block_bad_parent_root(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 -# # Fail receiving block of `GENESIS_SLOT + 1` slot -# block = build_empty_block_for_next_slot(spec, state) -# transition_unsigned_block(spec, state, block) -# block.state_root = state.hash_tree_root() + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + transition_unsigned_block(spec, state, block) + block.state_root = state.hash_tree_root() -# block.parent_root = b'\x45' * 32 + block.parent_root = b'\x45' * 32 -# signed_block = sign_block(spec, state, block) + signed_block = sign_block(spec, state, block) -# run_on_block(spec, store, signed_block, test_steps, valid=False) + yield from add_block(spec, store, signed_block, test_steps, valid=False) - -# @with_all_phases -# @spec_state_test -# def test_on_block_before_finalized(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# store.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # Fail receiving block of `GENESIS_SLOT + 1` slot -# block = build_empty_block_for_next_slot(spec, state) -# signed_block = state_transition_and_sign_block(spec, state, block) -# run_on_block(spec, store, signed_block, test_steps, valid=False) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_finalized_skip_slots(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# store.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # 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) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# run_on_block(spec, store, signed_block, test_steps) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): -# test_steps = [] -# # Initialization -# transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1) -# block = build_empty_block_for_next_slot(spec, state) -# transition_unsigned_block(spec, state, block) -# block.state_root = state.hash_tree_root() -# store = spec.get_forkchoice_store(state, block) -# store.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # First transition through the epoch to ensure no skipped slots -# state, store, _ = apply_next_epoch_with_attestations(spec, state, store) - -# # Now build a block at later slot than finalized epoch -# # Includes finalized block in chain, but not at appropriate skip slot -# 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) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# run_on_block(spec, store, signed_block, test_steps, valid=False) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 0 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock the justified checkpoint -# just_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=just_state.current_justified_checkpoint.epoch + 1, -# root=b'\x77' * 32, -# ) -# just_state.current_justified_checkpoint = new_justified - -# block = build_empty_block_for_next_slot(spec, just_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.justified_checkpoint == new_justified - - -# @with_all_phases -# @spec_state_test -# def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 0 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock justified block in store -# just_block = build_empty_block_for_next_slot(spec, state) -# # Slot is same as justified checkpoint so does not trigger an override in the store -# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) -# store.blocks[just_block.hash_tree_root()] = just_block - -# # Step time past safe slots -# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT -# on_tick_and_append_step(spec, store, time, test_steps) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - -# previously_justified = store.justified_checkpoint - -# # Add a series of new blocks with "better" justifications -# best_justified_checkpoint = spec.Checkpoint(epoch=0) -# for i in range(3, 0, -1): -# just_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=previously_justified.epoch + i, -# root=just_block.hash_tree_root(), -# ) -# if new_justified.epoch > best_justified_checkpoint.epoch: -# best_justified_checkpoint = new_justified - -# just_state.current_justified_checkpoint = new_justified - -# block = build_empty_block_for_next_slot(spec, just_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) - -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.justified_checkpoint == previously_justified -# # ensure the best from the series was stored -# assert store.best_justified_checkpoint == best_justified_checkpoint - - -# @with_all_phases -# @spec_state_test -# def test_on_block_outside_safe_slots_but_finality(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock justified block in store -# just_block = build_empty_block_for_next_slot(spec, state) -# # Slot is same as justified checkpoint so does not trigger an override in the store -# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) -# store.blocks[just_block.hash_tree_root()] = just_block - -# # Step time past safe slots -# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT -# on_tick_and_append_step(spec, store, time, test_steps) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - -# # Mock justified and finalized update in state -# just_fin_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=store.justified_checkpoint.epoch + 1, -# root=just_block.hash_tree_root(), -# ) -# new_finalized = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 1, -# root=just_block.parent_root, -# ) -# just_fin_state.current_justified_checkpoint = new_justified -# just_fin_state.finalized_checkpoint = new_finalized - -# # Build and add block that includes the new justified/finalized info -# block = build_empty_block_for_next_slot(spec, just_fin_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) - -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.finalized_checkpoint == new_finalized -# assert store.justified_checkpoint == new_justified + yield 'steps', test_steps 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 b1862d093..ce2ab95f4 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,123 +1,17 @@ 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.helpers.block import build_empty_block_for_next_slot, sign_block, transition_unsigned_block, \ - build_empty_block -from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store +from eth2spec.test.context import with_all_phases, spec_state_test, with_phases, PHASE0 +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 ( + get_genesis_forkchoice_store, + run_on_block, + apply_next_epoch_with_attestations, +) from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to -def run_on_block(spec, store, signed_block, valid=True): - if not valid: - try: - spec.on_block(store, signed_block) - except AssertionError: - return - else: - assert False - - spec.on_block(store, signed_block) - assert store.blocks[hash_tree_root(signed_block.message)] == signed_block.message - - -def apply_next_epoch_with_attestations(spec, state, store): - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) - for signed_block in new_signed_blocks: - block = signed_block.message - block_root = hash_tree_root(block) - store.blocks[block_root] = block - store.block_states[block_root] = post_state - last_signed_block = signed_block - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - return post_state, store, last_signed_block - - -@with_all_phases -@spec_state_test -def test_basic(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - assert store.time == time - - # On receiving a block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block) - - # On receiving a block of next epoch - store.time = time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - signed_block = state_transition_and_sign_block(spec, state, block) - - run_on_block(spec, store, signed_block) - - # TODO: add tests for justified_root and finalized_root - - -@with_all_phases -@spec_state_test -def test_on_block_checkpoints(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - - next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = 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) - last_block_root = hash_tree_root(last_signed_block.message) - - # Mock the finalized_checkpoint - fin_state = store.block_states[last_block_root] - fin_state.finalized_checkpoint = ( - store.block_states[last_block_root].current_justified_checkpoint - ) - - block = build_empty_block_for_next_slot(spec, fin_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(fin_state), block) - run_on_block(spec, store, signed_block) - - -@with_all_phases -@spec_state_test -def test_on_block_future_block(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # do not tick time - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, False) - - -@with_all_phases -@spec_state_test -def test_on_block_bad_parent_root(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - transition_unsigned_block(spec, state, block) - block.state_root = state.hash_tree_root() - - block.parent_root = b'\x45' * 32 - - signed_block = sign_block(spec, state, block) - - run_on_block(spec, store, signed_block, False) - - @with_all_phases @spec_state_test def test_on_block_before_finalized(spec, state): @@ -134,7 +28,7 @@ def test_on_block_before_finalized(spec, state): # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, False) + run_on_block(spec, store, signed_block, valid=False) @with_all_phases @@ -166,23 +60,31 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() store = spec.get_forkchoice_store(state, block) - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - # First transition through the epoch to ensure no skipped slots - state, store, _ = apply_next_epoch_with_attestations(spec, state, store) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + spec.on_tick(store, current_time) + assert store.time == current_time + + pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch + + # Finalized + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store) + assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 # Now build a block at later slot than finalized epoch # Includes finalized block in chain, but not at appropriate skip slot - 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) + pre_state = store.block_states[block.hash_tree_root()] + block = build_empty_block(spec, + state=pre_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) - run_on_block(spec, store, signed_block, False) + run_on_block(spec, store, signed_block, valid=False) -@with_all_phases +@with_phases([PHASE0]) @spec_state_test def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization @@ -192,21 +94,33 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + 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) - last_block_root = hash_tree_root(last_signed_block.message) + last_block = last_signed_block.message + last_block_root = last_block.hash_tree_root() - # Mock the justified checkpoint + # NOTE: Mock the justified checkpoint just_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=just_state.current_justified_checkpoint.epoch + 1, root=b'\x77' * 32, ) - just_state.current_justified_checkpoint = new_justified + just_state.current_justified_checkpoint = new_justified # Mutate `store` + + assert store.block_states[last_block_root].hash_tree_root() == just_state.hash_tree_root() block = build_empty_block_for_next_slot(spec, just_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + + # NOTE: Mock store so that the modified state could be accessed + parent_block = last_signed_block.message.copy() + parent_block.state_root = just_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + + signed_block = state_transition_and_sign_block(spec, just_state.copy(), block) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED run_on_block(spec, store, signed_block) @@ -223,10 +137,10 @@ 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) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + 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) - # Mock fictitious justified checkpoint in store + # NOTE: Mock fictitious justified checkpoint in store store.justified_checkpoint = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") @@ -248,6 +162,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Add a series of new blocks with "better" justifications best_justified_checkpoint = spec.Checkpoint(epoch=0) for i in range(3, 0, -1): + # Mutate store just_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=previously_justified.epoch + i, @@ -261,6 +176,14 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): block = build_empty_block_for_next_slot(spec, just_state) signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + # NOTE: Mock store so that the modified state could be accessed + parent_block = store.blocks[last_block_root].copy() + parent_block.state_root = just_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + run_on_block(spec, store, signed_block) assert store.justified_checkpoint == previously_justified @@ -273,15 +196,15 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): def test_on_block_outside_safe_slots_but_finality(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 + time = 0 spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + 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) - # Mock fictitious justified checkpoint in store + # NOTE: Mock fictitious justified checkpoint in store store.justified_checkpoint = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") @@ -290,7 +213,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) - # Create new higher justified checkpoint not in branch of store's justified checkpoint + # 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) store.blocks[just_block.hash_tree_root()] = just_block @@ -298,7 +221,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - # Mock justified and finalized update in state + # NOTE: Mock justified and finalized update in state just_fin_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(just_block.slot) + 1, @@ -317,6 +240,14 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): block = build_empty_block_for_next_slot(spec, just_fin_state) signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) + # NOTE: Mock store so that the modified state could be accessed + parent_block = last_signed_block.message.copy() + parent_block.state_root = just_fin_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_fin_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + run_on_block(spec, store, signed_block) assert store.finalized_checkpoint == new_finalized diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 9b6325ccf..0ebdbf2c0 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -4,7 +4,7 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ - # 'get_head', + 'get_head', 'on_block', ]} # No additional Altair specific finality tests, yet. From 2445fe5a7684fd2607129f3feea390b6d95cc264 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 18 Jun 2021 19:14:03 +0800 Subject: [PATCH 03/10] Add new test cases - `test_new_finalized_slot_is_not_justified_checkpoint_ancestor` - `test_new_finalized_slot_is_justified_checkpoint_ancestor` --- .../unittests/fork_choice/test_on_block.py | 141 +++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) 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 ce2ab95f4..195c53839 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,7 +1,8 @@ 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, with_phases, PHASE0 +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import next_epoch_with_attestations 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 ( @@ -84,7 +85,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): run_on_block(spec, store, signed_block, valid=False) -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization @@ -252,3 +253,139 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): assert store.finalized_checkpoint == new_finalized assert store.justified_checkpoint == new_justified + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + pre-store: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state: + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + another_state = state.copy() + # Initialization + store = get_genesis_forkchoice_store(spec, state) + time = 0 + spec.on_tick(store, time) + + # Process state + next_epoch(spec, state) + _, 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) + run_on_block(spec, store, block) + next_epoch(spec, state) + 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) + run_on_block(spec, store, block) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + 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) + + all_blocks = [] + for _ in range(3): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 2 + assert another_state.current_justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() + assert ( + state.current_justified_checkpoint.hash_tree_root() + != another_state.current_justified_checkpoint.hash_tree_root() + ) + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + run_on_block(spec, store, block) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot != store.finalized_checkpoint.root + + assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() + assert store.justified_checkpoint.hash_tree_root() == another_state.current_justified_checkpoint.hash_tree_root() + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + pre-store: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state: + <- [4] <- [5] + F+J + """ + # Initialization + store = get_genesis_forkchoice_store(spec, state) + time = 0 + spec.on_tick(store, time) + + # Process state + next_epoch(spec, state) + spec.on_tick(store, store.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) + 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) + run_on_block(spec, store, block) + next_epoch(spec, state) + spec.on_tick(store, store.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) + run_on_block(spec, store, block) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() + + # Create another chain + # Forking from epoch 3 + all_blocks = [] + slot = spec.compute_start_slot_at_epoch(3) + block_root = spec.get_block_root_at_slot(state, slot) + another_state = store.block_states[block_root].copy() + for _ in range(2): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 3 + assert another_state.current_justified_checkpoint.epoch == 4 + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + run_on_block(spec, store, block) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot == store.finalized_checkpoint.root + + assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() + assert store.justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() From 7a9ae5733584e3fde5b304d7c606a2264c26f5d8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 02:13:02 +0800 Subject: [PATCH 04/10] Minor formatting. `True` -> `true`, `False` -> `false` --- tests/formats/fork_choice/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 199b93784..03a1a6e9c 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -30,8 +30,8 @@ The parameter that is required for executing `on_tick(store, time)`. ```yaml { tick: int -- to execute `on_tick(store, time)`. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` @@ -45,8 +45,8 @@ The parameter that is required for executing `on_attestation(store, attestation) { attestation: string -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. To execute `on_attestation(store, attestation)` with the given attestation. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` The file is located in the same folder (see below). @@ -61,8 +61,8 @@ The parameter that is required for executing `on_block(store, block)`. { block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. To execute `on_block(store, block)` with the given attestation. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` The file is located in the same folder (see below). From 83598af188cd75ec0ee30ef60a21e3d419229771 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 06:29:01 +0800 Subject: [PATCH 05/10] 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) From 88be6cdf603b334d2d1f11daee3063c6ddbc4b5c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 22 Jun 2021 20:55:52 +0800 Subject: [PATCH 06/10] Apply Danny's suggestions from code review Co-authored-by: Danny Ryan --- .../test/phase0/unittests/fork_choice/test_on_block.py | 8 ++++++-- tests/formats/fork_choice/README.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) 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 70b5a9706..5ca362930 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 @@ -7,8 +7,12 @@ from eth2spec.test.helpers.attestations import ( 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.block import ( + build_empty_block, + build_empty_block_for_next_slot, + sign_block, + transition_unsigned_block, +) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store, run_on_block, diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 03a1a6e9c..90c0aafc7 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -29,7 +29,7 @@ The parameter that is required for executing `on_tick(store, time)`. ```yaml { - tick: int -- to execute `on_tick(store, time)`. + tick: int -- to execute `on_tick(store, time)`. valid: bool -- optional, default to `true`. If it's `false`, this execution step is expected to be invalid. } From 69a645aa8b428dcd47836e4efaa1d9b408853995 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Jun 2021 03:38:17 +0800 Subject: [PATCH 07/10] Apply PR feedback --- .../unittests/fork_choice/test_on_block.py | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) 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 5ca362930..2f945f06c 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 @@ -26,8 +26,6 @@ from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_bl def test_on_block_before_finalized(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) store.finalized_checkpoint = spec.Checkpoint( epoch=store.finalized_checkpoint.epoch + 2, @@ -45,9 +43,8 @@ def test_on_block_before_finalized(spec, state): def test_on_block_finalized_skip_slots(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) + # Create a finalized chain store.finalized_checkpoint = spec.Checkpoint( epoch=store.finalized_checkpoint.epoch + 2, root=store.finalized_checkpoint.root @@ -98,16 +95,13 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) 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.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block = last_signed_block.message - last_block_root = last_block.hash_tree_root() + last_block_root = last_signed_block.message.hash_tree_root() # NOTE: Mock the justified checkpoint just_state = store.block_states[last_block_root] @@ -141,8 +135,6 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) @@ -205,8 +197,6 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): def test_on_block_outside_safe_slots_but_finality(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) @@ -291,8 +281,6 @@ def test_new_justified_is_later_than_store_justified(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, fork_1_state) - time = 0 - spec.on_tick(store, time) # ----- Process fork_1_state # Skip epoch 0 @@ -422,20 +410,21 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state (forked from genesis): - [0] <- [1] <- [2] <- [3] <- [4] <- [5] + another_state (forked from epoch 0): + └──── [1] <- [2] <- [3] <- [4] <- [5] F J """ - another_state = state.copy() # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) # ----- Process state # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` # Skip epoch 0 next_epoch(spec, state) + + # Forking another_state + another_state = state.copy() + # Fill epoch 1 with previous epoch attestations _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: @@ -457,8 +446,6 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): # Create another chain # 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) @@ -503,8 +490,6 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): """ # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) # Process state next_epoch(spec, state) From f55afefe9025c103e1fc9f6a193891514b8f8c0e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Jun 2021 04:58:27 +0800 Subject: [PATCH 08/10] Move more tests from unittests to testgen tests - `test_on_block_before_finalized` - `test_on_block_finalized_skip_slots` - `test_on_block_finalized_skip_slots_not_in_skip_chain` --- .../test/phase0/fork_choice/test_on_block.py | 110 +++++++++++++++++- .../unittests/fork_choice/test_on_block.py | 74 +----------- 2 files changed, 106 insertions(+), 78 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index e9f2fae63..c54a7a7be 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -17,6 +17,7 @@ from eth2spec.test.helpers.fork_choice import ( from eth2spec.test.helpers.state import ( next_epoch, state_transition_and_sign_block, + transition_to, ) @@ -51,8 +52,8 @@ def test_basic(spec, state): @with_all_phases -@with_presets([MINIMAL], reason="too slow") @spec_state_test +@with_presets([MINIMAL], reason="too slow") def test_on_block_checkpoints(spec, state): test_steps = [] # Initialization @@ -76,10 +77,8 @@ def test_on_block_checkpoints(spec, state): on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) # Mock the finalized_checkpoint and build a block on it - fin_state = store.block_states[last_block_root] - fin_state.finalized_checkpoint = ( - store.block_states[last_block_root].current_justified_checkpoint - ) + fin_state = store.block_states[last_block_root].copy() + fin_state.finalized_checkpoint = store.block_states[last_block_root].current_justified_checkpoint.copy() block = build_empty_block_for_next_slot(spec, fin_state) signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) @@ -133,3 +132,104 @@ def test_on_block_bad_parent_root(spec, state): yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_before_finalized(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 + + # Fork + another_state = state.copy() + + # Create a finalized chain + for _ in range(4): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == 2 + + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, another_state) + block.body.graffiti = b'\x12' * 32 + signed_block = state_transition_and_sign_block(spec, another_state, block) + assert signed_block.message.hash_tree_root() not in store.blocks + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_finalized_skip_slots(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 + + # Create a finalized chain + for _ in range(4): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == 2 + + # Another chain + another_state = store.block_states[store.finalized_checkpoint.root].copy() + # Build block that includes the skipped slots up to finality in chain + block = build_empty_block(spec, + another_state, + spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) + block.body.graffiti = b'\x12' * 32 + signed_block = state_transition_and_sign_block(spec, another_state, block) + + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): + test_steps = [] + # Initialization + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1) + block = build_empty_block_for_next_slot(spec, state) + transition_unsigned_block(spec, state, block) + block.state_root = state.hash_tree_root() + store = spec.get_forkchoice_store(state, block) + yield 'anchor_state', state + yield 'anchor_block', 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 + + pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch + + # Finalized + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 + + # Now build a block at later slot than finalized epoch + # Includes finalized block in chain, but not at appropriate skip slot + pre_state = store.block_states[block.hash_tree_root()].copy() + block = build_empty_block(spec, + state=pre_state, + slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) + block.body.graffiti = b'\x12' * 32 + signed_block = sign_block(spec, pre_state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps 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 2f945f06c..18e23ade2 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 @@ -8,86 +8,14 @@ from eth2spec.test.helpers.attestations import ( state_transition_with_signed_full_block, ) from eth2spec.test.helpers.block import ( - build_empty_block, build_empty_block_for_next_slot, - sign_block, - transition_unsigned_block, ) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store, run_on_block, apply_next_epoch_with_attestations, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to, next_slots - - -@with_all_phases -@spec_state_test -def test_on_block_before_finalized(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, valid=False) - - -@with_all_phases -@spec_state_test -def test_on_block_finalized_skip_slots(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # Create a finalized chain - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - - # 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.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, signed_block) - - -@with_all_phases -@spec_state_test -def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): - # Initialization - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1) - block = build_empty_block_for_next_slot(spec, state) - transition_unsigned_block(spec, state, block) - block.state_root = state.hash_tree_root() - store = spec.get_forkchoice_store(state, block) - - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - spec.on_tick(store, current_time) - assert store.time == current_time - - pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch - - # Finalized - for _ in range(3): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store) - assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 - - # Now build a block at later slot than finalized epoch - # Includes finalized block in chain, but not at appropriate skip slot - pre_state = store.block_states[block.hash_tree_root()] - block = build_empty_block(spec, - state=pre_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.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, signed_block, valid=False) +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, next_slots @with_all_phases From 29a93f62853bb7f09e6448acd2e74fe2167dc667 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Jun 2021 18:48:30 +0800 Subject: [PATCH 09/10] Move more unit tests to test vectors --- .../eth2spec/test/helpers/attestations.py | 73 ++- .../eth2spec/test/helpers/fork_choice.py | 104 +++- .../test/phase0/fork_choice/test_on_block.py | 466 +++++++++++++++++- .../unittests/fork_choice/test_on_block.py | 412 +--------------- 4 files changed, 608 insertions(+), 447 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index fd0e0d880..ffd484ecd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -217,30 +217,13 @@ def next_slots_with_attestations(spec, post_state = state.copy() signed_blocks = [] for _ in range(slot_count): - block = build_empty_block_for_next_slot(spec, post_state) - if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: - slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 - if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): - attestations = _get_valid_attestation_at_slot( - post_state, - spec, - slot_to_attest, - participation_fn=participation_fn - ) - for attestation in attestations: - block.body.attestations.append(attestation) - if fill_prev_epoch: - slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 - attestations = _get_valid_attestation_at_slot( - post_state, - spec, - slot_to_attest, - participation_fn=participation_fn - ) - for attestation in attestations: - block.body.attestations.append(attestation) - - signed_block = state_transition_and_sign_block(spec, post_state, block) + signed_block = state_transition_with_full_block( + spec, + post_state, + fill_cur_epoch, + fill_prev_epoch, + participation_fn, + ) signed_blocks.append(signed_block) return state, signed_blocks, post_state @@ -249,7 +232,8 @@ def next_slots_with_attestations(spec, def next_epoch_with_attestations(spec, state, fill_cur_epoch, - fill_prev_epoch): + fill_prev_epoch, + participation_fn=None): assert state.slot % spec.SLOTS_PER_EPOCH == 0 return next_slots_with_attestations( @@ -258,15 +242,50 @@ def next_epoch_with_attestations(spec, spec.SLOTS_PER_EPOCH, fill_cur_epoch, fill_prev_epoch, + participation_fn, ) -def state_transition_with_signed_full_block(spec, state, fill_cur_epoch, fill_prev_epoch): +def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None): + """ + Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch. + """ + block = build_empty_block_for_next_slot(spec, state) + if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: + slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 + if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)): + attestations = _get_valid_attestation_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(attestation) + if fill_prev_epoch: + slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1 + attestations = _get_valid_attestation_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(attestation) + + signed_block = state_transition_and_sign_block(spec, state, block) + return signed_block + + +def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, fill_prev_epoch): + """ + Build and apply a block with attestions at all valid slots of current epoch and/or previous epoch. + """ # Build a block with previous attestations block = build_empty_block_for_next_slot(spec, state) attestations = [] - if fill_prev_epoch: + if fill_cur_epoch: # current epoch slots = state.slot % spec.SLOTS_PER_EPOCH for slot_offset in range(slots): diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index bac3d1ff5..ec5793af5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,5 +1,8 @@ from eth_utils import encode_hex -from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, +) def get_anchor_root(spec, state): @@ -19,23 +22,20 @@ 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=None, valid=True): - if test_steps is None: - test_steps = [] - +def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=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 store.time < block_time: on_tick_and_append_step(spec, store, block_time, test_steps) - yield from add_block(spec, store, signed_block, test_steps, valid=valid) + post_state = yield from add_block( + spec, store, signed_block, test_steps, valid=valid, allow_invalid_attestations=allow_invalid_attestations) + + return post_state -def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): - if test_steps is None: - test_steps = [] - +def tick_and_run_on_attestation(spec, store, attestation, test_steps): parent_block = store.blocks[attestation.data.beacon_block_root] pre_state = store.block_states[spec.hash_tree_root(parent_block)] block_time = pre_state.genesis_time + parent_block.slot * spec.config.SECONDS_PER_SLOT @@ -50,6 +50,37 @@ def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): test_steps.append({'attestation': get_attestation_file_name(attestation)}) +def add_attestation(spec, store, attestation, test_steps, valid=True): + yield get_attestation_file_name(attestation), attestation + + if not valid: + try: + run_on_attestation(spec, store, attestation, valid=True) + except AssertionError: + test_steps.append({ + 'attestation': get_attestation_file_name(attestation), + 'valid': False, + }) + return + else: + assert False + + run_on_attestation(spec, store, attestation, valid=True) + test_steps.append({'attestation': get_attestation_file_name(attestation)}) + + +def run_on_attestation(spec, store, attestation, valid=True): + if not valid: + try: + spec.on_attestation(store, attestation) + except AssertionError: + return + else: + assert False + + spec.on_attestation(store, attestation) + + def get_genesis_forkchoice_store(spec, genesis_state): store, _ = get_genesis_forkchoice_store_and_block(spec, genesis_state) return store @@ -87,10 +118,10 @@ 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=None, valid=True): - if test_steps is None: - test_steps = [] - +def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): + """ + Run on_block and on_attestation + """ yield get_block_file_name(signed_block), signed_block if not valid: @@ -109,8 +140,14 @@ def add_block(spec, store, signed_block, test_steps=None, valid=True): test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations - for attestation in signed_block.message.body.attestations: - spec.on_attestation(store, attestation) + try: + for attestation in signed_block.message.body.attestations: + run_on_attestation(spec, store, attestation, valid=True) + except AssertionError: + if allow_invalid_attestations: + pass + else: + raise block_root = signed_block.message.hash_tree_root() assert store.blocks[block_root] == signed_block.message @@ -125,6 +162,8 @@ def add_block(spec, store, signed_block, test_steps=None, valid=True): } }) + return store.block_states[signed_block.message.hash_tree_root()] + def get_formatted_head_output(spec, store): head = spec.get_head(store) @@ -135,11 +174,40 @@ def get_formatted_head_output(spec, store): } -def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): +def apply_next_epoch_with_attestations(spec, + state, + store, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=None, + test_steps=None): if test_steps is None: test_steps = [] - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + _, new_signed_blocks, post_state = next_epoch_with_attestations( + spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) + for signed_block in new_signed_blocks: + block = signed_block.message + yield from tick_and_add_block(spec, store, signed_block, test_steps) + block_root = block.hash_tree_root() + assert store.blocks[block_root] == block + last_signed_block = signed_block + + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + + return post_state, store, last_signed_block + + +def apply_next_slots_with_attestations(spec, + state, + store, + slots, + fill_cur_epoch, + fill_prev_epoch, + test_steps, + participation_fn=None): + _, new_signed_blocks, post_state = next_slots_with_attestations( + spec, state, slots, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) for signed_block in new_signed_blocks: block = signed_block.message yield from tick_and_add_block(spec, store, signed_block, test_steps) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index c54a7a7be..c3374d4f8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -1,6 +1,13 @@ -from eth2spec.utils.ssz.ssz_impl import hash_tree_root +import random +from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, + state_transition_with_full_block, + state_transition_with_full_attestations_block, +) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, build_empty_block, @@ -13,14 +20,27 @@ from eth2spec.test.helpers.fork_choice import ( add_block, tick_and_add_block, apply_next_epoch_with_attestations, + apply_next_slots_with_attestations, ) from eth2spec.test.helpers.state import ( next_epoch, + next_slots, state_transition_and_sign_block, transition_to, ) +rng = random.Random(2020) + + +def _drop_random_one_third(_slot, _index, indices): + committee_len = len(indices) + assert committee_len >= 3 + filter_len = committee_len // 3 + participant_count = committee_len - filter_len + return rng.sample(indices, participant_count) + + @with_all_phases @spec_state_test def test_basic(spec, state): @@ -68,7 +88,8 @@ def test_on_block_checkpoints(spec, state): next_epoch(spec, state) on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) last_block_root = hash_tree_root(last_signed_block.message) assert spec.get_head(store) == last_block_root @@ -152,7 +173,8 @@ def test_on_block_before_finalized(spec, state): # Create a finalized chain for _ in range(4): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) assert store.finalized_checkpoint.epoch == 2 # Fail receiving block of `GENESIS_SLOT + 1` slot @@ -180,7 +202,8 @@ def test_on_block_finalized_skip_slots(spec, state): # Create a finalized chain for _ in range(4): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) assert store.finalized_checkpoint.epoch == 2 # Another chain @@ -219,7 +242,8 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): # Finalized for _ in range(3): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 # Now build a block at later slot than finalized epoch @@ -233,3 +257,435 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="mainnet config requires too many pre-generated public/private keys") +def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): + """ + Test `should_update_justified_checkpoint`: + compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED + """ + 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 + + # Skip epoch 0 & 1 + for _ in range(2): + next_epoch(spec, state) + # Fill epoch 2 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 + # Skip epoch 3 & 4 + for _ in range(2): + next_epoch(spec, state) + # Epoch 5: Attest current epoch + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, participation_fn=_drop_random_one_third, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == 2 + assert store.justified_checkpoint.epoch == 2 + assert state.current_justified_checkpoint == store.justified_checkpoint + + # Skip epoch 6 + next_epoch(spec, state) + + pre_state = state.copy() + + # Build a block to justify epoch 5 + signed_block = state_transition_with_full_block(spec, state, True, True) + assert state.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == 5 + assert state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + # Run on_block + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # Ensure justified_checkpoint has been changed but finality is unchanged + assert store.justified_checkpoint.epoch == 5 + assert store.justified_checkpoint == state.current_justified_checkpoint + assert store.finalized_checkpoint.epoch == pre_state.finalized_checkpoint.epoch == 0 + + yield 'steps', test_steps + + +@with_all_phases +@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") +@spec_state_test +def test_on_block_outside_safe_slots_but_finality(spec, state): + """ + Test `should_update_justified_checkpoint` case + - compute_slots_since_epoch_start(get_current_slot(store)) > SAFE_SLOTS_TO_UPDATE_JUSTIFIED + - new_justified_checkpoint and store.justified_checkpoint.root are NOT conflicting + + Thus should_update_justified_checkpoint returns True. + + Part of this script is similar to `test_new_justified_is_later_than_store_justified`. + """ + 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 + + # Skip epoch 0 + next_epoch(spec, state) + # Fill epoch 1 to 3, attest current epoch + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Skip epoch 4-6 + for _ in range(3): + next_epoch(spec, state) + + # epoch 7 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == 7 + + # epoch 8, attest the first 5 blocks + state, store, _ = yield from apply_next_slots_with_attestations( + spec, state, store, 5, True, True, test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 + + # Propose a block at epoch 9, 5th slot + next_epoch(spec, state) + next_slots(spec, state, 4) + signed_block = state_transition_with_full_attestations_block(spec, state, True, True) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 + + # Propose an empty block at epoch 10, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot + # This block would trigger justification and finality updates on store + next_epoch(spec, state) + next_slots(spec, state, 4) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + assert state.finalized_checkpoint.epoch == 7 + assert state.current_justified_checkpoint.epoch == 8 + # Step time past safe slots and run on_block + if store.time < spec.compute_time_at_slot(state, signed_block.message.slot): + time = store.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + yield from add_block(spec, store, signed_block, test_steps) + + # Ensure justified_checkpoint finality has been changed + assert store.finalized_checkpoint.epoch == 7 + assert store.finalized_checkpoint == state.finalized_checkpoint + assert store.justified_checkpoint.epoch == 8 + assert store.justified_checkpoint == state.current_justified_checkpoint + + yield 'steps', test_steps + + +@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() + + 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 + + # ----- Process fork_1_state + # Skip epoch 0 + next_epoch(spec, fork_1_state) + # Fill epoch 1 with previous epoch attestations + fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( + spec, fork_1_state, store, False, True, test_steps=test_steps) + + # 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): + fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( + spec, fork_1_state, store, False, True, test_steps=test_steps) + + 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 == fork_1_state.current_justified_checkpoint + + # ------ 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) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + 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_full_attestations_block(spec, fork_2_state, True, True) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + 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_full_attestations_block(spec, fork_2_state, True, True) + 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 + # Run on_block + yield from add_block(spec, store, signed_block, test_steps) + 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_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_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 + + # FIXME: pending on the `on_block`, `on_attestation` fix + # # 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) + # # valid_attestations=False because the attestations are outdated (older than previous epoch) + # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=False) + + # assert store.finalized_checkpoint == fork_3_state.finalized_checkpoint + # assert (store.justified_checkpoint + # == fork_3_state.current_justified_checkpoint + # != store.best_justified_checkpoint) + # assert (store.best_justified_checkpoint + # == fork_2_state.current_justified_checkpoint) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + state (forked from genesis): + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state (forked from epoch 0): + └──── [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + 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 + + # ----- Process state + # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` + # Skip epoch 0 + next_epoch(spec, state) + + # Forking another_state + another_state = state.copy() + + # Fill epoch 1 with previous epoch attestations + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + # Skip epoch 2 + next_epoch(spec, state) + # Fill epoch 3 & 4 with previous epoch attestations + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint == state.current_justified_checkpoint + + # Create another chain + # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` + all_blocks = [] + # 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 + + assert another_state.finalized_checkpoint.epoch == 2 + assert another_state.current_justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() + assert ( + state.current_justified_checkpoint.hash_tree_root() + != another_state.current_justified_checkpoint.hash_tree_root() + ) + # pre_store_justified_checkpoint_root = store.justified_checkpoint.root + + # FIXME: pending on the `on_block`, `on_attestation` fix + # # Apply blocks of `another_state` to `store` + # for block in all_blocks: + # # NOTE: Do not call `on_tick` here + # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + + # finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + # ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + # assert ancestor_at_finalized_slot != store.finalized_checkpoint.root + + # assert store.finalized_checkpoint == another_state.finalized_checkpoint + # assert store.justified_checkpoint == another_state.current_justified_checkpoint + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + state: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state (forked from state at epoch 3): + └──── [4] <- [5] + F J + """ + 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 + + # Process state + next_epoch(spec, state) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) + + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + next_epoch(spec, state) + + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.justified_checkpoint == state.current_justified_checkpoint + + # Create another chain + # Forking from epoch 3 + all_blocks = [] + slot = spec.compute_start_slot_at_epoch(3) + block_root = spec.get_block_root_at_slot(state, slot) + another_state = store.block_states[block_root].copy() + for _ in range(2): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 3 + assert another_state.current_justified_checkpoint.epoch == 4 + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + # FIXME: Once `on_block` and `on_attestation` logic is fixed, + # fix test case and remove allow_invalid_attestations flag + yield from tick_and_add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot == store.finalized_checkpoint.root + + assert store.finalized_checkpoint == another_state.finalized_checkpoint + assert store.justified_checkpoint != another_state.current_justified_checkpoint + + yield 'steps', test_steps 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 18e23ade2..92382c884 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,11 +1,9 @@ from copy import deepcopy -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -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.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.context import ( + spec_state_test, + with_all_phases, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, @@ -15,58 +13,25 @@ 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, next_slots - - -@with_all_phases -@spec_state_test -def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - next_epoch(spec, state) - 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.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block_root = last_signed_block.message.hash_tree_root() - - # NOTE: Mock the justified checkpoint - just_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=just_state.current_justified_checkpoint.epoch + 1, - root=b'\x77' * 32, - ) - just_state.current_justified_checkpoint = new_justified # Mutate `store` - - assert store.block_states[last_block_root].hash_tree_root() == just_state.hash_tree_root() - - block = build_empty_block_for_next_slot(spec, just_state) - - # NOTE: Mock store so that the modified state could be accessed - parent_block = last_signed_block.message.copy() - parent_block.state_root = just_state.hash_tree_root() - store.blocks[block.parent_root] = parent_block - store.block_states[block.parent_root] = just_state.copy() - assert block.parent_root in store.blocks.keys() - assert block.parent_root in store.block_states.keys() - - signed_block = state_transition_and_sign_block(spec, just_state.copy(), block) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - run_on_block(spec, store, signed_block) - - assert store.justified_checkpoint == new_justified +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, +) @with_all_phases @spec_state_test def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): + """ + NOTE: test_new_justified_is_later_than_store_justified also tests best_justified_checkpoint + """ # Initialization store = get_genesis_forkchoice_store(spec, state) next_epoch(spec, state) 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) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False) last_block_root = hash_tree_root(last_signed_block.message) # NOTE: Mock fictitious justified checkpoint in store @@ -86,6 +51,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + previously_finalized = store.finalized_checkpoint previously_justified = store.justified_checkpoint # Add a series of new blocks with "better" justifications @@ -115,355 +81,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): run_on_block(spec, store, signed_block) + assert store.finalized_checkpoint == previously_finalized assert store.justified_checkpoint == previously_justified # ensure the best from the series was stored assert store.best_justified_checkpoint == best_justified_checkpoint - - -@with_all_phases -@spec_state_test -def test_on_block_outside_safe_slots_but_finality(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - next_epoch(spec, state) - 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) - - # NOTE: Mock fictitious justified checkpoint in store - store.justified_checkpoint = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), - root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") - ) - - next_epoch(spec, state) - 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) - store.blocks[just_block.hash_tree_root()] = just_block - - # Step time past safe slots - spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - - # NOTE: Mock justified and finalized update in state - just_fin_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(just_block.slot) + 1, - root=just_block.hash_tree_root(), - ) - assert new_justified.epoch > store.justified_checkpoint.epoch - new_finalized = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(just_block.slot), - root=just_block.parent_root, - ) - assert new_finalized.epoch > store.finalized_checkpoint.epoch - just_fin_state.current_justified_checkpoint = new_justified - just_fin_state.finalized_checkpoint = new_finalized - - # Build and add block that includes the new justified/finalized info - block = build_empty_block_for_next_slot(spec, just_fin_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) - - # NOTE: Mock store so that the modified state could be accessed - parent_block = last_signed_block.message.copy() - parent_block.state_root = just_fin_state.hash_tree_root() - store.blocks[block.parent_root] = parent_block - store.block_states[block.parent_root] = just_fin_state.copy() - assert block.parent_root in store.blocks.keys() - assert block.parent_root in store.block_states.keys() - - run_on_block(spec, store, signed_block) - - assert store.finalized_checkpoint == new_finalized - 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) - - # ----- 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 - state (forked from genesis): - epoch - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - - another_state (forked from epoch 0): - └──── [1] <- [2] <- [3] <- [4] <- [5] - F J - """ - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # ----- Process state - # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` - # Skip epoch 0 - next_epoch(spec, state) - - # Forking another_state - another_state = state.copy() - - # 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.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.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 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 - assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() - - # Create another chain - # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` - all_blocks = [] - # 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 - - assert another_state.finalized_checkpoint.epoch == 2 - assert another_state.current_justified_checkpoint.epoch == 3 - assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() - assert ( - 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) - ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) - assert ancestor_at_finalized_slot != store.finalized_checkpoint.root - - assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() - assert store.justified_checkpoint.hash_tree_root() == another_state.current_justified_checkpoint.hash_tree_root() - - -@with_all_phases -@spec_state_test -def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): - """ - J: Justified - F: Finalized - state: - epoch - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - - another_state (forked from state at epoch 3): - └──── [4] <- [5] - F J - """ - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # Process state - next_epoch(spec, state) - 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.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.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - next_epoch(spec, state) - 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.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 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 - assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() - - # Create another chain - # Forking from epoch 3 - all_blocks = [] - slot = spec.compute_start_slot_at_epoch(3) - block_root = spec.get_block_root_at_slot(state, slot) - another_state = store.block_states[block_root].copy() - for _ in range(2): - _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) - all_blocks += signed_blocks - - assert another_state.finalized_checkpoint.epoch == 3 - assert another_state.current_justified_checkpoint.epoch == 4 - - 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) - ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) - assert ancestor_at_finalized_slot == store.finalized_checkpoint.root - - assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() - assert store.justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() From 27763bdd869d98193e7be0a8b39d312f0bb6d1d4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 30 Jun 2021 05:40:26 +0800 Subject: [PATCH 10/10] clean up --- .../test/phase0/fork_choice/test_on_block.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index c3374d4f8..634610fe7 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -86,7 +86,7 @@ def test_on_block_checkpoints(spec, state): # Run for 1 epoch with full attestations next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) state, store, last_signed_block = yield from apply_next_epoch_with_attestations( spec, state, store, True, False, test_steps=test_steps) @@ -95,7 +95,7 @@ def test_on_block_checkpoints(spec, state): # Forward 1 epoch next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) # Mock the finalized_checkpoint and build a block on it fin_state = store.block_states[last_block_root].copy() @@ -596,11 +596,9 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): assert another_state.finalized_checkpoint.epoch == 2 assert another_state.current_justified_checkpoint.epoch == 3 - assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() - assert ( - state.current_justified_checkpoint.hash_tree_root() - != another_state.current_justified_checkpoint.hash_tree_root() - ) + assert state.finalized_checkpoint != another_state.finalized_checkpoint + assert state.current_justified_checkpoint != another_state.current_justified_checkpoint + # pre_store_justified_checkpoint_root = store.justified_checkpoint.root # FIXME: pending on the `on_block`, `on_attestation` fix