Apply proto's feedback, fix+refactor test_get_head, fix test format doc

Note that to execute on_attestation after on_block

Output more checking field

Disable mainnet config test_filtered_block_tree

Fix after rectoring + use more run_on_block

Fix and refactor `tick_and_run_on_attestation`
This commit is contained in:
Hsiao-Wei Wang 2021-03-12 22:13:07 +08:00
parent d29926efe6
commit e77ba91821
No known key found for this signature in database
GPG Key ID: 1111A8A81778319E
3 changed files with 137 additions and 99 deletions

View File

@ -10,7 +10,17 @@ def get_anchor_root(spec, state):
return spec.hash_tree_root(anchor_block_header) return spec.hash_tree_root(anchor_block_header)
def add_block_to_store(spec, store, signed_block, test_steps=None): def add_block_to_store(spec, store, signed_block):
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)
spec.on_block(store, signed_block)
def tick_and_run_on_block(spec, store, signed_block, test_steps=None):
if test_steps is None: if test_steps is None:
test_steps = [] test_steps = []
@ -18,14 +28,12 @@ def add_block_to_store(spec, store, signed_block, test_steps=None):
block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
if store.time < block_time: if store.time < block_time:
spec.on_tick(store, block_time) on_tick_and_append_step(spec, store, block_time, test_steps)
test_steps.append({'tick': int(block_time)})
spec.on_block(store, signed_block) yield from run_on_block(spec, store, signed_block, test_steps)
test_steps.append({'block': get_block_file_name(signed_block)})
def add_attestation_to_store(spec, store, attestation, test_steps=None): def tick_and_run_on_attestation(spec, store, attestation, test_steps=None):
if test_steps is None: if test_steps is None:
test_steps = [] test_steps = []
@ -39,6 +47,7 @@ def add_attestation_to_store(spec, store, attestation, test_steps=None):
test_steps.append({'tick': int(next_epoch_time)}) test_steps.append({'tick': int(next_epoch_time)})
spec.on_attestation(store, attestation) spec.on_attestation(store, attestation)
yield get_attestation_file_name(attestation), attestation
test_steps.append({'attestation': get_attestation_file_name(attestation)}) test_steps.append({'attestation': get_attestation_file_name(attestation)})
@ -60,3 +69,47 @@ def get_block_file_name(block):
def get_attestation_file_name(attestation): def get_attestation_file_name(attestation):
return f"attestation_{encode_hex(attestation.hash_tree_root())}" return f"attestation_{encode_hex(attestation.hash_tree_root())}"
def on_tick_and_append_step(spec, store, time, test_steps):
spec.on_tick(store, time)
test_steps.append({'tick': int(time)})
def run_on_block(spec, store, signed_block, test_steps, valid=True):
if not valid:
try:
spec.on_block(store, signed_block)
except AssertionError:
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
for attestation in signed_block.message.body.attestations:
spec.on_attestation(store, attestation)
assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message
test_steps.append({
'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),
}
})
def get_formatted_head_output(spec, store):
head = spec.get_head(store)
slot = store.blocks[head].slot
return {
'slot': int(slot),
'root': encode_hex(head),
}

View File

