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 on_block tests and update test format #2577

Merged
merged 5 commits into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 12 additions & 3 deletions tests/core/pyspec/eth2spec/test/helpers/fork_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,18 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root),
'finalized_checkpoint_root': encode_hex(store.finalized_checkpoint.root),
'best_justified_checkpoint': encode_hex(store.best_justified_checkpoint.root),
'justified_checkpoint': {
'epoch': int(store.justified_checkpoint.epoch),
'root': encode_hex(store.justified_checkpoint.root),
},
'finalized_checkpoint': {
'epoch': int(store.finalized_checkpoint.epoch),
'root': encode_hex(store.finalized_checkpoint.root),
},
'best_justified_checkpoint': {
'epoch': int(store.best_justified_checkpoint.epoch),
'root': encode_hex(store.best_justified_checkpoint.root),
},
}
})

Expand Down
95 changes: 58 additions & 37 deletions tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
next_epoch,
next_slots,
state_transition_and_sign_block,
transition_to,
)


Expand Down Expand Up @@ -191,6 +190,10 @@ def test_on_block_before_finalized(spec, state):
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_on_block_finalized_skip_slots(spec, state):
"""
Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579
And then rewrote largely.
"""
test_steps = []
# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
Expand All @@ -200,21 +203,31 @@ def test_on_block_finalized_skip_slots(spec, state):
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time

# Create a finalized chain
for _ in range(4):
# Fill epoch 0 and the first slot of epoch 1
state, store, _ = yield from apply_next_slots_with_attestations(
spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps)

# Skip the rest slots of epoch 1 and the first slot of epoch 2
next_slots(spec, state, spec.SLOTS_PER_EPOCH)

# The state after the skipped slots
target_state = state.copy()

# Fill epoch 3 and 4
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, False, test_steps=test_steps)
assert store.finalized_checkpoint.epoch == 2
spec, state, store, True, True, test_steps=test_steps)

# 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)
# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.justified_checkpoint == state.current_justified_checkpoint

# Now build a block at later slot than finalized *epoch*
# Includes finalized block in chain and the skipped slots
block = build_empty_block_for_next_slot(spec, target_state)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is valid wrt the finalized epoch (including the skip slots), but this block isn't the head, right?

apply_next_epoch_with_attestations for epoch 3 and 4 will have created blocks of much higher weight and win out

So -- Does adding this block change anything? or does it catch a regression in the prior fork choice implementations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and if block is supposed to be the head? can we check that it is against a dynamic call to get_head()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. It's just checking in on_block can even be run anymore which would yield valid: False if it didn't work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. It's just checking in on_block can even be run anymore which would yield valid: False if it didn't work

Right, it is for comparing with test_on_block_finalized_skip_slots_not_in_skip_chain (valid: False) test case.

signed_block = state_transition_and_sign_block(spec, target_state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps)

yield 'steps', test_steps
Expand All @@ -224,36 +237,43 @@ def test_on_block_finalized_skip_slots(spec, state):
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
"""
Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579
And then rewrote largely.
"""
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, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', block

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

pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch
# Fill epoch 0 and the first slot of epoch 1
state, store, _ = yield from apply_next_slots_with_attestations(
spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps)

# Finalized
for _ in range(3):
# Skip the rest slots of epoch 1 and the first slot of epoch 2
next_slots(spec, state, spec.SLOTS_PER_EPOCH)

# Fill epoch 3 and 4
for _ in range(2):
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
# 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)
spec, state, store, True, True, test_steps=test_steps)

# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.justified_checkpoint == state.current_justified_checkpoint

# Now build a block after the block of the finalized **root**
# Includes finalized block in chain, but does not include finalized skipped slots
another_state = store.block_states[store.finalized_checkpoint.root].copy()
assert another_state.slot == spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch - 1)
block = build_empty_block_for_next_slot(spec, another_state)
signed_block = state_transition_and_sign_block(spec, another_state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False)

yield 'steps', test_steps
Expand Down Expand Up @@ -483,7 +503,8 @@ def test_new_justified_is_later_than_store_justified(spec, state):
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)
time = store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, time, test_steps)
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)
Expand Down Expand Up @@ -526,7 +547,8 @@ def test_new_justified_is_later_than_store_justified(spec, state):
# # 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)
# time = store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT
# on_tick_and_append_step(spec, store, time, test_steps)
# # 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)

Expand Down Expand Up @@ -643,7 +665,6 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state):

# 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)
Expand Down
36 changes: 22 additions & 14 deletions tests/formats/fork_choice/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,34 @@ checks: {<store_attibute>: value} -- the assertions.
`<store_attibute>` is the field member or property of [`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by client implementation. Currently, the possible fields included:

```yaml
head: { -- Encoded 32-byte value from get_head(store)
slot: slot,
root: string,
head: {
slot: int,
root: string, -- Encoded 32-byte value from get_head(store)
}
time: int -- store.time
genesis_time: int -- store.genesis_time
justified_checkpoint: {
epoch: int, -- Integer value from store.justified_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.justified_checkpoint.root
}
finalized_checkpoint: {
epoch: int, -- Integer value from store.finalized_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root
}
best_justified_checkpoint: {
epoch: int, -- Integer value from store.best_justified_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root
}
time: int -- store.time
genesis_time: int -- store.genesis_time
justified_checkpoint_root: string -- Encoded 32-byte value from store.justified_checkpoint.root
finalized_checkpoint_root: string -- Encoded 32-byte value from store.finalized_checkpoint.root
best_justified_checkpoint_root: string -- Encoded 32-byte value from store.best_justified_checkpoint.root
```

For example:
```yaml
- checks:
time: 144
genesis_time: 0
head: {slot: 17, root: '0xd2724c86002f7e1f8656ab44a341a409ad80e6e70a5225fd94835566deebb66f'}
justified_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
finalized_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
best_justified_checkpoint: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
time: 192
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
```

*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
Expand Down