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

fork-choice test vectors: starting with get_head tests #2202

Merged
merged 9 commits into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
31 changes: 28 additions & 3 deletions tests/core/pyspec/eth2spec/test/helpers/fork_choice.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from eth_utils import encode_hex

from eth2spec.phase0 import spec as phase0_spec


Expand All @@ -8,30 +10,53 @@ def get_anchor_root(spec, state):
return spec.hash_tree_root(anchor_block_header)


def add_block_to_store(spec, store, signed_block):
def add_block_to_store(spec, store, signed_block, test_steps=None):
if test_steps is None:
test_steps = []

pre_state = store.block_states[signed_block.message.parent_root]
block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT

if store.time < block_time:
spec.on_tick(store, block_time)
test_steps.append({'tick': int(block_time)})

spec.on_block(store, signed_block)
test_steps.append({'block': get_block_file_name(signed_block)})


def add_attestation_to_store(spec, store, attestation, test_steps=None):
if test_steps is None:
test_steps = []

def add_attestation_to_store(spec, store, attestation):
parent_block = store.blocks[attestation.data.beacon_block_root]
pre_state = store.block_states[spec.hash_tree_root(parent_block)]
block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT

if store.time < next_epoch_time:
spec.on_tick(store, next_epoch_time)
test_steps.append({'tick': int(next_epoch_time)})

spec.on_attestation(store, attestation)
test_steps.append({'attestation': get_attestation_file_name(attestation)})


def get_genesis_forkchoice_store(spec, genesis_state):
store, _ = get_genesis_forkchoice_store_and_block(spec, genesis_state)
return store


def get_genesis_forkchoice_store_and_block(spec, genesis_state):
assert genesis_state.slot == spec.GENESIS_SLOT
# The genesis block must be a Phase 0 `BeaconBlock`
genesis_block = phase0_spec.BeaconBlock(state_root=genesis_state.hash_tree_root())
return spec.get_forkchoice_store(genesis_state, genesis_block)
return spec.get_forkchoice_store(genesis_state, genesis_block), genesis_block


def get_block_file_name(block):
return f"block_{encode_hex(block.hash_tree_root())}"


def get_attestation_file_name(attestation):
return f"attestation_{encode_hex(attestation.hash_tree_root())}"
Empty file.
288 changes: 288 additions & 0 deletions tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
from eth_utils import encode_hex

from eth2spec.test.context import MINIMAL, with_all_phases, with_configs, spec_state_test
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.fork_choice import (
add_attestation_to_store,
add_block_to_store, get_anchor_root,
get_genesis_forkchoice_store_and_block,
get_attestation_file_name,
get_block_file_name,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
)


@with_all_phases
@with_configs([MINIMAL])
@spec_state_test
def test_genesis(spec, state):
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
test_steps = []
# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block

anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store)
assert head == anchor_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

yield 'steps', test_steps


@with_all_phases
@with_configs([MINIMAL])
@spec_state_test
def test_chain_no_attestations(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

anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store)
assert head == anchor_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

# On receiving a block of `GENESIS_SLOT + 1` slot
block_1 = build_empty_block_for_next_slot(spec, state)
signed_block_1 = state_transition_and_sign_block(spec, state, block_1)
add_block_to_store(spec, store, signed_block_1, test_steps)
yield get_block_file_name(signed_block_1), signed_block_1

# On receiving a block of next epoch
block_2 = build_empty_block_for_next_slot(spec, state)
signed_block_2 = state_transition_and_sign_block(spec, state, block_2)
add_block_to_store(spec, store, signed_block_2, test_steps)
yield get_block_file_name(signed_block_2), signed_block_2

head = spec.get_head(store)
assert head == spec.hash_tree_root(block_2)
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

yield 'steps', test_steps


@with_all_phases
@with_configs([MINIMAL])
@spec_state_test
def test_split_tie_breaker_no_attestations(spec, state):
test_steps = []
genesis_state = state.copy()

# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store)
assert head == anchor_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

# block at slot 1
block_1_state = genesis_state.copy()
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1)
add_block_to_store(spec, store, signed_block_1, test_steps)
yield get_block_file_name(signed_block_1), signed_block_1

# additional block at slot 1
block_2_state = genesis_state.copy()
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
block_2.body.graffiti = b'\x42' * 32
signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2)
add_block_to_store(spec, store, signed_block_2, test_steps)
yield get_block_file_name(signed_block_2), signed_block_2

highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
head = spec.get_head(store)
assert head == highest_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

yield 'steps', test_steps


@with_all_phases
@spec_state_test
def test_shorter_chain_but_heavier_weight(spec, state):
test_steps = []
genesis_state = state.copy()

# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store)
assert head == anchor_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

# build longer tree
long_state = genesis_state.copy()
for _ in range(3):
long_block = build_empty_block_for_next_slot(spec, long_state)
signed_long_block = state_transition_and_sign_block(spec, long_state, long_block)
add_block_to_store(spec, store, signed_long_block, test_steps)
yield get_block_file_name(signed_long_block), signed_long_block

# build short tree
short_state = genesis_state.copy()
short_block = build_empty_block_for_next_slot(spec, short_state)
short_block.body.graffiti = b'\x42' * 32
signed_short_block = state_transition_and_sign_block(spec, short_state, short_block)
add_block_to_store(spec, store, signed_short_block, test_steps)
yield get_block_file_name(signed_short_block), signed_short_block

short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True)
add_attestation_to_store(spec, store, short_attestation, test_steps)

head = spec.get_head(store)
assert head == spec.hash_tree_root(short_block)
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

yield 'steps', test_steps


@with_all_phases
@spec_state_test
def test_filtered_block_tree(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

anchor_root = get_anchor_root(spec, state)

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

head = spec.get_head(store)
assert head == anchor_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

# fill in attestations for entire epoch, justifying the recent epoch
prev_state, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False)
attestations = [
attestation for signed_block in signed_blocks
for attestation in signed_block.message.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 signed_block in signed_blocks:
spec.on_block(store, signed_block)
test_steps.append({'block': get_block_file_name(signed_block)})
yield get_block_file_name(signed_block), signed_block

for attestation in attestations:
spec.on_attestation(store, attestation)
test_steps.append({'attestation': get_attestation_file_name(attestation)})
yield get_attestation_file_name(attestation), attestation

assert store.justified_checkpoint == state.current_justified_checkpoint

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

test_steps.append({
'checks': {
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.hash_tree_root()),
'head': encode_hex(head),
}
})

#
# 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)
signed_rogue_block = 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_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot))):
attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True)
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)
test_steps.append({'tick': int(current_time)})

# include rogue block and associated attestations in the store
spec.on_block(store, signed_rogue_block)
test_steps.append({'block': get_block_file_name(signed_rogue_block)})
yield get_block_file_name(signed_rogue_block), signed_rogue_block

for attestation in attestations:
spec.on_attestation(store, attestation)
test_steps.append({'attestation': get_attestation_file_name(attestation)})
yield get_attestation_file_name(attestation), attestation

# ensure that get_head still returns the head from the previous branch
head = spec.get_head(store)
assert head == expected_head_root
test_steps.append({
'checks': {
'head': encode_hex(head)
}
})

yield 'steps', test_steps
Loading