@ -1,14 +1,22 @@
from eth_utils import encode_hex from eth_utils import encode_hex
from eth2spec.test.context import MINIMAL, with_all_phases, with_configs, spec_state_test from eth2spec.test.context import (
MINIMAL,
is_post_altair,
spec_state_test,
with_all_phases,
with_configs,
)
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations 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.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.fork_choice import ( from eth2spec.test.helpers.fork_choice import (
add_attestation_to_store, tick_and_run_on_attestation,
add_block_to_store, get_anchor_root, tick_and_run_on_block,
get_anchor_root,
get_genesis_forkchoice_store_and_block, get_genesis_forkchoice_store_and_block,
get_attestation_file_name, get_formatted_head_output,
get_block_file_name, on_tick_and_append_step,
run_on_block,
) )
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
next_epoch, next_epoch,
@ -17,7 +25,6 @@ from eth2spec.test.helpers.state import (
@with_all_phases @with_all_phases
@with_configs([MINIMAL])
@spec_state_test @spec_state_test
def test_genesis(spec, state): def test_genesis(spec, state):
test_steps = [] test_steps = []
@ -27,19 +34,21 @@ def test_genesis(spec, state):
yield 'anchor_block', anchor_block yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state) anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store) assert spec.get_head(store) == anchor_root
assert head == anchor_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'genesis_time': int(store.genesis_time),
'head': get_formatted_head_output(spec, store),
} }
}) })
yield 'steps', test_steps yield 'steps', test_steps
if is_post_altair(spec):
yield 'description', 'meta', f"Although it's not phase 0, we may use {spec.fork} spec to start testnets."
@with_all_phases @with_all_phases
@with_configs([MINIMAL])
@spec_state_test @spec_state_test
def test_chain_no_attestations(spec, state): def test_chain_no_attestations(spec, state):
test_steps = [] test_steps = []
@ -49,31 +58,27 @@ def test_chain_no_attestations(spec, state):
yield 'anchor_block', anchor_block yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state) anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store) assert spec.get_head(store) == anchor_root
assert head == anchor_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
# On receiving a block of `GENESIS_SLOT + 1` slot # On receiving a block of `GENESIS_SLOT + 1` slot
block_1 = build_empty_block_for_next_slot(spec, state) block_1 = build_empty_block_for_next_slot(spec, state)
signed_block_1 = state_transition_and_sign_block(spec, state, block_1) signed_block_1 = state_transition_and_sign_block(spec, state, block_1)
add_block_to_store(spec, store, signed_block_1, test_steps) yield from tick_and_run_on_block(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 # On receiving a block of next epoch
block_2 = build_empty_block_for_next_slot(spec, state) block_2 = build_empty_block_for_next_slot(spec, state)
signed_block_2 = state_transition_and_sign_block(spec, state, block_2) signed_block_2 = state_transition_and_sign_block(spec, state, block_2)
add_block_to_store(spec, store, signed_block_2, test_steps) yield from tick_and_run_on_block(spec, store, signed_block_2, test_steps)
yield get_block_file_name(signed_block_2), signed_block_2
head = spec.get_head(store) assert spec.get_head(store) == spec.hash_tree_root(block_2)
assert head == spec.hash_tree_root(block_2)
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
@ -81,7 +86,6 @@ def test_chain_no_attestations(spec, state):
@with_all_phases @with_all_phases
@with_configs([MINIMAL])
@spec_state_test @spec_state_test
def test_split_tie_breaker_no_attestations(spec, state): def test_split_tie_breaker_no_attestations(spec, state):
test_steps = [] test_steps = []
@ -92,11 +96,10 @@ def test_split_tie_breaker_no_attestations(spec, state):
yield 'anchor_state', state yield 'anchor_state', state
yield 'anchor_block', anchor_block yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state) anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store) assert spec.get_head(store) == anchor_root
assert head == anchor_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
@ -104,23 +107,20 @@ def test_split_tie_breaker_no_attestations(spec, state):
block_1_state = genesis_state.copy() block_1_state = genesis_state.copy()
block_1 = build_empty_block_for_next_slot(spec, block_1_state) 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) 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 from tick_and_run_on_block(spec, store, signed_block_1, test_steps)
yield get_block_file_name(signed_block_1), signed_block_1
# additional block at slot 1 # additional block at slot 1
block_2_state = genesis_state.copy() block_2_state = genesis_state.copy()
block_2 = build_empty_block_for_next_slot(spec, block_2_state) block_2 = build_empty_block_for_next_slot(spec, block_2_state)
block_2.body.graffiti = b'\x42' * 32 block_2.body.graffiti = b'\x42' * 32
signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2) 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 from tick_and_run_on_block(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)) highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
head = spec.get_head(store) assert spec.get_head(store) == highest_root
assert head == highest_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
@ -138,11 +138,10 @@ def test_shorter_chain_but_heavier_weight(spec, state):
yield 'anchor_state', state yield 'anchor_state', state
yield 'anchor_block', anchor_block yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state) anchor_root = get_anchor_root(spec, state)
head = spec.get_head(store) assert spec.get_head(store) == anchor_root
assert head == anchor_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
@ -151,25 +150,22 @@ def test_shorter_chain_but_heavier_weight(spec, state):
for _ in range(3): for _ in range(3):
long_block = build_empty_block_for_next_slot(spec, long_state) long_block = build_empty_block_for_next_slot(spec, long_state)
signed_long_block = state_transition_and_sign_block(spec, long_state, long_block) 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 from tick_and_run_on_block(spec, store, signed_long_block, test_steps)
yield get_block_file_name(signed_long_block), signed_long_block
# build short tree # build short tree
short_state = genesis_state.copy() short_state = genesis_state.copy()
short_block = build_empty_block_for_next_slot(spec, short_state) short_block = build_empty_block_for_next_slot(spec, short_state)
short_block.body.graffiti = b'\x42' * 32 short_block.body.graffiti = b'\x42' * 32
signed_short_block = state_transition_and_sign_block(spec, short_state, short_block) 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 from tick_and_run_on_block(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) short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True)
add_attestation_to_store(spec, store, short_attestation, test_steps) yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps)
head = spec.get_head(store) assert spec.get_head(store) == spec.hash_tree_root(short_block)
assert head == spec.hash_tree_root(short_block)
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store),
} }
}) })
@ -178,59 +174,44 @@ def test_shorter_chain_but_heavier_weight(spec, state):
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_filtered_block_tree(spec, state): def test_filtered_block_tree(spec, state):
test_steps = [] test_steps = []
# Initialization # Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state yield 'anchor_state', state
yield 'anchor_block', anchor_block yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state) anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
# transition state past initial couple of epochs # transition state past initial couple of epochs
next_epoch(spec, state) next_epoch(spec, state)
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 # fill in attestations for entire epoch, justifying the recent epoch
prev_state, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) 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 assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch
# tick time forward and add blocks and attestations to store # tick time forward and add blocks and attestations to store
current_time = state.slot * spec.SECONDS_PER_SLOT + store.genesis_time current_time = state.slot * spec.SECONDS_PER_SLOT + store.genesis_time
spec.on_tick(store, current_time) on_tick_and_append_step(spec, store, current_time, test_steps)
for signed_block in signed_blocks: for signed_block in signed_blocks:
spec.on_block(store, signed_block) yield from run_on_block(spec, store, signed_block, test_steps)
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 assert store.justified_checkpoint == state.current_justified_checkpoint
# the last block in the branch should be the head # the last block in the branch should be the head
expected_head_root = spec.hash_tree_root(signed_blocks[-1].message) expected_head_root = spec.hash_tree_root(signed_blocks[-1].message)
head = spec.get_head(store) assert spec.get_head(store) == expected_head_root
assert head == expected_head_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': get_formatted_head_output(spec, store),
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.hash_tree_root()), 'justified_checkpoint_root': encode_hex(store.justified_checkpoint.hash_tree_root()),
'head': encode_hex(head),
} }
}) })
@ -263,25 +244,19 @@ def test_filtered_block_tree(spec, state):
# tick time forward to be able to include up to the latest 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 current_time = (attestations[-1].data.slot + 1) * spec.SECONDS_PER_SLOT + store.genesis_time
spec.on_tick(store, current_time) on_tick_and_append_step(spec, store, current_time, test_steps)
test_steps.append({'tick': int(current_time)})
# include rogue block and associated attestations in the store # include rogue block and associated attestations in the store
spec.on_block(store, signed_rogue_block) yield from run_on_block(spec, store, signed_rogue_block, test_steps)
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: for attestation in attestations:
spec.on_attestation(store, attestation) yield from tick_and_run_on_attestation(spec, store, attestation, test_steps)
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 # ensure that get_head still returns the head from the previous branch
head = spec.get_head(store) assert spec.get_head(store) == expected_head_root
assert head == expected_head_root
test_steps.append({ test_steps.append({
'checks': { 'checks': {
'head': encode_hex(head) 'head': get_formatted_head_output(spec, store)
} }
}) })

