Add functions for deriving light client data

Adds `create_light_client_bootstrap` and `create_light_client_update`
functions as a reference implementation for serving light client data.
This also enables a new test harness to verify that light client data
gets applied to a `LightClientStore` as expected.
This commit is contained in:
Etan Kissling 2022-05-03 01:17:46 +02:00
parent 3b6222d74e
commit 86fe93ca96
No known key found for this signature in database
GPG Key ID: B21DA824C5A3D03D
7 changed files with 599 additions and 1 deletions

View File

@ -457,6 +457,7 @@ class AltairSpecBuilder(Phase0SpecBuilder):
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
'''

View File

@ -31,6 +31,9 @@
- [`validate_light_client_update`](#validate_light_client_update)
- [`apply_light_client_update`](#apply_light_client_update)
- [`process_light_client_update`](#process_light_client_update)
- [Deriving light client data](#deriving-light-client-data)
- [`create_light_client_bootstrap`](#create_light_client_bootstrap)
- [`create_light_client_update`](#create_light_client_update)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -417,3 +420,105 @@ def process_light_client_update(store: LightClientStore,
apply_light_client_update(store, update)
store.best_valid_update = None
```
## 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=build_proof(state.get_backing(), 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`
```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 = build_proof(attested_state.get_backing(), 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:
finalized_header = BeaconBlockHeader()
assert attested_state.finalized_checkpoint.root == Bytes32()
finality_branch = build_proof(attested_state.get_backing(), 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.

View File

@ -0,0 +1,424 @@
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 finalize_test(test):
yield "steps", test.steps
yield "expected_finalized_header", test.store.finalized_header
yield "expected_optimistic_header", test.store.optimistic_header
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 emit_slot(test, spec, state):
current_slot = state.slot
yield from []
test.steps.append({
"process_slot": {
"current_slot": int(current_slot),
}
})
spec.process_slot_for_light_client_store(test.store, current_slot)
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
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),
}
})
spec.process_light_client_update(test.store, update, current_slot, test.genesis_validators_root)
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):
if state.fork.current_version == spec.config.GENESIS_FORK_VERSION:
return # Test requires Altair or later
# 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 finalize_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):
if state.fork.current_version == spec.config.GENESIS_FORK_VERSION:
return # Test requires Altair or later
# 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 finalize_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):
if state.fork.current_version == spec.config.GENESIS_FORK_VERSION:
return # Test requires Altair or later
# 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 finalize_test(test)

View File

@ -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

View File

@ -3,4 +3,5 @@
This series of tests provides reference test vectors for the light client sync protocol spec.
Handlers:
- `light_client_sync`: see [Light client sync test format](./light_client_sync.md)
- `update_ranking`: see [`LightClientUpdate` ranking test format](./update_ranking.md)

View File

@ -0,0 +1,59 @@
# 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. There may be multiple items of the following types:
#### `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
}
```
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
}
```
After this step, the `store` object may have been updated.
### `expected_finalized_header.ssz_snappy`
An SSZ-snappy encoded `expected_finalized_header` object of type `BeaconBlockHeader` that represents the expected `store.finalized_header` after applying all the test steps.
### `expected_optimistic_header.ssz_snappy`
An SSZ-snappy encoded `expected_optimistic_header` object of type `BeaconBlockHeader` that represents the expected `store.finalized_header` after applying all the test steps.
## 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. Finally, it should verify that the resulting `store` refers to the provided `expected_finalized_header` and `expected_optimistic_header`.

View File

@ -4,6 +4,7 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
if __name__ == "__main__":
altair_mods = {key: 'eth2spec.test.altair.sync_protocol.test_' + key for key in [
'light_client_sync',
'update_ranking',
]}
bellatrix_mods = altair_mods