WIP. Rework on_block tests

This commit is contained in:
Hsiao-Wei Wang 2021-06-15 21:52:25 +08:00
parent a63916a6ec
commit 42eae81013
No known key found for this signature in database
GPG Key ID: 1111A8A81778319E
4 changed files with 363 additions and 6 deletions

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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