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

Add optimistic sync tests #2982

Merged
merged 4 commits into from
Sep 12, 2022
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
4 changes: 2 additions & 2 deletions sync/optimistic.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ Let `current_slot: Slot` be `(time - genesis_time) // SECONDS_PER_SLOT` where
class OptimisticStore(object):
optimistic_roots: Set[Root]
head_block_root: Root
blocks: Dict[Root, BeaconBlock]
block_states: Dict[Root, BeaconState]
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
```

```python
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import json
from typing import Iterable, AnyStr, Any, Callable
import traceback

from ruamel.yaml import (
YAML,
)
Expand Down Expand Up @@ -98,6 +97,11 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
yaml = YAML(pure=True)
yaml.default_flow_style = None

def _represent_none(self, _):
return self.represent_scalar('tag:yaml.org,2002:null', 'null')

yaml.representer.add_representer(type(None), _represent_none)

# Spec config is using a YAML subset
cfg_yaml = YAML(pure=True)
cfg_yaml.default_flow_style = False # Emit separate line for each key
Expand Down
Empty file.
99 changes: 99 additions & 0 deletions tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from eth2spec.test.context import (
spec_state_test,
with_bellatrix_and_later,
)
from eth2spec.test.helpers.attestations import (
state_transition_with_full_block,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.fork_choice import (
get_genesis_forkchoice_store_and_block,
on_tick_and_append_step,
)
from eth2spec.test.helpers.optimistic_sync import (
PayloadStatusV1,
PayloadStatusV1Status,
MegaStore,
add_optimistic_block,
get_optimistic_store,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
)


@with_bellatrix_and_later
@spec_state_test
def test_from_syncing_to_invalid(spec, state):
test_steps = []
# Initialization
fc_store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
op_store = get_optimistic_store(spec, state, anchor_block)
mega_store = MegaStore(spec, fc_store, op_store)
yield 'anchor_state', state
yield 'anchor_block', anchor_block

next_epoch(spec, state)

current_time = (
(spec.SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY * 10 + state.slot) * spec.config.SECONDS_PER_SLOT
+ fc_store.genesis_time
)
on_tick_and_append_step(spec, fc_store, current_time, test_steps)

# Block 0
block_0 = build_empty_block_for_next_slot(spec, state)
block_0.body.execution_payload.block_hash = spec.hash(bytes(f'block_0', 'UTF-8'))
signed_block = state_transition_and_sign_block(spec, state, block_0)
yield from add_optimistic_block(spec, mega_store, signed_block, test_steps, status=PayloadStatusV1Status.VALID)
assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root

state_0 = state.copy()

# Create VALID chain `a`
signed_blocks_a = []
for i in range(3):
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.block_hash = spec.hash(bytes(f'chain_a_{i}', 'UTF-8'))
block.body.execution_payload.parent_hash = (
spec.hash(bytes(f'chain_a_{i - 1}', 'UTF-8')) if i != 0 else block_0.body.execution_payload.block_hash
)

signed_block = state_transition_and_sign_block(spec, state, block)
yield from add_optimistic_block(spec, mega_store, signed_block, test_steps, status=PayloadStatusV1Status.VALID)
assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root
signed_blocks_a.append(signed_block.copy())

# Create SYNCING chain `b`
signed_blocks_b = []
state = state_0.copy()
for i in range(3):
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.block_hash = spec.hash(bytes(f'chain_b_{i}', 'UTF-8'))
block.body.execution_payload.parent_hash = (
spec.hash(bytes(f'chain_b_{i - 1}', 'UTF-8')) if i != 0 else block_0.body.execution_payload.block_hash
)
signed_block = state_transition_with_full_block(spec, state, True, True, block=block)
signed_blocks_b.append(signed_block.copy())
yield from add_optimistic_block(spec, mega_store, signed_block, test_steps,
status=PayloadStatusV1Status.SYNCING)
assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root

# Now add block 4 to chain `b` with INVALID
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.block_hash = spec.hash(bytes(f'chain_b_3', 'UTF-8'))
block.body.execution_payload.parent_hash = signed_blocks_b[-1].message.body.execution_payload.block_hash
signed_block = state_transition_and_sign_block(spec, state, block)
payload_status = PayloadStatusV1(
status=PayloadStatusV1Status.INVALID,
latest_valid_hash=block_0.body.execution_payload.block_hash,
validation_error="invalid",
)
yield from add_optimistic_block(spec, mega_store, signed_block, test_steps,
payload_status=payload_status)
assert mega_store.opt_store.head_block_root == signed_blocks_a[-1].message.hash_tree_root()

yield 'steps', test_steps
6 changes: 4 additions & 2 deletions tests/core/pyspec/eth2spec/test/helpers/attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,13 @@ def state_transition_with_full_block(spec,
fill_cur_epoch,
fill_prev_epoch,
participation_fn=None,
sync_aggregate=None):
sync_aggregate=None,
block=None):
"""
Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch.
"""
block = build_empty_block_for_next_slot(spec, state)
if block is None:
block = build_empty_block_for_next_slot(spec, state)
if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)):
Expand Down
80 changes: 40 additions & 40 deletions tests/core/pyspec/eth2spec/test/helpers/fork_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,8 @@ def get_anchor_root(spec, state):
return spec.hash_tree_root(anchor_block_header)


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.config.SECONDS_PER_SLOT

if store.time < block_time:
spec.on_tick(store, block_time)

spec.on_block(store, signed_block)


def tick_and_add_block(spec, store, signed_block, test_steps, valid=True,
merge_block=False, block_not_found=False):
merge_block=False, block_not_found=False, is_optimistic=False):
pre_state = store.block_states[signed_block.message.parent_root]
block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
if merge_block:
Expand All @@ -37,6 +27,7 @@ def tick_and_add_block(spec, store, signed_block, test_steps, valid=True,
spec, store, signed_block, test_steps,
valid=valid,
block_not_found=block_not_found,
is_optimistic=is_optimistic,
)

return post_state
Expand Down Expand Up @@ -119,28 +110,36 @@ def add_block(spec,
signed_block,
test_steps,
valid=True,
block_not_found=False):
block_not_found=False,
is_optimistic=False):
"""
Run on_block and on_attestation
"""
yield get_block_file_name(signed_block), signed_block

if not valid:
try:
if is_optimistic:
run_on_block(spec, store, signed_block, valid=True)
except (AssertionError, BlockNotFoundException) as e:
if isinstance(e, BlockNotFoundException) and not block_not_found:
assert False
test_steps.append({
'block': get_block_file_name(signed_block),
'valid': False,
})
return
else:
assert False

run_on_block(spec, store, signed_block, valid=True)
test_steps.append({'block': get_block_file_name(signed_block)})
try:
run_on_block(spec, store, signed_block, valid=True)
except (AssertionError, BlockNotFoundException) as e:
if isinstance(e, BlockNotFoundException) and not block_not_found:
assert False
test_steps.append({
'block': get_block_file_name(signed_block),
'valid': False,
})
return
else:
assert False
else:
run_on_block(spec, store, signed_block, valid=True)
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:
Expand All @@ -153,25 +152,26 @@ def add_block(spec,
block_root = signed_block.message.hash_tree_root()
assert store.blocks[block_root] == signed_block.message
assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root
test_steps.append({
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'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),
},
'proposer_boost_root': encode_hex(store.proposer_boost_root),
}
})
if not is_optimistic:
test_steps.append({
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'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),
},
'proposer_boost_root': encode_hex(store.proposer_boost_root),
}
})

return store.block_states[signed_block.message.hash_tree_root()]

Expand Down
Loading