Add `get_head` test vectors
This commit is contained in:
parent
8ec082fcf9
commit
27507fb3e2
|
@ -1,3 +1,5 @@
|
|||
from eth_utils import encode_hex
|
||||
|
||||
from eth2spec.phase0 import spec as phase0_spec
|
||||
|
||||
|
||||
|
@ -8,17 +10,25 @@ 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):
|
||||
def add_attestation_to_store(spec, store, attestation, test_steps=None):
|
||||
if test_steps is None:
|
||||
test_steps = []
|
||||
|
||||
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
|
||||
|
@ -26,12 +36,27 @@ def add_attestation_to_store(spec, store, attestation):
|
|||
|
||||
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())}"
|
||||
|
|
|
@ -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):
|
||||
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
|
|
@ -0,0 +1,3 @@
|
|||
# Fork choice tests
|
||||
|
||||
TODO
|
|
@ -0,0 +1,40 @@
|
|||
from typing import Iterable
|
||||
|
||||
from gen_base import gen_runner, gen_typing
|
||||
from gen_from_tests.gen import generate_from_tests
|
||||
from importlib import reload, import_module
|
||||
from eth2spec.config import config_util
|
||||
from eth2spec.phase0 import spec as spec_phase0
|
||||
from eth2spec.test.context import PHASE0
|
||||
from eth2spec.utils import bls
|
||||
|
||||
|
||||
def create_provider(fork_name: str, handler_name: str,
|
||||
tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
|
||||
def prepare_fn(configs_path: str) -> str:
|
||||
config_util.prepare_config(configs_path, config_name)
|
||||
reload(spec_phase0)
|
||||
bls.use_milagro()
|
||||
return config_name
|
||||
|
||||
def cases_fn() -> Iterable[gen_typing.TestCase]:
|
||||
tests_src = import_module(tests_src_mod_name)
|
||||
return generate_from_tests(
|
||||
runner_name='fork_choice',
|
||||
handler_name=handler_name,
|
||||
src=tests_src,
|
||||
fork_name=fork_name,
|
||||
)
|
||||
|
||||
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [
|
||||
'get_head',
|
||||
]}
|
||||
|
||||
# TODO: add other configs and forks
|
||||
gen_runner.run_generator(f"fork_choice", [
|
||||
create_provider(PHASE0, key, mod_name, 'minimal') for key, mod_name in phase_0_mods.items()
|
||||
])
|
|
@ -0,0 +1,2 @@
|
|||
../../core/gen_helpers
|
||||
../../../
|
Loading…
Reference in New Issue