Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fork choice store checkpoints inconsistency issue #2518

Merged
merged 4 commits into from
Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions specs/phase0/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,13 @@ def on_tick(store: Store, time: uint64) -> None:
# Not a new epoch, return
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
return
# Update store.justified_checkpoint if a better checkpoint is known

# Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain
if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = store.best_justified_checkpoint
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot)
if ancestor_at_finalized_slot == store.finalized_checkpoint.root:
store.justified_checkpoint = store.best_justified_checkpoint
```

#### `on_block`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
transition_to,
)


def run_on_tick(spec, store, time, new_justified_checkpoint=False):
Expand All @@ -26,18 +34,92 @@ def test_basic(spec, state):

@with_all_phases
@spec_state_test
def test_update_justified_single(spec, state):
def test_update_justified_single_on_store_finalized_chain(spec, state):
store = get_genesis_forkchoice_store(spec, state)
next_epoch = spec.get_current_epoch(state) + 1
next_epoch_start_slot = spec.compute_start_slot_at_epoch(next_epoch)
seconds_until_next_epoch = next_epoch_start_slot * spec.config.SECONDS_PER_SLOT - store.time

store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
parent_block = block.copy()
# To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch
slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
transition_to(spec, state, slot)
# Create a block at the start of epoch 2
block = build_empty_block_for_next_slot(spec, state)
# Mock state
state.current_justified_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(parent_block.slot),
root=parent_block.hash_tree_root(),
)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block
store.block_states[block.hash_tree_root()] = state
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()

run_on_tick(
spec,
store,
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
new_justified_checkpoint=True
)


@with_all_phases
@spec_state_test
def test_update_justified_single_not_on_store_finalized_chain(spec, state):
store = get_genesis_forkchoice_store(spec, state)
init_state = state.copy()

# Chain grows
# Create a block at epoch 1
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
block.body.graffiti = b'\x11' * 32
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
# Mock store.finalized_checkpoint
store.finalized_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(block.slot),
root=block.hash_tree_root(),
)

run_on_tick(spec, store, store.time + seconds_until_next_epoch, True)
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
state = init_state.copy()
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
block.body.graffiti = b'\x22' * 32
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
parent_block = block.copy()
# To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch
slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
transition_to(spec, state, slot)
# Create a block at the start of epoch 2
block = build_empty_block_for_next_slot(spec, state)
# Mock state
state.current_justified_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(parent_block.slot),
root=parent_block.hash_tree_root(),
)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()

run_on_tick(
spec,
store,
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
)


@with_all_phases
Expand Down