From f8d92c3bd8479ea832ceeaf802c26c44687e66a7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 16 Aug 2022 01:22:19 +0800 Subject: [PATCH 1/4] Add basic test case --- sync/optimistic.md | 4 +- .../eth2spec/test/bellatrix/sync/__init__.py | 0 .../test/bellatrix/sync/test_optimistic.py | 98 +++++++++ .../eth2spec/test/helpers/attestations.py | 6 +- .../eth2spec/test/helpers/fork_choice.py | 74 +++---- .../eth2spec/test/helpers/optimistic_sync.py | 189 ++++++++++++++++++ tests/generators/sync/main.py | 14 ++ tests/generators/sync/requirements.txt | 2 + 8 files changed, 346 insertions(+), 41 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/bellatrix/sync/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py create mode 100644 tests/generators/sync/main.py create mode 100644 tests/generators/sync/requirements.txt diff --git a/sync/optimistic.md b/sync/optimistic.md index 4e03cc6bb..07a509592 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sync/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py new file mode 100644 index 000000000..899c6b5ab --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py @@ -0,0 +1,98 @@ +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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 7ba71a969..854dbf59a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -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)): diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index d524060a2..a1d2e6ffc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -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: @@ -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 @@ -119,25 +110,33 @@ 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 + 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 run_on_block(spec, store, signed_block, valid=True) test_steps.append({'block': get_block_file_name(signed_block)}) @@ -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()] diff --git a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py new file mode 100644 index 000000000..3fc31e5b0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py @@ -0,0 +1,189 @@ +from dataclasses import dataclass +from enum import Enum +from typing import ( + Dict, + Optional, +) + +from eth_utils import encode_hex + +from eth2spec.utils.ssz.ssz_typing import Bytes32 +from eth2spec.test.helpers.fork_choice import ( + add_block, +) + + +class PayloadStatusV1StatusAlias(Enum): + NOT_VALIDATED = "NOT_VALIDATED" + INVALIDATED = "INVALIDATED" + + +class PayloadStatusV1Status(Enum): + VALID = "VALID" + INVALID = "INVALID" + SYNCING = "SYNCING" + ACCEPTED = "ACCEPTED" + INVALID_BLOCK_HASH = "INVALID_BLOCK_HASH" + + @property + def alias(self) -> PayloadStatusV1StatusAlias: + if self.value in (self.SYNCING.value, self.ACCEPTED.value): + return PayloadStatusV1StatusAlias.NOT_VALIDATED + elif self.value in (self.INVALID.value, self.INVALID_BLOCK_HASH.value): + return PayloadStatusV1StatusAlias.INVALIDATED + + +@dataclass +class PayloadStatusV1: + status: PayloadStatusV1Status = PayloadStatusV1Status.VALID + latest_valid_hash: Optional[Bytes32] = None + validation_error: Optional[str] = None + + @property + def formatted_output(self): + return { + 'status': str(self.status), + 'latest_valid_hash': encode_hex(self.latest_valid_hash) if self.latest_valid_hash is not None else 'null', + 'validation_error': str(self.validation_error) if self.validation_error is not None else 'null' + } + + +class MegaStore(object): + spec = None + fc_store = None + opt_store = None + block_payload_statuses: Dict[Bytes32, PayloadStatusV1] = dict() + + def __init__(self, spec, fc_store, opt_store): + self.spec = spec + self.fc_store = fc_store + self.opt_store = opt_store + + +def get_optimistic_store(spec, anchor_state, anchor_block): + assert anchor_block.state_root == anchor_state.hash_tree_root() + + opt_store = spec.OptimisticStore( + optimistic_roots=set(), + head_block_root=anchor_block.hash_tree_root(), + + ) + anchor_block_root = anchor_block.hash_tree_root() + opt_store.blocks[anchor_block_root] = anchor_block.copy() + opt_store.block_states[anchor_block_root] = anchor_state.copy() + + return opt_store + + +def add_optimistic_block(spec, mega_store, signed_block, test_steps, + payload_status=None, status=PayloadStatusV1Status.SYNCING): + block = signed_block.message + block_root = block.hash_tree_root() + el_block_hash = block.body.execution_payload.block_hash + + if payload_status is None: + payload_status = PayloadStatusV1(status=status) + if payload_status.status == PayloadStatusV1Status.VALID: + payload_status.latest_valid_hash = el_block_hash + + mega_store.block_payload_statuses[block_root] = payload_status + test_steps.append({ + 'payload_status': payload_status.formatted_output, + }) + + # Optimistic sync + + valid = True + + # Case: INVALID + if payload_status.status == PayloadStatusV1Status.INVALID: + # Update parent status to INVALID + assert payload_status.latest_valid_hash is not None + current_block = block + while el_block_hash != payload_status.latest_valid_hash and el_block_hash != spec.Bytes32(): + current_block_root = current_block.hash_tree_root() + assert current_block_root in mega_store.block_payload_statuses + mega_store.block_payload_statuses[current_block_root].status = PayloadStatusV1Status.INVALID + # Get parent + current_block = mega_store.fc_store.blocks[current_block.parent_root] + el_block_hash = current_block.body.execution_payload.block_hash + + if payload_status.status != PayloadStatusV1Status.VALID: + valid = False + + yield from add_block(spec, mega_store.fc_store, signed_block, + valid=valid, + test_steps=test_steps, + is_optimistic=True) + + # Update stores + is_optimistic_candidate = spec.is_optimistic_candidate_block( + mega_store.opt_store, + current_slot=spec.get_current_slot(mega_store.fc_store), + block=signed_block.message, + ) + if is_optimistic_candidate: + mega_store.opt_store.optimistic_roots.add(block_root) + mega_store.opt_store.blocks[block_root] = signed_block.message.copy() + if not is_invalidated(mega_store, block_root): + mega_store.opt_store.block_states[block_root] = mega_store.fc_store.block_states[block_root].copy() + + # Clean up the invalidated blocks + clean_up_store(mega_store) + + # Update head + mega_store.opt_store.head_block_root = get_opt_head_block_root(spec, mega_store) + test_steps.append({ + 'checks': { + 'head': get_formatted_optimistic_head_output(mega_store), + } + }) + + +def get_opt_head_block_root(spec, mega_store): + """ + Copied and modified from fork-choice spec `get_head` function. + """ + store = mega_store.fc_store + + # Get filtered block tree that only includes viable branches + blocks = spec.get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root for root in blocks.keys() + if ( + blocks[root].parent_root == head + and not is_invalidated(mega_store, root) # For optimistic sync + ) + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + # Ties broken by favoring block with lexicographically higher root + head = max(children, key=lambda root: (spec.get_latest_attesting_balance(store, root), root)) + + +def is_invalidated(mega_store, block_root): + if block_root in mega_store.block_payload_statuses: + return mega_store.block_payload_statuses[block_root].status.alias == PayloadStatusV1StatusAlias.INVALIDATED + else: + return False + + +def get_formatted_optimistic_head_output(mega_store): + head = mega_store.opt_store.head_block_root + slot = mega_store.fc_store.blocks[head].slot + return { + 'slot': int(slot), + 'root': encode_hex(head), + } + + +def clean_up_store(mega_store): + """ + Remove invalidated blocks + """ + # TODO + ... diff --git a/tests/generators/sync/main.py b/tests/generators/sync/main.py new file mode 100644 index 000000000..ad83b78a0 --- /dev/null +++ b/tests/generators/sync/main.py @@ -0,0 +1,14 @@ +from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators +from eth2spec.test.helpers.constants import BELLATRIX + + +if __name__ == "__main__": + bellatrix_mods = {key: 'eth2spec.test.bellatrix.sync.test_' + key for key in [ + 'optimistic', + ]} + + all_mods = { + BELLATRIX: bellatrix_mods, + } + + run_state_test_generators(runner_name="sync", all_mods=all_mods) diff --git a/tests/generators/sync/requirements.txt b/tests/generators/sync/requirements.txt new file mode 100644 index 000000000..735f863fa --- /dev/null +++ b/tests/generators/sync/requirements.txt @@ -0,0 +1,2 @@ +pytest>=4.4 +../../../[generator] \ No newline at end of file From ac717b106af596fc5758b0004b98badefcf18986 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 31 Aug 2022 22:02:47 +0800 Subject: [PATCH 2/4] Address PR feedback from @mkalinin --- .../test/bellatrix/sync/test_optimistic.py | 5 +++-- .../pyspec/eth2spec/test/helpers/fork_choice.py | 6 +++--- .../eth2spec/test/helpers/optimistic_sync.py | 14 ++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py index 899c6b5ab..cf757b583 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py @@ -79,7 +79,7 @@ def test_from_syncing_to_invalid(spec, state): 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) + status=PayloadStatusV1Status.SYNCING, valid=False) assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root # Now add block 4 to chain `b` with INVALID @@ -92,7 +92,8 @@ def test_from_syncing_to_invalid(spec, state): 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) + yield from add_optimistic_block(spec, mega_store, signed_block, test_steps, + payload_status=payload_status, valid=False) assert mega_store.opt_store.head_block_root == signed_blocks_a[-1].message.hash_tree_root() yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index a1d2e6ffc..bd8abd95b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -137,9 +137,9 @@ def add_block(spec, return else: assert False - - run_on_block(spec, store, signed_block, valid=True) - test_steps.append({'block': get_block_file_name(signed_block)}) + 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: diff --git a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py index 3fc31e5b0..d88513a8a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py +++ b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py @@ -76,7 +76,14 @@ def get_optimistic_store(spec, anchor_state, anchor_block): def add_optimistic_block(spec, mega_store, signed_block, test_steps, - payload_status=None, status=PayloadStatusV1Status.SYNCING): + payload_status=None, status=PayloadStatusV1Status.SYNCING, + valid=True): + """ + Add a block with optimistic sync logic + + ``valid`` indicates if the given ``signed_block.message.body.execution_payload`` is valid/invalid + from ``notify_new_payload`` method response. + """ block = signed_block.message block_root = block.hash_tree_root() el_block_hash = block.body.execution_payload.block_hash @@ -93,8 +100,6 @@ def add_optimistic_block(spec, mega_store, signed_block, test_steps, # Optimistic sync - valid = True - # Case: INVALID if payload_status.status == PayloadStatusV1Status.INVALID: # Update parent status to INVALID @@ -108,9 +113,6 @@ def add_optimistic_block(spec, mega_store, signed_block, test_steps, current_block = mega_store.fc_store.blocks[current_block.parent_root] el_block_hash = current_block.body.execution_payload.block_hash - if payload_status.status != PayloadStatusV1Status.VALID: - valid = False - yield from add_block(spec, mega_store.fc_store, signed_block, valid=valid, test_steps=test_steps, From 0f8b5ae6bd3fe8a0b36b875a3553d04012bdfac5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 2 Sep 2022 20:41:55 +0800 Subject: [PATCH 3/4] Apply PR feedback from @michaelsproul --- .../pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py | 6 +++++- tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py index 1d2b03fd2..dccb9313f 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py @@ -9,7 +9,6 @@ import sys import json from typing import Iterable, AnyStr, Any, Callable import traceback - from ruamel.yaml import ( YAML, ) @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py index d88513a8a..a89c3083e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py +++ b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py @@ -42,9 +42,9 @@ class PayloadStatusV1: @property def formatted_output(self): return { - 'status': str(self.status), - 'latest_valid_hash': encode_hex(self.latest_valid_hash) if self.latest_valid_hash is not None else 'null', - 'validation_error': str(self.validation_error) if self.validation_error is not None else 'null' + 'status': str(self.status.value), + 'latest_valid_hash': encode_hex(self.latest_valid_hash) if self.latest_valid_hash is not None else None, + 'validation_error': str(self.validation_error) if self.validation_error is not None else None } @@ -95,6 +95,7 @@ def add_optimistic_block(spec, mega_store, signed_block, test_steps, mega_store.block_payload_statuses[block_root] = payload_status test_steps.append({ + 'block_hash': encode_hex(el_block_hash), 'payload_status': payload_status.formatted_output, }) From 2e7309125866b6f60961f569bb9c2638a2e11b9e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 8 Sep 2022 22:35:53 +0800 Subject: [PATCH 4/4] Add `get_valid_flag_value` helper to determine the `valid` flag value --- .../test/bellatrix/sync/test_optimistic.py | 4 ++-- .../eth2spec/test/helpers/optimistic_sync.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py index cf757b583..4fb8adbaf 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py @@ -79,7 +79,7 @@ def test_from_syncing_to_invalid(spec, state): 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, valid=False) + 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 @@ -93,7 +93,7 @@ def test_from_syncing_to_invalid(spec, state): validation_error="invalid", ) yield from add_optimistic_block(spec, mega_store, signed_block, test_steps, - payload_status=payload_status, valid=False) + payload_status=payload_status) assert mega_store.opt_store.head_block_root == signed_blocks_a[-1].message.hash_tree_root() yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py index a89c3083e..6f42aa9ba 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py +++ b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py @@ -75,9 +75,18 @@ def get_optimistic_store(spec, anchor_state, anchor_block): return opt_store +def get_valid_flag_value(status: PayloadStatusV1Status) -> bool: + if status == PayloadStatusV1Status.VALID: + return True + elif status.alias == PayloadStatusV1StatusAlias.NOT_VALIDATED: + return True + else: + # status.alias == PayloadStatusV1StatusAlias.INVALIDATED or other cases + return False + + def add_optimistic_block(spec, mega_store, signed_block, test_steps, - payload_status=None, status=PayloadStatusV1Status.SYNCING, - valid=True): + payload_status=None, status=PayloadStatusV1Status.SYNCING): """ Add a block with optimistic sync logic @@ -99,6 +108,9 @@ def add_optimistic_block(spec, mega_store, signed_block, test_steps, 'payload_status': payload_status.formatted_output, }) + # Set `valid` flag + valid = get_valid_flag_value(payload_status.status) + # Optimistic sync # Case: INVALID