Move more unit tests to test vectors

This commit is contained in:
Hsiao-Wei Wang 2021-06-25 18:48:30 +08:00
parent f55afefe90
commit 29a93f6285
No known key found for this signature in database
GPG Key ID: 1111A8A81778319E
4 changed files with 608 additions and 447 deletions

View File

@ -217,30 +217,13 @@ def next_slots_with_attestations(spec,
post_state = state.copy() post_state = state.copy()
signed_blocks = [] signed_blocks = []
for _ in range(slot_count): for _ in range(slot_count):
block = build_empty_block_for_next_slot(spec, post_state) signed_block = state_transition_with_full_block(
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, 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, post_state,
spec, fill_cur_epoch,
slot_to_attest, fill_prev_epoch,
participation_fn=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_blocks.append(signed_block) signed_blocks.append(signed_block)
return state, signed_blocks, post_state return state, signed_blocks, post_state
@ -249,7 +232,8 @@ def next_slots_with_attestations(spec,
def next_epoch_with_attestations(spec, def next_epoch_with_attestations(spec,
state, state,
fill_cur_epoch, fill_cur_epoch,
fill_prev_epoch): fill_prev_epoch,
participation_fn=None):
assert state.slot % spec.SLOTS_PER_EPOCH == 0 assert state.slot % spec.SLOTS_PER_EPOCH == 0
return next_slots_with_attestations( return next_slots_with_attestations(
@ -258,15 +242,50 @@ def next_epoch_with_attestations(spec,
spec.SLOTS_PER_EPOCH, spec.SLOTS_PER_EPOCH,
fill_cur_epoch, fill_cur_epoch,
fill_prev_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 # Build a block with previous attestations
block = build_empty_block_for_next_slot(spec, state) block = build_empty_block_for_next_slot(spec, state)
attestations = [] attestations = []
if fill_prev_epoch: if fill_cur_epoch:
# current epoch # current epoch
slots = state.slot % spec.SLOTS_PER_EPOCH slots = state.slot % spec.SLOTS_PER_EPOCH
for slot_offset in range(slots): for slot_offset in range(slots):

View File

@ -1,5 +1,8 @@
from eth_utils import encode_hex 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): 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) spec.on_block(store, signed_block)
def tick_and_add_block(spec, store, signed_block, test_steps=None, valid=True): def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False):
if test_steps is None:
test_steps = []
pre_state = store.block_states[signed_block.message.parent_root] 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 block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
if store.time < block_time: if store.time < block_time:
on_tick_and_append_step(spec, store, block_time, test_steps) 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): def tick_and_run_on_attestation(spec, store, attestation, test_steps):
if test_steps is None:
test_steps = []
parent_block = store.blocks[attestation.data.beacon_block_root] parent_block = store.blocks[attestation.data.beacon_block_root]
pre_state = store.block_states[spec.hash_tree_root(parent_block)] 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 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)}) 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): def get_genesis_forkchoice_store(spec, genesis_state):
store, _ = get_genesis_forkchoice_store_and_block(spec, genesis_state) store, _ = get_genesis_forkchoice_store_and_block(spec, genesis_state)
return store 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 assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message
def add_block(spec, store, signed_block, test_steps=None, valid=True): def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False):
if test_steps is None: """
test_steps = [] Run on_block and on_attestation
"""
yield get_block_file_name(signed_block), signed_block yield get_block_file_name(signed_block), signed_block
if not valid: 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)}) test_steps.append({'block': get_block_file_name(signed_block)})
# An on_block step implies receiving block's attestations # An on_block step implies receiving block's attestations
try:
for attestation in signed_block.message.body.attestations: for attestation in signed_block.message.body.attestations:
spec.on_attestation(store, attestation) 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() block_root = signed_block.message.hash_tree_root()
assert store.blocks[block_root] == signed_block.message 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): def get_formatted_head_output(spec, store):
head = spec.get_head(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: if test_steps is None:
test_steps = [] 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: for signed_block in new_signed_blocks:
block = signed_block.message block = signed_block.message
yield from tick_and_add_block(spec, store, signed_block, test_steps) yield from tick_and_add_block(spec, store, signed_block, test_steps)

View File

@ -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.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 ( from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block_for_next_slot,
build_empty_block, build_empty_block,
@ -13,14 +20,27 @@ from eth2spec.test.helpers.fork_choice import (
add_block, add_block,
tick_and_add_block, tick_and_add_block,
apply_next_epoch_with_attestations, apply_next_epoch_with_attestations,
apply_next_slots_with_attestations,
) )
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
next_epoch, next_epoch,
next_slots,
state_transition_and_sign_block, state_transition_and_sign_block,
transition_to, 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 @with_all_phases
@spec_state_test @spec_state_test
def test_basic(spec, state): def test_basic(spec, state):
@ -68,7 +88,8 @@ def test_on_block_checkpoints(spec, state):
next_epoch(spec, state) 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.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) last_block_root = hash_tree_root(last_signed_block.message)
assert spec.get_head(store) == last_block_root assert spec.get_head(store) == last_block_root
@ -152,7 +173,8 @@ def test_on_block_before_finalized(spec, state):
# Create a finalized chain # Create a finalized chain
for _ in range(4): 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 assert store.finalized_checkpoint.epoch == 2
# Fail receiving block of `GENESIS_SLOT + 1` slot # 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 # Create a finalized chain
for _ in range(4): 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 assert store.finalized_checkpoint.epoch == 2
# Another chain # Another chain
@ -219,7 +242,8 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
# Finalized # Finalized
for _ in range(3): 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 assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1
# Now build a block at later slot than finalized epoch # 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 from tick_and_add_block(spec, store, signed_block, test_steps, valid=False)
yield 'steps', test_steps 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

View File

@ -1,11 +1,9 @@
from copy import deepcopy 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.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.helpers.attestations import ( from eth2spec.test.context import (
next_epoch_with_attestations, spec_state_test,
next_slots_with_attestations, with_all_phases,
state_transition_with_signed_full_block,
) )
from eth2spec.test.helpers.block import ( from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block_for_next_slot,
@ -15,58 +13,25 @@ from eth2spec.test.helpers.fork_choice import (
run_on_block, run_on_block,
apply_next_epoch_with_attestations, apply_next_epoch_with_attestations,
) )
from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, next_slots from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
@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
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): 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 # Initialization
store = get_genesis_forkchoice_store(spec, state) store = get_genesis_forkchoice_store(spec, state)
next_epoch(spec, state) next_epoch(spec, state)
spec.on_tick(store, store.genesis_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) 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) last_block_root = hash_tree_root(last_signed_block.message)
# NOTE: Mock fictitious justified checkpoint in store # 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) 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 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 previously_justified = store.justified_checkpoint
# Add a series of new blocks with "better" justifications # 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) run_on_block(spec, store, signed_block)
assert store.finalized_checkpoint == previously_finalized
assert store.justified_checkpoint == previously_justified assert store.justified_checkpoint == previously_justified
# ensure the best from the series was stored # ensure the best from the series was stored
assert store.best_justified_checkpoint == best_justified_checkpoint 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()