Skip to content

Commit

Permalink
add tests for block filter in get_head
Browse files Browse the repository at this point in the history
  • Loading branch information
djrtwo committed Dec 4, 2019
1 parent 2275cdf commit 5c44117
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 20 deletions.
38 changes: 20 additions & 18 deletions specs/core/0_fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,43 +155,45 @@ def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconB
return False

# If leaf block, check finalized/justified checkpoints as matching latest.
# If matching, add to viable block-tree and signal viability to parent.
head_state = store.block_states[block_root]

# Handle base case where justified hasn't updated yet
if head_state.current_justified_checkpoint.epoch == GENESIS_EPOCH:
is_viable_branch = True
else:
is_viable_branch = (
head_state.finalized_checkpoint == store.finalized_checkpoint
and head_state.current_justified_checkpoint == store.justified_checkpoint
)
if is_viable_branch:
correct_justified = (
store.justified_checkpoint.epoch == GENESIS_EPOCH
or head_state.current_justified_checkpoint == store.justified_checkpoint
)
correct_finalized = (
store.finalized_checkpoint.epoch == GENESIS_EPOCH
or head_state.finalized_checkpoint == store.finalized_checkpoint
)
# If expected finalized/justified, add to viable block-tree and signal viability to parent.
if correct_justified and correct_finalized:
blocks[block_root] = block
return True

# Otherwise, not viable
# Otherwise, branch not viable
return False
```

#### `get_filtered_block_tree`

```python
def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]:
head = store.justified_checkpoint.root
"""
Retrieve a filtered block true from ``store``, only returning branches
whose leaf state's justified/finalized info agrees with that in ``store``.
"""
base = store.justified_checkpoint.root
blocks: Dict[Root, BeaconBlock] = {}
filter_block_tree(store, head, blocks)
filter_block_tree(store, base, blocks)
return blocks
```

#### `get_head`

```python
def get_head(store: Store) -> Root:
# Get filtered block tree that includes viable branches
# Get filtered block tree that only includes viable branches
blocks = get_filtered_block_tree(store)
print(len(blocks))
print(len(store.blocks))
# Execute the LMD-GHOST fork choice
head = store.justified_checkpoint.root
justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
Expand Down Expand Up @@ -224,8 +226,8 @@ def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: C
if new_justified_block.slot <= compute_start_slot_at_epoch(store.justified_checkpoint.epoch):
return False
if not (
get_ancestor(store, new_justified_checkpoint.root, store.blocks[store.justified_checkpoint.root].slot) ==
store.justified_checkpoint.root
get_ancestor(store, new_justified_checkpoint.root, store.blocks[store.justified_checkpoint.root].slot)
== store.justified_checkpoint.root
):
return False

Expand Down
80 changes: 79 additions & 1 deletion test_libs/pyspec/eth2spec/test/fork_choice/test_get_head.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.state import state_transition_and_sign_block
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_with_attestations,
state_transition_and_sign_block,
)
from eth2spec.utils.ssz.ssz_impl import signing_root


def add_block_to_store(spec, store, block):
Expand Down Expand Up @@ -112,3 +117,76 @@ def test_shorter_chain_but_heavier_weight(spec, state):
add_attestation_to_store(spec, store, short_attestation)

assert spec.get_head(store) == spec.signing_root(short_block)


@with_all_phases
@spec_state_test
def test_filtered_block_tree(spec, state):
genesis_state = state.copy()

# transition state past initial couple of epochs
next_epoch(spec, state)
next_epoch(spec, state)

# Initialization
store = spec.get_genesis_store(genesis_state)
genesis_block = spec.BeaconBlock(state_root=genesis_state.hash_tree_root())
assert spec.get_head(store) == spec.signing_root(genesis_block)

# fill in attestations for entire epoch, justifying the recent epoch
prev_state, blocks, state = next_epoch_with_attestations(spec, state, True, False)
attestations = [attestation for block in blocks for attestation in block.body.attestations]
assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch

# tick time forward and add blocks and attestations to store
current_time = state.slot * spec.SECONDS_PER_SLOT + store.genesis_time
spec.on_tick(store, current_time)
for block in blocks:
spec.on_block(store, block)
for attestation in attestations:
spec.on_attestation(store, attestation)

assert store.justified_checkpoint == state.current_justified_checkpoint

# the last block in the branch should be the head
expected_head_root = signing_root(blocks[-1])
assert spec.get_head(store) == expected_head_root

#
# create branch containing the justified block but not containing enough on
# chain votes to justify that block
#

# build a chain without attestations off of previous justified block
non_viable_state = store.block_states[store.justified_checkpoint.root].copy()

# ensure that next wave of votes are for future epoch
next_epoch(spec, non_viable_state)
next_epoch(spec, non_viable_state)
next_epoch(spec, non_viable_state)
assert spec.get_current_epoch(non_viable_state) > store.justified_checkpoint.epoch

# create rogue block that will be attested to in this non-viable branch
rogue_block = build_empty_block_for_next_slot(spec, non_viable_state, True)
state_transition_and_sign_block(spec, non_viable_state, rogue_block)

# create an epoch's worth of attestations for the rogue block
next_epoch(spec, non_viable_state)
attestations = []
for i in range(spec.SLOTS_PER_EPOCH):
slot = rogue_block.slot + i
for index in range(spec.get_committee_count_at_slot(non_viable_state, slot)):
attestation = get_valid_attestation(spec, non_viable_state, rogue_block.slot + i, index)
attestations.append(attestation)

# tick time forward to be able to include up to the latest attestation
current_time = (attestations[-1].data.slot + 1) * spec.SECONDS_PER_SLOT + store.genesis_time
spec.on_tick(store, current_time)

# include rogue block and associated attestations in the store
spec.on_block(store, rogue_block)
for attestation in attestations:
spec.on_attestation(store, attestation)

# ensure that get_head still returns the head from the previous branch
assert spec.get_head(store) == expected_head_root
2 changes: 1 addition & 1 deletion test_libs/pyspec/eth2spec/test/helpers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def transition_to(spec, state, slot):
Transition to ``slot``.
"""
assert state.slot <= slot
for _ in range(slot - state.slot):
for _ in range(slot - slot):
next_slot(spec, state)
assert state.slot == slot

Expand Down

0 comments on commit 5c44117

Please sign in to comment.