View File

@ -1,6 +1,6 @@
# Fork choice tests # Fork choice tests
The aim of the tests for the fork choice rules. The aim of the fork choice tests is to provide test coverage of the various components of the fork choice.
## Test case format ## Test case format
@ -38,7 +38,7 @@ After this step, the `store` object may have been updated.
The parameter that is required for executing `on_attestation(store, attestation)`. The parameter that is required for executing `on_attestation(store, attestation)`.
```yaml ```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.
``` ```
The file is located in the same folder (see below). The file is located in the same folder (see below).
@ -49,7 +49,7 @@ After this step, the `store` object may have been updated.
The parameter that is required for executing `on_block(store, block)`. The parameter that is required for executing `on_block(store, block)`.
```yaml ```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.
``` ```
The file is located in the same folder (see below). The file is located in the same folder (see below).
@ -60,27 +60,36 @@ After this step, the `store` object may have been updated.
The checks to verify the current status of `store`. The checks to verify the current status of `store`.
```yaml ```yaml
checks: {<store_field_name>: value} -- the assertions. checks: {<store_attibute>: value} -- the assertions.
``` ```
`<store_field_name>` is the field member of [`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by client implementation. Currently, the possible fields included: `<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 ```yaml
head: { -- Encoded 32-byte value from get_head(store)
slot: slot,
root: string,
}
time: int -- store.time time: int -- store.time
genesis_time: int -- store.genesis_time genesis_time: int -- store.genesis_time
justified_checkpoint_root: string -- store.justified_checkpoint.root justified_checkpoint_root: string -- Encoded 32-byte value from store.justified_checkpoint.root
finalized_checkpoint_root: string -- store.finalized_checkpoint_root.root finalized_checkpoint_root: string -- Encoded 32-byte value from store.finalized_checkpoint.root
best_justified_checkpoint_root: string -- store.best_justified_checkpoint_root.root best_justified_checkpoint_root: string -- Encoded 32-byte value from store.best_justified_checkpoint.root
``` ```
For example: For example:
```yaml ```yaml
- checks: { - checks:
justified_checkpoint_root: '0x347468b606d03f8429afd491f94e32cd3a2295c2536e808c863a9d132a521dc4', time: 144
head: '0x17aa608f5fce87592c6f02ca6ca3c49ca70b5cef5456697709b2e5894e3879c2' genesis_time: 0
} head: {slot: 17, root: '0xd2724c86002f7e1f8656ab44a341a409ad80e6e70a5225fd94835566deebb66f'}
justified_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
finalized_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
best_justified_checkpoint: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
``` ```
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
### `attestation_<32-byte-root>.ssz_snappy` ### `attestation_<32-byte-root>.ssz_snappy`
`<32-byte-root>` is the hash tree root of the given attestation. `<32-byte-root>` is the hash tree root of the given attestation.
@ -98,4 +107,5 @@ Each file is an SSZ-snappy encoded `SignedBeaconBlock`.
1. Deserialize `anchor_state.ssz_snappy` and `anchor_block.ssz_snappy` to initialize the local store object by with `get_forkchoice_store(anchor_state, anchor_block)` helper. 1. Deserialize `anchor_state.ssz_snappy` and `anchor_block.ssz_snappy` to initialize the local store object by with `get_forkchoice_store(anchor_state, anchor_block)` helper.
2. Iterate sequentially through `steps.yaml` 2. Iterate sequentially through `steps.yaml`
- For each execution, look up the corresponding ssz_snappy file. Execute the corresponding helper function on the current store. - For each execution, look up the corresponding ssz_snappy file. Execute the corresponding helper function on the current store.
- For the `on_block` execution step: if `len(block.message.body.attestations) > 0`, execute each attestation with `on_attestation(store, attestation)` after executing `on_block(store, block)`.
- For each `checks` step, the assertions on the current store must be satisfied. - For each `checks` step, the assertions on the current store must be satisfied.