diff --git a/Makefile b/Makefile index 6afe429937..725e64de87 100644 --- a/Makefile +++ b/Makefile @@ -23,13 +23,15 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER # To check generator matching: #$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) -MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \ +MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) \ + $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SPEC_DIR)/altair/**/*.md) \ $(wildcard $(SPEC_DIR)/bellatrix/*.md) \ $(wildcard $(SPEC_DIR)/capella/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ $(wildcard $(SPEC_DIR)/sharding/*.md) \ - $(wildcard $(SPEC_DIR)/eip4844/*.md) + $(wildcard $(SPEC_DIR)/eip4844/*.md) \ + $(wildcard $(SSZ_DIR)/*.md) COV_HTML_OUT=.htmlcov COV_HTML_OUT_DIR=$(PY_SPEC_DIR)/$(COV_HTML_OUT) diff --git a/README.md b/README.md index fcae73a321..9431dd2015 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ Features are researched and developed in parallel, and then consolidated into se | Seq. | Code Name | Fork Epoch | Specs | | - | - | - | - | | 0 | **Phase0** |`0` | | -| 1 | **Altair** | `74240` | | +| 1 | **Altair** | `74240` | | | 2 | **Bellatrix**
(["The Merge"](https://ethereum.org/en/upgrades/merge/)) | TBD | | ### In-development Specifications -| Code Name or Topic | Specs | Notes | +| Code Name or Topic | Specs | Notes | | - | - | - | | Capella (tentative) | | | EIP4844 (tentative) | | diff --git a/setup.py b/setup.py index 42ddc69021..c8f0cea0c2 100644 --- a/setup.py +++ b/setup.py @@ -458,6 +458,7 @@ def imports(cls, preset_name: str) -> str: from typing import NewType, Union as PyUnion from eth2spec.phase0 import {preset_name} as phase0 +from eth2spec.test.helpers.merkle import build_proof from eth2spec.utils.ssz.ssz_typing import Path ''' @@ -475,7 +476,12 @@ def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariab ssz_path = Path(ssz_class) for item in path: ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex())''' + return GeneralizedIndex(ssz_path.gindex()) + + +def compute_merkle_proof_for_state(state: BeaconState, + index: GeneralizedIndex) -> Sequence[Bytes32]: + return build_proof(state.get_backing(), index)''' @classmethod @@ -656,7 +662,11 @@ def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str protocols_spec = '\n\n\n'.join(format_protocol(k, v) for k, v in spec_object.protocols.items()) for k in list(spec_object.functions): - if "ceillog2" in k or "floorlog2" in k: + if k in [ + "ceillog2", + "floorlog2", + "compute_merkle_proof_for_state", + ]: del spec_object.functions[k] functions = builder.implement_optimizations(spec_object.functions) functions_spec = '\n\n\n'.join(functions.values()) @@ -817,7 +827,7 @@ def parse_config_vars(conf: Dict[str, str]) -> Dict[str, str]: for k, v in conf.items(): if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE' or k == 'CONFIG_NAME'): # Represent byte data with string, to avoid misinterpretation as big-endian int. - # Everything is either byte data or an integer, with PRESET_BASE as one exception. + # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. out[k] = f"'{v}'" else: out[k] = str(int(v)) @@ -923,12 +933,13 @@ def finalize_options(self): """ if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA, EIP4844): self.md_doc_paths += """ + specs/altair/light-client/full-node.md + specs/altair/light-client/sync-protocol.md specs/altair/beacon-chain.md specs/altair/bls.md specs/altair/fork.md specs/altair/validator.md specs/altair/p2p-interface.md - specs/altair/sync-protocol.md """ if self.spec_fork in (BELLATRIX, CAPELLA, EIP4844): self.md_doc_paths += """ diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md new file mode 100644 index 0000000000..a7dd551015 --- /dev/null +++ b/specs/altair/light-client/full-node.md @@ -0,0 +1,135 @@ +# Altair Light Client -- Full Node + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [`compute_merkle_proof_for_state`](#compute_merkle_proof_for_state) +- [Deriving light client data](#deriving-light-client-data) + - [`create_light_client_bootstrap`](#create_light_client_bootstrap) + - [`create_light_client_update`](#create_light_client_update) + + + + +## Introduction + +This document provides helper functions to enable full nodes to serve light client data. Full nodes SHOULD implement the described functionality to enable light clients to sync with the network. + +## Helper functions + +### `compute_merkle_proof_for_state` + +```python +def compute_merkle_proof_for_state(state: BeaconState, + index: GeneralizedIndex) -> Sequence[Bytes32]: + ... +``` + +## Deriving light client data + +Full nodes are expected to derive light client data from historic blocks and states and provide it to other clients. + +### `create_light_client_bootstrap` + +```python +def create_light_client_bootstrap(state: BeaconState) -> LightClientBootstrap: + assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH + assert state.slot == state.latest_block_header.slot + + return LightClientBootstrap( + header=BeaconBlockHeader( + slot=state.latest_block_header.slot, + proposer_index=state.latest_block_header.proposer_index, + parent_root=state.latest_block_header.parent_root, + state_root=hash_tree_root(state), + body_root=state.latest_block_header.body_root, + ), + current_sync_committee=state.current_sync_committee, + current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX) + ) +``` + +Full nodes SHOULD provide `LightClientBootstrap` for all finalized epoch boundary blocks in the epoch range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientBootstrap` for other blocks. + +Blocks are considered to be epoch boundary blocks if their block root can occur as part of a valid `Checkpoint`, i.e., if their slot is the initial slot of an epoch, or if all following slots through the initial slot of the next epoch are empty (no block proposed / orphaned). + +`LightClientBootstrap` is computed from the block's immediate post state (without applying empty slots). + +### `create_light_client_update` + +To form a `LightClientUpdate`, the following historical states and blocks are needed: +- `state`: the post state of any block with a post-Altair parent block +- `block`: the corresponding block +- `attested_state`: the post state of the block referred to by `block.parent_root` +- `finalized_block`: the block referred to by `attested_state.finalized_checkpoint.root`, if locally available (may be unavailable, e.g., when using checkpoint sync, or if it was pruned locally) + +```python +def create_light_client_update(state: BeaconState, + block: SignedBeaconBlock, + attested_state: BeaconState, + finalized_block: Optional[SignedBeaconBlock]) -> LightClientUpdate: + assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH + assert sum(block.message.body.sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + assert state.slot == state.latest_block_header.slot + header = state.latest_block_header.copy() + header.state_root = hash_tree_root(state) + assert hash_tree_root(header) == hash_tree_root(block.message) + update_signature_period = compute_sync_committee_period(compute_epoch_at_slot(block.message.slot)) + + assert attested_state.slot == attested_state.latest_block_header.slot + attested_header = attested_state.latest_block_header.copy() + attested_header.state_root = hash_tree_root(attested_state) + assert hash_tree_root(attested_header) == block.message.parent_root + update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot)) + + # `next_sync_committee` is only useful if the message is signed by the current sync committee + if update_attested_period == update_signature_period: + next_sync_committee = attested_state.next_sync_committee + next_sync_committee_branch = compute_merkle_proof_for_state(attested_state, NEXT_SYNC_COMMITTEE_INDEX) + else: + next_sync_committee = SyncCommittee() + next_sync_committee_branch = [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] + + # Indicate finality whenever possible + if finalized_block is not None: + if finalized_block.message.slot != GENESIS_SLOT: + finalized_header = BeaconBlockHeader( + slot=finalized_block.message.slot, + proposer_index=finalized_block.message.proposer_index, + parent_root=finalized_block.message.parent_root, + state_root=finalized_block.message.state_root, + body_root=hash_tree_root(finalized_block.message.body), + ) + assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root + else: + assert attested_state.finalized_checkpoint.root == Bytes32() + finalized_header = BeaconBlockHeader() + finality_branch = compute_merkle_proof_for_state(attested_state, FINALIZED_ROOT_INDEX) + else: + finalized_header = BeaconBlockHeader() + finality_branch = [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] + + return LightClientUpdate( + attested_header=attested_header, + next_sync_committee=next_sync_committee, + next_sync_committee_branch=next_sync_committee_branch, + finalized_header=finalized_header, + finality_branch=finality_branch, + sync_aggregate=block.message.body.sync_aggregate, + signature_slot=block.message.slot, + ) +``` + +Full nodes SHOULD provide the best derivable `LightClientUpdate` (according to `is_better_update`) for each sync committee period covering any epochs in range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientUpdate` for other sync committee periods. + +- `LightClientUpdate` are assigned to sync committee periods based on their `attested_header.slot` +- `LightClientUpdate` are only considered if `compute_sync_committee_period(compute_epoch_at_slot(update.attested_header.slot)) == compute_sync_committee_period(compute_epoch_at_slot(update.signature_slot))` +- Only `LightClientUpdate` with `next_sync_committee` as selected by fork choice are provided, regardless of ranking by `is_better_update`. To uniquely identify a non-finalized sync committee fork, all of `period`, `current_sync_committee` and `next_sync_committee` need to be incorporated, as sync committees may reappear over time. diff --git a/specs/altair/sync-protocol.md b/specs/altair/light-client/sync-protocol.md similarity index 99% rename from specs/altair/sync-protocol.md rename to specs/altair/light-client/sync-protocol.md index 8d420eda56..217171db18 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/light-client/sync-protocol.md @@ -1,4 +1,4 @@ -# Altair -- Minimal Light Client +# Altair Light Client -- Sync Protocol **Notice**: This document is a work-in-progress for researchers and implementers. @@ -45,6 +45,9 @@ and metered VMs (e.g. blockchain VMs for cross-chain bridges). This document suggests a minimal light client design for the beacon chain that uses sync committees introduced in [this beacon chain extension](./beacon-chain.md). +Additional documents describe how the light client sync protocol can be used: +- [Full node](./full-node.md) + ## Constants | Name | Value | @@ -307,8 +310,8 @@ def validate_light_client_update(store: LightClientStore, assert update.finalized_header == BeaconBlockHeader() else: if update.finalized_header.slot == GENESIS_SLOT: - finalized_root = Bytes32() assert update.finalized_header == BeaconBlockHeader() + finalized_root = Bytes32() else: finalized_root = hash_tree_root(update.finalized_header) assert is_valid_merkle_branch( diff --git a/specs/altair/validator.md b/specs/altair/validator.md index c59aa29f71..6265353749 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -54,7 +54,7 @@ It builds on the [previous document for the behavior of an "honest validator" fr This previous document is referred to below as the "Phase 0 document". Altair introduces a new type of committee: the sync committee. Sync committees are responsible for signing each block of the canonical chain and there exists an efficient algorithm for light clients to sync the chain using the output of the sync committees. -See the [sync protocol](./sync-protocol.md) for further details on the light client sync. +See the [sync protocol](./light-client/sync-protocol.md) for further details on the light client sync. Under this network upgrade, validators track their participation in this new committee type and produce the relevant signatures as required. Block proposers incorporate the (aggregated) sync committee signatures into each block they produce. @@ -265,7 +265,7 @@ This process occurs each slot. ##### Prepare sync committee message -If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. +If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. This logic is triggered upon the same conditions as when producing an attestation. Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot) -- whichever comes first. 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 3e5d71d148..1d2b03fd24 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 @@ -1,3 +1,4 @@ +from eth_utils import encode_hex import os import time import shutil @@ -97,6 +98,20 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): yaml = YAML(pure=True) yaml.default_flow_style = 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 + + def cfg_represent_bytes(self, data): + return self.represent_int(encode_hex(data)) + + cfg_yaml.representer.add_representer(bytes, cfg_represent_bytes) + + def cfg_represent_quoted_str(self, data): + return self.represent_scalar(u'tag:yaml.org,2002:str', data, style="'") + + cfg_yaml.representer.add_representer(context.quoted_str, cfg_represent_quoted_str) + log_file = Path(output_dir) / 'testgen_error_log.txt' print(f"Generating tests into {output_dir}") @@ -169,10 +184,14 @@ def output_part(out_kind: str, name: str, fn: Callable[[Path, ], None]): written_part = True if out_kind == "meta": meta[name] = data - if out_kind == "data": - output_part("data", name, dump_yaml_fn(data, name, file_mode, yaml)) - if out_kind == "ssz": - output_part("ssz", name, dump_ssz_fn(data, name, file_mode)) + elif out_kind == "cfg": + output_part(out_kind, name, dump_yaml_fn(data, name, file_mode, cfg_yaml)) + elif out_kind == "data": + output_part(out_kind, name, dump_yaml_fn(data, name, file_mode, yaml)) + elif out_kind == "ssz": + output_part(out_kind, name, dump_ssz_fn(data, name, file_mode)) + else: + assert False # Unknown kind except SkippedTest as e: print(e) skipped_test_count += 1 diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py index 669238d1c3..2bb66d06c7 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py @@ -10,9 +10,10 @@ # Elements: name, out_kind, data # # out_kind is the type of data: +# - "meta" for generic data to collect into a meta data dict +# - "cfg" for a spec config dictionary # - "data" for generic # - "ssz" for SSZ encoded bytes -# - "meta" for generic data to collect into a meta data dict. TestCasePart = NewType("TestCasePart", Tuple[str, str, Any]) diff --git a/tests/core/pyspec/eth2spec/test/altair/sync_protocol/__init__.py b/tests/core/pyspec/eth2spec/test/altair/light_client/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/altair/sync_protocol/__init__.py rename to tests/core/pyspec/eth2spec/test/altair/light_client/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py new file mode 100644 index 0000000000..9448302e75 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py @@ -0,0 +1,430 @@ +from typing import (Any, Dict, List) + +from eth_utils import encode_hex +from eth2spec.test.context import ( + spec_state_test_with_matching_config, + with_presets, + with_altair_and_later, +) +from eth2spec.test.helpers.attestations import ( + next_slots_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.light_client import ( + get_sync_aggregate, +) +from eth2spec.test.helpers.state import ( + next_slots, + transition_to, +) + + +def setup_test(spec, state): + class LightClientSyncTest(object): + steps: List[Dict[str, Any]] + genesis_validators_root: spec.Root + store: spec.LightClientStore + + test = LightClientSyncTest() + test.steps = [] + + yield "genesis_validators_root", "meta", "0x" + state.genesis_validators_root.hex() + test.genesis_validators_root = state.genesis_validators_root + + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2 - 1) + trusted_block = state_transition_with_full_block(spec, state, True, True) + trusted_block_root = trusted_block.message.hash_tree_root() + bootstrap = spec.create_light_client_bootstrap(state) + yield "trusted_block_root", "meta", "0x" + trusted_block_root.hex() + yield "bootstrap", bootstrap + test.store = spec.initialize_light_client_store(trusted_block_root, bootstrap) + + return test + + +def finish_test(test): + yield "steps", test.steps + + +def get_update_file_name(spec, update): + if spec.is_sync_committee_update(update): + suffix1 = "s" + else: + suffix1 = "x" + if spec.is_finality_update(update): + suffix2 = "f" + else: + suffix2 = "x" + return f"update_{encode_hex(update.attested_header.hash_tree_root())}_{suffix1}{suffix2}" + + +def get_checks(store): + return { + "finalized_header": { + 'slot': int(store.finalized_header.slot), + 'root': encode_hex(store.finalized_header.hash_tree_root()), + }, + "optimistic_header": { + 'slot': int(store.optimistic_header.slot), + 'root': encode_hex(store.optimistic_header.hash_tree_root()), + }, + } + + +def emit_slot(test, spec, state): + current_slot = state.slot + spec.process_slot_for_light_client_store(test.store, current_slot) + + yield from [] # Consistently enable `yield from` syntax in calling tests + test.steps.append({ + "process_slot": { + "current_slot": int(current_slot), + "checks": get_checks(test.store), + } + }) + + +def emit_update(test, spec, state, block, attested_state, finalized_block, with_next_sync_committee=True): + update = spec.create_light_client_update(state, block, attested_state, finalized_block) + if not with_next_sync_committee: + update.next_sync_committee = spec.SyncCommittee() + update.next_sync_committee_branch = \ + [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] + current_slot = state.slot + spec.process_light_client_update(test.store, update, current_slot, test.genesis_validators_root) + + yield get_update_file_name(spec, update), update + test.steps.append({ + "process_update": { + "update": get_update_file_name(spec, update), + "current_slot": int(current_slot), + "checks": get_checks(test.store), + } + }) + return update + + +def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): + return spec.compute_start_slot_at_epoch(sync_committee_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + + +def compute_start_slot_at_next_sync_committee_period(spec, state): + sync_committee_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot)) + return compute_start_slot_at_sync_committee_period(spec, sync_committee_period + 1) + + +@with_altair_and_later +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_light_client_sync(spec, state): + # Start test + test = yield from setup_test(spec, state) + + # Initial `LightClientUpdate`, populating `store.next_sync_committee` + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Advance to next sync committee period + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Edge case: Signature in next period + # ``` + # | + # +-----------+ +----------+ | +-----------+ + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | + # +-----------+ +----------+ | +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 2) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Edge case: Finalized header not included + # ``` + # | + # + - - - - - + | +----------+ +-----------+ + # ¦ finalized ¦ <-- (2 epochs) -- | attested | <-- | signature | + # + - - - - - + | +----------+ +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update(test, spec, state, block, attested_state, finalized_block=None) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.slot == attested_state.slot + + # Non-finalized case: Attested `next_sync_committee` is not finalized + # ``` + # | + # +-----------+ | +----------+ +-----------+ + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | + # +-----------+ | +----------+ +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + attested_state = state.copy() + store_state = attested_state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.slot == attested_state.slot + + # Force-update using timeout + # ``` + # | + # +-----------+ | +----------+ + # | finalized | <-- (2 epochs) -- | attested | + # +-----------+ | +----------+ + # | ^ + # | \ + # sync committee `--- store.finalized_header + # period boundary + # ``` + attested_state = state.copy() + next_slots(spec, state, spec.UPDATE_TIMEOUT - 1) + yield from emit_slot(test, spec, state) + assert test.store.finalized_header.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == store_state.slot + + # Edge case: Finalized header not included, after force-update + # ``` + # | | + # + - - - - - + | +--+ +----------+ | +-----------+ + # ¦ finalized ¦ <-- (2 epochs) -- | | <-- | attested | <-- | signature | + # + - - - - - + | +--+ +----------+ | +-----------+ + # | / | + # | store.fin | + # sync committee sync committee + # period boundary period boundary + # ``` + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update(test, spec, state, block, attested_state, finalized_block=None) + assert test.store.finalized_header.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.slot == attested_state.slot + + # Edge case: Finalized header older than store + # ``` + # | | + # +-----------+ | +--+ | +----------+ +-----------+ + # | finalized | <-- (2 epochs) -- | | <-- | attested | <-- | signature | + # +-----------+ | +--+ | +----------+ +-----------+ + # | / | + # | store.fin | + # sync committee sync committee + # period boundary period boundary + # ``` + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.slot == attested_state.slot + yield from emit_slot(test, spec, state) + assert test.store.finalized_header.slot == attested_state.slot + assert test.store.next_sync_committee == attested_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Advance to next sync committee period + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Finish test + yield from finish_test(test) + + +@with_altair_and_later +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_supply_sync_committee_from_past_update(spec, state): + # Advance the chain, so that a `LightClientUpdate` from the past is available + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2 - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + past_state = state.copy() + + # Start test + test = yield from setup_test(spec, state) + assert not spec.is_next_sync_committee_known(test.store) + + # Apply `LightClientUpdate` from the past, populating `store.next_sync_committee` + yield from emit_update(test, spec, past_state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == state.slot + + # Finish test + yield from finish_test(test) + + +@with_altair_and_later +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_advance_finality_without_sync_committee(spec, state): + # Start test + test = yield from setup_test(spec, state) + + # Initial `LightClientUpdate`, populating `store.next_sync_committee` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Advance finality into next sync committee period, but omit `next_sync_committee` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) + justified_block = state_transition_with_full_block(spec, state, True, True) + justified_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, finalized_block, with_next_sync_committee=False) + assert test.store.finalized_header.slot == finalized_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Advance finality once more, with `next_sync_committee` still unknown + past_state = finalized_state + finalized_block = justified_block + finalized_state = justified_state + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + + # Apply `LightClientUpdate` without `finalized_header` nor `next_sync_committee` + update = yield from emit_update(test, spec, state, block, attested_state, None, with_next_sync_committee=False) + assert test.store.finalized_header.slot == past_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update == update + assert test.store.optimistic_header.slot == attested_state.slot + + # Apply `LightClientUpdate` with `finalized_header` but no `next_sync_committee` + yield from emit_update(test, spec, state, block, attested_state, finalized_block, with_next_sync_committee=False) + assert test.store.finalized_header.slot == finalized_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Apply full `LightClientUpdate`, supplying `next_sync_committee` + yield from emit_update(test, spec, state, block, attested_state, finalized_block) + assert test.store.finalized_header.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.slot == attested_state.slot + + # Finish test + yield from finish_test(test) diff --git a/tests/core/pyspec/eth2spec/test/altair/sync_protocol/test_update_ranking.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py similarity index 97% rename from tests/core/pyspec/eth2spec/test/altair/sync_protocol/test_update_ranking.py rename to tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py index 4b73a15ba8..ae5d5296a8 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sync_protocol/test_update_ranking.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py @@ -15,7 +15,6 @@ from eth2spec.test.helpers.state import ( next_slots, ) -from eth2spec.test.helpers.merkle import build_proof from math import floor @@ -27,14 +26,14 @@ def create_update(spec, test, with_next_sync_committee, with_finality, participa if with_next_sync_committee: next_sync_committee = attested_state.next_sync_committee - next_sync_committee_branch = build_proof(attested_state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) + next_sync_committee_branch = spec.compute_merkle_proof_for_state(attested_state, spec.NEXT_SYNC_COMMITTEE_INDEX) else: next_sync_committee = spec.SyncCommittee() next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] if with_finality: finalized_header = signed_block_to_header(spec, finalized_block) - finality_branch = build_proof(attested_state.get_backing(), spec.FINALIZED_ROOT_INDEX) + finality_branch = spec.compute_merkle_proof_for_state(attested_state, spec.FINALIZED_ROOT_INDEX) else: finalized_header = spec.BeaconBlockHeader() finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] diff --git a/tests/core/pyspec/eth2spec/test/altair/merkle/test_single_proof.py b/tests/core/pyspec/eth2spec/test/altair/merkle/test_single_proof.py index d90feb1466..064760bf02 100644 --- a/tests/core/pyspec/eth2spec/test/altair/merkle/test_single_proof.py +++ b/tests/core/pyspec/eth2spec/test/altair/merkle/test_single_proof.py @@ -2,14 +2,13 @@ spec_state_test, with_altair_and_later, ) -from eth2spec.test.helpers.merkle import build_proof @with_altair_and_later @spec_state_test def test_current_sync_committee_merkle_proof(spec, state): yield "state", state - current_sync_committee_branch = build_proof(state.get_backing(), spec.CURRENT_SYNC_COMMITTEE_INDEX) + current_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.CURRENT_SYNC_COMMITTEE_INDEX) yield "proof", { "leaf": "0x" + state.current_sync_committee.hash_tree_root().hex(), "leaf_index": spec.CURRENT_SYNC_COMMITTEE_INDEX, @@ -28,7 +27,7 @@ def test_current_sync_committee_merkle_proof(spec, state): @spec_state_test def test_next_sync_committee_merkle_proof(spec, state): yield "state", state - next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) + next_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX) yield "proof", { "leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(), "leaf_index": spec.NEXT_SYNC_COMMITTEE_INDEX, @@ -47,7 +46,7 @@ def test_next_sync_committee_merkle_proof(spec, state): @spec_state_test def test_finality_root_merkle_proof(spec, state): yield "state", state - finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX) + finality_branch = spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX) yield "proof", { "leaf": "0x" + state.finalized_checkpoint.root.hex(), "leaf_index": spec.FINALIZED_ROOT_INDEX, diff --git a/tests/generators/sync_protocol/__init__.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/__init__.py similarity index 100% rename from tests/generators/sync_protocol/__init__.py rename to tests/core/pyspec/eth2spec/test/altair/unittests/light_client/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py similarity index 97% rename from tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py rename to tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py index 17c450f170..e7845292af 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py @@ -18,7 +18,6 @@ from eth2spec.test.helpers.state import ( next_slots, ) -from eth2spec.test.helpers.merkle import build_proof @with_altair_and_later @@ -125,7 +124,7 @@ def test_process_light_client_update_timeout(spec, state): # Sync committee is updated next_sync_committee = state.next_sync_committee - next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) + next_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX) # Finality is unchanged finality_header = spec.BeaconBlockHeader() finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] @@ -182,7 +181,7 @@ def test_process_light_client_update_finality_updated(spec, state): finalized_header = signed_block_to_header(spec, finalized_block) assert finalized_header.slot == spec.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) assert finalized_header.hash_tree_root() == state.finalized_checkpoint.root - finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX) + finality_branch = spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX) update = spec.LightClientUpdate( attested_header=attested_header, diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index e46e41a701..bc04c05f2c 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,7 +1,6 @@ import pytest from dataclasses import dataclass import importlib -from eth_utils import encode_hex from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal @@ -523,18 +522,22 @@ def wrapper(*args, spec: Spec, **kw): return decorator +class quoted_str(str): + pass + + def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]: """ - Get dict of Python built-in types from a dict of SSZ objects. + Get dict of basic types from a dict of SSZ objects. """ result = {} for k, v in ssz_dict.items(): if isinstance(v, int): value = int(v) elif isinstance(v, bytes): - value = encode_hex(v) + value = bytes(bytearray(v)) else: - value = str(v) + value = quoted_str(v) result[k] = value return result @@ -570,7 +573,7 @@ def wrapper(*args, spec: Spec, **kw): # To output the changed config to could be serialized with yaml test vectors, # the dict SSZ objects have to be converted into Python built-in types. output_config = _get_basic_dict(modified_config) - yield 'config', 'data', output_config + yield 'config', 'cfg', output_config spec.config = spec.Configuration(**modified_config) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index ffd484ecdf..7ba71a9693 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -246,7 +246,12 @@ def next_epoch_with_attestations(spec, ) -def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None): +def state_transition_with_full_block(spec, + state, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=None, + sync_aggregate=None): """ Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch. """ @@ -272,6 +277,8 @@ def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoc ) for attestation in attestations: block.body.attestations.append(attestation) + if sync_aggregate is not None: + block.body.sync_aggregate = sync_aggregate signed_block = state_transition_and_sign_block(spec, state, block) return signed_block diff --git a/tests/core/pyspec/eth2spec/test/utils/utils.py b/tests/core/pyspec/eth2spec/test/utils/utils.py index bad6c867bd..14a7ceeb95 100644 --- a/tests/core/pyspec/eth2spec/test/utils/utils.py +++ b/tests/core/pyspec/eth2spec/test/utils/utils.py @@ -17,6 +17,7 @@ def runner(fn): # this wraps the function, to yield type-annotated entries of data. # Valid types are: # - "meta": all key-values with this type can be collected by the generator, to put somewhere together. + # - "cfg": spec config dictionary # - "ssz": raw SSZ bytes # - "data": a python structure to be encoded by the user. def entry(*args, **kw): diff --git a/tests/formats/sync_protocol/README.md b/tests/formats/light_client/README.md similarity index 80% rename from tests/formats/sync_protocol/README.md rename to tests/formats/light_client/README.md index da93972d68..8c7571d4bb 100644 --- a/tests/formats/sync_protocol/README.md +++ b/tests/formats/light_client/README.md @@ -3,4 +3,5 @@ This series of tests provides reference test vectors for the light client sync protocol spec. Handlers: +- `sync`: see [Light client sync test format](./sync.md) - `update_ranking`: see [`LightClientUpdate` ranking test format](./update_ranking.md) diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md new file mode 100644 index 0000000000..31ecf6036f --- /dev/null +++ b/tests/formats/light_client/sync.md @@ -0,0 +1,68 @@ +# Light client sync tests + +This series of tests provides reference test vectors for validating that a light client implementing the sync protocol can sync to the latest block header. + +## Test case format + +### `meta.yaml` + +```yaml +genesis_validators_root: Bytes32 -- string, hex encoded, with 0x prefix +trusted_block_root: Bytes32 -- string, hex encoded, with 0x prefix +``` + +### `bootstrap.ssz_snappy` + +An SSZ-snappy encoded `bootstrap` object of type `LightClientBootstrap` to initialize a local `store` object of type `LightClientStore` using `initialize_light_client_store(trusted_block_rooot, bootstrap)`. + +### `steps.yaml` + +The steps to execute in sequence. + +#### Checks to run after each step + +Each step includes checks to verify the expected impact on the `store` object. + +```yaml +finalized_header: { + slot: int, -- Integer value from store.finalized_header.slot + root: string, -- Encoded 32-byte value from store.finalized_header.hash_tree_root() +} +optimistic_header: { + slot: int, -- Integer value from store.optimistic_header.slot + root: string, -- Encoded 32-byte value from store.optimistic_header.hash_tree_root() +} +``` + +#### `process_slot` execution step + +The function `process_slot_for_light_client_store(store, current_slot)` +should be executed with the specified parameters: + +```yaml +{ + current_slot: int -- integer, decimal + checks: {: value} -- the assertions. +} +``` + +After this step, the `store` object may have been updated. + +#### `process_update` execution step + +The function `process_light_client_update(store, update, current_slot, genesis_validators_root)` should be executed with the specified parameters: + +```yaml +{ + update: string -- name of the `*.ssz_snappy` file to load + as a `LightClientUpdate` object + current_slot: int -- integer, decimal + checks: {: value} -- the assertions. +} +``` + +After this step, the `store` object may have been updated. + +## Condition + +A test-runner should initialize a local `LightClientStore` using the provided `bootstrap` object. It should then proceed to execute all the test steps in sequence. After each step, it should verify that the resulting `store` verifies against the provided `checks`. diff --git a/tests/formats/sync_protocol/update_ranking.md b/tests/formats/light_client/update_ranking.md similarity index 100% rename from tests/formats/sync_protocol/update_ranking.md rename to tests/formats/light_client/update_ranking.md diff --git a/tests/generators/sync_protocol/README.md b/tests/generators/light_client/README.md similarity index 53% rename from tests/generators/sync_protocol/README.md rename to tests/generators/light_client/README.md index f3c8ea3390..7eabc2520c 100644 --- a/tests/generators/sync_protocol/README.md +++ b/tests/generators/light_client/README.md @@ -1,5 +1,5 @@ -# Light client sync protocol tests +# Light client tests The purpose of this test-generator is to provide test-vectors for validating the correct implementation of the light client sync protocol. -Test-format documentation can be found [here](../../formats/sync_protocol/README.md). +Test-format documentation can be found [here](../../formats/light_client/README.md). diff --git a/tests/generators/light_client/__init__.py b/tests/generators/light_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/generators/sync_protocol/main.py b/tests/generators/light_client/main.py similarity index 64% rename from tests/generators/sync_protocol/main.py rename to tests/generators/light_client/main.py index 0fe8e0fe14..ef6c2cbcad 100644 --- a/tests/generators/sync_protocol/main.py +++ b/tests/generators/light_client/main.py @@ -3,7 +3,8 @@ if __name__ == "__main__": - altair_mods = {key: 'eth2spec.test.altair.sync_protocol.test_' + key for key in [ + altair_mods = {key: 'eth2spec.test.altair.light_client.test_' + key for key in [ + 'sync', 'update_ranking', ]} bellatrix_mods = altair_mods @@ -13,4 +14,4 @@ BELLATRIX: bellatrix_mods, } - run_state_test_generators(runner_name="sync_protocol", all_mods=all_mods) + run_state_test_generators(runner_name="light_client", all_mods=all_mods) diff --git a/tests/generators/sync_protocol/requirements.txt b/tests/generators/light_client/requirements.txt similarity index 100% rename from tests/generators/sync_protocol/requirements.txt rename to tests/generators/light_client/requirements.txt