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 typing import NewType, Union as PyUnion
from eth2spec.phase0 import {preset_name} as phase0 from eth2spec.phase0 import {preset_name} as phase0
from eth2spec.test.helpers.merkle import build_proof
from eth2spec.utils.ssz.ssz_typing import Path from eth2spec.utils.ssz.ssz_typing import Path
''' '''

View File

@ -31,6 +31,9 @@
- [`validate_light_client_update`](#validate_light_client_update) - [`validate_light_client_update`](#validate_light_client_update)
- [`apply_light_client_update`](#apply_light_client_update) - [`apply_light_client_update`](#apply_light_client_update)
- [`process_light_client_update`](#process_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 --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC --> <!-- /TOC -->
@ -417,3 +420,105 @@ def process_light_client_update(store: LightClientStore,
apply_light_client_update(store, update) apply_light_client_update(store, update)
store.best_valid_update = None 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. 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: for attestation in attestations:
block.body.attestations.append(attestation) 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) signed_block = state_transition_and_sign_block(spec, state, block)
return signed_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. This series of tests provides reference test vectors for the light client sync protocol spec.
Handlers: Handlers:
- `light_client_sync`: see [Light client sync test format](./light_client_sync.md)
- `update_ranking`: see [`LightClientUpdate` ranking test format](./update_ranking.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__": if __name__ == "__main__":
altair_mods = {key: 'eth2spec.test.altair.sync_protocol.test_' + key for key in [ altair_mods = {key: 'eth2spec.test.altair.sync_protocol.test_' + key for key in [
'light_client_sync',
'update_ranking', 'update_ranking',
]} ]}
bellatrix_mods = altair_mods bellatrix_mods = altair_mods