mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-25 16:55:19 +00:00
Merge pull request #2805 from etan-status/lc-period
Allow light client to verify signatures at period boundary
This commit is contained in:
commit
8cc008d11c
@ -52,7 +52,7 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain.
|
|||||||
|
|
||||||
| Name | Value | Unit | Duration |
|
| Name | Value | Unit | Duration |
|
||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators |
|
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators | |
|
||||||
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | slots | ~27.3 hours |
|
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | slots | ~27.3 hours |
|
||||||
|
|
||||||
## Containers
|
## Containers
|
||||||
@ -73,6 +73,8 @@ class LightClientUpdate(Container):
|
|||||||
sync_aggregate: SyncAggregate
|
sync_aggregate: SyncAggregate
|
||||||
# Fork version for the aggregate signature
|
# Fork version for the aggregate signature
|
||||||
fork_version: Version
|
fork_version: Version
|
||||||
|
# Slot at which the aggregate signature was created (untrusted)
|
||||||
|
signature_slot: Slot
|
||||||
```
|
```
|
||||||
|
|
||||||
### `LightClientStore`
|
### `LightClientStore`
|
||||||
@ -162,15 +164,16 @@ def validate_light_client_update(store: LightClientStore,
|
|||||||
genesis_validators_root: Root) -> None:
|
genesis_validators_root: Root) -> None:
|
||||||
# Verify update slot is larger than slot of current best finalized header
|
# Verify update slot is larger than slot of current best finalized header
|
||||||
active_header = get_active_header(update)
|
active_header = get_active_header(update)
|
||||||
assert current_slot >= active_header.slot > store.finalized_header.slot
|
assert current_slot >= update.signature_slot > active_header.slot > store.finalized_header.slot
|
||||||
|
|
||||||
# Verify update does not skip a sync committee period
|
# Verify update does not skip a sync committee period
|
||||||
finalized_period = compute_sync_committee_period(compute_epoch_at_slot(store.finalized_header.slot))
|
finalized_period = compute_sync_committee_period(compute_epoch_at_slot(store.finalized_header.slot))
|
||||||
update_period = compute_sync_committee_period(compute_epoch_at_slot(active_header.slot))
|
update_period = compute_sync_committee_period(compute_epoch_at_slot(active_header.slot))
|
||||||
assert update_period in (finalized_period, finalized_period + 1)
|
signature_period = compute_sync_committee_period(compute_epoch_at_slot(update.signature_slot))
|
||||||
|
assert signature_period in (finalized_period, finalized_period + 1)
|
||||||
|
|
||||||
# Verify that the `finalized_header`, if present, actually is the finalized header saved in the
|
# Verify that the `finalized_header`, if present, actually is the finalized header saved in the
|
||||||
# state of the `attested header`
|
# state of the `attested_header`
|
||||||
if not is_finality_update(update):
|
if not is_finality_update(update):
|
||||||
assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
|
assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
|
||||||
else:
|
else:
|
||||||
@ -184,10 +187,8 @@ def validate_light_client_update(store: LightClientStore,
|
|||||||
|
|
||||||
# Verify update next sync committee if the update period incremented
|
# Verify update next sync committee if the update period incremented
|
||||||
if update_period == finalized_period:
|
if update_period == finalized_period:
|
||||||
sync_committee = store.current_sync_committee
|
|
||||||
assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
|
assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
|
||||||
else:
|
else:
|
||||||
sync_committee = store.next_sync_committee
|
|
||||||
assert is_valid_merkle_branch(
|
assert is_valid_merkle_branch(
|
||||||
leaf=hash_tree_root(update.next_sync_committee),
|
leaf=hash_tree_root(update.next_sync_committee),
|
||||||
branch=update.next_sync_committee_branch,
|
branch=update.next_sync_committee_branch,
|
||||||
@ -202,6 +203,10 @@ def validate_light_client_update(store: LightClientStore,
|
|||||||
assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
|
assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
|
||||||
|
|
||||||
# Verify sync committee aggregate signature
|
# Verify sync committee aggregate signature
|
||||||
|
if signature_period == finalized_period:
|
||||||
|
sync_committee = store.current_sync_committee
|
||||||
|
else:
|
||||||
|
sync_committee = store.next_sync_committee
|
||||||
participant_pubkeys = [
|
participant_pubkeys = [
|
||||||
pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys)
|
pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys)
|
||||||
if bit
|
if bit
|
||||||
|
@ -39,8 +39,9 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||||||
state_root=signed_block.message.state_root,
|
state_root=signed_block.message.state_root,
|
||||||
body_root=signed_block.message.body.hash_tree_root(),
|
body_root=signed_block.message.body.hash_tree_root(),
|
||||||
)
|
)
|
||||||
# Sync committee signing the header
|
|
||||||
sync_aggregate = get_sync_aggregate(spec, state, block_header, block_root=None)
|
# Sync committee signing the block_header
|
||||||
|
sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header)
|
||||||
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
|
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
|
||||||
|
|
||||||
# Ensure that finality checkpoint is genesis
|
# Ensure that finality checkpoint is genesis
|
||||||
@ -56,12 +57,13 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||||||
finalized_header=finality_header,
|
finalized_header=finality_header,
|
||||||
finality_branch=finality_branch,
|
finality_branch=finality_branch,
|
||||||
sync_aggregate=sync_aggregate,
|
sync_aggregate=sync_aggregate,
|
||||||
fork_version=state.fork.current_version,
|
fork_version=fork_version,
|
||||||
|
signature_slot=signature_slot,
|
||||||
)
|
)
|
||||||
|
|
||||||
pre_store = deepcopy(store)
|
pre_store = deepcopy(store)
|
||||||
|
|
||||||
spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root)
|
spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root)
|
||||||
|
|
||||||
assert store.current_max_active_participants > 0
|
assert store.current_max_active_participants > 0
|
||||||
assert store.optimistic_header == update.attested_header
|
assert store.optimistic_header == update.attested_header
|
||||||
@ -69,6 +71,57 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||||||
assert store.best_valid_update == update
|
assert store.best_valid_update == update
|
||||||
|
|
||||||
|
|
||||||
|
@with_altair_and_later
|
||||||
|
@spec_state_test
|
||||||
|
@with_presets([MINIMAL], reason="too slow")
|
||||||
|
def test_process_light_client_update_at_period_boundary(spec, state):
|
||||||
|
store = initialize_light_client_store(spec, state)
|
||||||
|
|
||||||
|
# Forward to slot before next sync committee period so that next block is final one in period
|
||||||
|
next_slots(spec, state, spec.UPDATE_TIMEOUT - 2)
|
||||||
|
snapshot_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot))
|
||||||
|
update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot))
|
||||||
|
assert snapshot_period == update_period
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
block_header = spec.BeaconBlockHeader(
|
||||||
|
slot=signed_block.message.slot,
|
||||||
|
proposer_index=signed_block.message.proposer_index,
|
||||||
|
parent_root=signed_block.message.parent_root,
|
||||||
|
state_root=signed_block.message.state_root,
|
||||||
|
body_root=signed_block.message.body.hash_tree_root(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync committee signing the block_header
|
||||||
|
sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header)
|
||||||
|
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(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))]
|
||||||
|
|
||||||
|
update = spec.LightClientUpdate(
|
||||||
|
attested_header=block_header,
|
||||||
|
next_sync_committee=state.next_sync_committee,
|
||||||
|
next_sync_committee_branch=next_sync_committee_branch,
|
||||||
|
finalized_header=finality_header,
|
||||||
|
finality_branch=finality_branch,
|
||||||
|
sync_aggregate=sync_aggregate,
|
||||||
|
fork_version=fork_version,
|
||||||
|
signature_slot=signature_slot,
|
||||||
|
)
|
||||||
|
|
||||||
|
pre_store = deepcopy(store)
|
||||||
|
|
||||||
|
spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root)
|
||||||
|
|
||||||
|
assert store.current_max_active_participants > 0
|
||||||
|
assert store.optimistic_header == update.attested_header
|
||||||
|
assert store.best_valid_update == update
|
||||||
|
assert store.finalized_header == pre_store.finalized_header
|
||||||
|
|
||||||
|
|
||||||
@with_altair_and_later
|
@with_altair_and_later
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
@with_presets([MINIMAL], reason="too slow")
|
@with_presets([MINIMAL], reason="too slow")
|
||||||
@ -91,9 +144,8 @@ def test_process_light_client_update_timeout(spec, state):
|
|||||||
body_root=signed_block.message.body.hash_tree_root(),
|
body_root=signed_block.message.body.hash_tree_root(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sync committee signing the finalized_block_header
|
# Sync committee signing the block_header
|
||||||
sync_aggregate = get_sync_aggregate(
|
sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header)
|
||||||
spec, state, block_header, block_root=spec.Root(block_header.hash_tree_root()))
|
|
||||||
|
|
||||||
# Sync committee is updated
|
# Sync committee is updated
|
||||||
next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX)
|
next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX)
|
||||||
@ -108,12 +160,13 @@ def test_process_light_client_update_timeout(spec, state):
|
|||||||
finalized_header=finality_header,
|
finalized_header=finality_header,
|
||||||
finality_branch=finality_branch,
|
finality_branch=finality_branch,
|
||||||
sync_aggregate=sync_aggregate,
|
sync_aggregate=sync_aggregate,
|
||||||
fork_version=state.fork.current_version,
|
fork_version=fork_version,
|
||||||
|
signature_slot=signature_slot,
|
||||||
)
|
)
|
||||||
|
|
||||||
pre_store = deepcopy(store)
|
pre_store = deepcopy(store)
|
||||||
|
|
||||||
spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root)
|
spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root)
|
||||||
|
|
||||||
assert store.current_max_active_participants > 0
|
assert store.current_max_active_participants > 0
|
||||||
assert store.optimistic_header == update.attested_header
|
assert store.optimistic_header == update.attested_header
|
||||||
@ -157,9 +210,8 @@ def test_process_light_client_update_finality_updated(spec, state):
|
|||||||
body_root=block.body.hash_tree_root(),
|
body_root=block.body.hash_tree_root(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sync committee signing the finalized_block_header
|
# Sync committee signing the block_header
|
||||||
sync_aggregate = get_sync_aggregate(
|
sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header)
|
||||||
spec, state, block_header, block_root=spec.Root(block_header.hash_tree_root()))
|
|
||||||
|
|
||||||
update = spec.LightClientUpdate(
|
update = spec.LightClientUpdate(
|
||||||
attested_header=block_header,
|
attested_header=block_header,
|
||||||
@ -168,10 +220,11 @@ def test_process_light_client_update_finality_updated(spec, state):
|
|||||||
finalized_header=finalized_block_header,
|
finalized_header=finalized_block_header,
|
||||||
finality_branch=finality_branch,
|
finality_branch=finality_branch,
|
||||||
sync_aggregate=sync_aggregate,
|
sync_aggregate=sync_aggregate,
|
||||||
fork_version=state.fork.current_version,
|
fork_version=fork_version,
|
||||||
|
signature_slot=signature_slot,
|
||||||
)
|
)
|
||||||
|
|
||||||
spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root)
|
spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root)
|
||||||
|
|
||||||
assert store.current_max_active_participants > 0
|
assert store.current_max_active_participants > 0
|
||||||
assert store.optimistic_header == update.attested_header
|
assert store.optimistic_header == update.attested_header
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
from eth2spec.test.helpers.state import (
|
||||||
|
transition_to,
|
||||||
|
)
|
||||||
from eth2spec.test.helpers.sync_committee import (
|
from eth2spec.test.helpers.sync_committee import (
|
||||||
compute_aggregate_sync_committee_signature,
|
compute_aggregate_sync_committee_signature,
|
||||||
|
compute_committee_indices,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -15,21 +19,31 @@ def initialize_light_client_store(spec, state):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_sync_aggregate(spec, state, block_header, block_root=None, signature_slot=None):
|
def get_sync_aggregate(spec, state, block_header, signature_slot=None):
|
||||||
|
# By default, the sync committee signs the previous slot
|
||||||
if signature_slot is None:
|
if signature_slot is None:
|
||||||
signature_slot = block_header.slot
|
signature_slot = block_header.slot + 1
|
||||||
|
|
||||||
all_pubkeys = [v.pubkey for v in state.validators]
|
# Ensure correct sync committee and fork version are selected
|
||||||
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys]
|
signature_state = state.copy()
|
||||||
sync_committee_bits = [True] * len(committee)
|
transition_to(spec, signature_state, signature_slot)
|
||||||
|
|
||||||
|
# Fetch sync committee
|
||||||
|
committee_indices = compute_committee_indices(spec, signature_state)
|
||||||
|
committee_size = len(committee_indices)
|
||||||
|
|
||||||
|
# Compute sync aggregate
|
||||||
|
sync_committee_bits = [True] * committee_size
|
||||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||||
spec,
|
spec,
|
||||||
state,
|
signature_state,
|
||||||
block_header.slot,
|
signature_slot,
|
||||||
committee,
|
committee_indices,
|
||||||
block_root=block_root,
|
block_root=spec.Root(block_header.hash_tree_root()),
|
||||||
)
|
)
|
||||||
return spec.SyncAggregate(
|
sync_aggregate = spec.SyncAggregate(
|
||||||
sync_committee_bits=sync_committee_bits,
|
sync_committee_bits=sync_committee_bits,
|
||||||
sync_committee_signature=sync_committee_signature,
|
sync_committee_signature=sync_committee_signature,
|
||||||
)
|
)
|
||||||
|
fork_version = signature_state.fork.current_version
|
||||||
|
return sync_aggregate, fork_version, signature_slot
|
||||||
|
Loading…
x
Reference in New Issue
Block a user