update to pre-release light client sync protocol (#3465)
This adopts the spec sections of the pre-release proposal of the libp2p based light client sync protocol, and also adds a test runner for the new accompanying tests. While the release version of the light client sync protocol contains conflicting definitions, it is currently unused, and the code specific to the pre-release proposal is marked as such. See https://github.com/ethereum/consensus-specs/pull/2802
This commit is contained in:
parent
aaa5a5ad40
commit
5a3ba5d968
|
@ -799,13 +799,19 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
|||
+ Testing VoluntaryExit OK
|
||||
```
|
||||
OK: 35/35 Fail: 0/35 Skip: 0/35
|
||||
## EF - Altair - Sync protocol - Light client [Preset: mainnet]
|
||||
```diff
|
||||
All tests Skip
|
||||
```
|
||||
OK: 0/1 Fail: 0/1 Skip: 1/1
|
||||
## EF - Altair - Unittests - Sync protocol [Preset: mainnet]
|
||||
```diff
|
||||
+ process_light_client_update_finality_updated OK
|
||||
+ process_light_client_update_timeout OK
|
||||
+ test_process_light_client_update_at_period_boundary OK
|
||||
+ test_process_light_client_update_not_timeout OK
|
||||
```
|
||||
OK: 3/3 Fail: 0/3 Skip: 0/3
|
||||
OK: 4/4 Fail: 0/4 Skip: 0/4
|
||||
## EF - Bellatrix - Epoch Processing - Effective balance updates [Preset: mainnet]
|
||||
```diff
|
||||
+ Effective balance updates - effective_balance_hysteresis [Preset: mainnet] OK
|
||||
|
@ -1209,4 +1215,4 @@ OK: 44/44 Fail: 0/44 Skip: 0/44
|
|||
OK: 27/27 Fail: 0/27 Skip: 0/27
|
||||
|
||||
---TOTAL---
|
||||
OK: 1034/1035 Fail: 0/1035 Skip: 1/1035
|
||||
OK: 1035/1037 Fail: 0/1037 Skip: 2/1037
|
||||
|
|
|
@ -840,13 +840,19 @@ OK: 5/5 Fail: 0/5 Skip: 0/5
|
|||
+ Testing VoluntaryExit OK
|
||||
```
|
||||
OK: 35/35 Fail: 0/35 Skip: 0/35
|
||||
## EF - Altair - Sync protocol - Light client [Preset: minimal]
|
||||
```diff
|
||||
All tests Skip
|
||||
```
|
||||
OK: 0/1 Fail: 0/1 Skip: 1/1
|
||||
## EF - Altair - Unittests - Sync protocol [Preset: minimal]
|
||||
```diff
|
||||
+ process_light_client_update_finality_updated OK
|
||||
+ process_light_client_update_timeout OK
|
||||
+ test_process_light_client_update_at_period_boundary OK
|
||||
+ test_process_light_client_update_not_timeout OK
|
||||
```
|
||||
OK: 3/3 Fail: 0/3 Skip: 0/3
|
||||
OK: 4/4 Fail: 0/4 Skip: 0/4
|
||||
## EF - Bellatrix - Epoch Processing - Effective balance updates [Preset: minimal]
|
||||
```diff
|
||||
+ Effective balance updates - effective_balance_hysteresis [Preset: minimal] OK
|
||||
|
@ -1286,4 +1292,4 @@ OK: 48/48 Fail: 0/48 Skip: 0/48
|
|||
OK: 30/30 Fail: 0/30 Skip: 0/30
|
||||
|
||||
---TOTAL---
|
||||
OK: 1084/1104 Fail: 0/1104 Skip: 20/1104
|
||||
OK: 1085/1106 Fail: 0/1106 Skip: 21/1106
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# References to `vFuture` refer to the pre-release proposal of the libp2p based
|
||||
# light client sync protocol. Conflicting release versions are not in use.
|
||||
# https://github.com/ethereum/consensus-specs/pull/2802
|
||||
|
||||
import
|
||||
std/[typetraits, sets, hashes],
|
||||
chronicles,
|
||||
|
@ -51,13 +55,14 @@ const
|
|||
TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE* = 16
|
||||
SYNC_COMMITTEE_SUBNET_COUNT* = 4
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#constants
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#constants
|
||||
# All of these indices are rooted in `BeaconState`.
|
||||
# The first member (`genesis_time`) is 32, subsequent members +1 each.
|
||||
# If there are ever more than 32 members in `BeaconState`, indices change!
|
||||
# `FINALIZED_ROOT_INDEX` is one layer deeper, i.e., `52 * 2 + 1`.
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/ssz/merkle-proofs.md
|
||||
FINALIZED_ROOT_INDEX* = 105.GeneralizedIndex # `finalized_checkpoint` > `root`
|
||||
CURRENT_SYNC_COMMITTEE_INDEX* = 54.GeneralizedIndex # `current_sync_committee`
|
||||
NEXT_SYNC_COMMITTEE_INDEX* = 55.GeneralizedIndex # `next_sync_committee`
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/beacon-chain.md#participation-flag-indices
|
||||
|
@ -157,12 +162,23 @@ type
|
|||
|
||||
### Modified/overloaded
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#lightclientupdate
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#lightclientbootstrap
|
||||
LightClientBootstrap* = object
|
||||
header*: BeaconBlockHeader ##\
|
||||
## The requested beacon block header
|
||||
|
||||
# Current sync committee corresponding to the requested header
|
||||
current_sync_committee*: SyncCommittee
|
||||
current_sync_committee_branch*:
|
||||
array[log2trunc(CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#lightclientupdate
|
||||
LightClientUpdate* = object
|
||||
attested_header*: BeaconBlockHeader ##\
|
||||
## The beacon block header that is attested to by the sync committee
|
||||
|
||||
# Next sync committee corresponding to the active header
|
||||
# Next sync committee corresponding to the active header,
|
||||
# if signature is from current sync committee
|
||||
next_sync_committee*: SyncCommittee
|
||||
next_sync_committee_branch*:
|
||||
array[log2trunc(NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
|
@ -177,6 +193,20 @@ type
|
|||
fork_version*: Version ##\
|
||||
## Fork version for the aggregate signature
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#optimisticlightclientupdate
|
||||
OptimisticLightClientUpdate* = object
|
||||
attested_header*: BeaconBlockHeader ##\
|
||||
## The beacon block header that is attested to by the sync committee
|
||||
|
||||
sync_aggregate*: SyncAggregate ##\
|
||||
## Sync committee aggregate signature
|
||||
|
||||
fork_version*: Version ##\
|
||||
## Fork version for the aggregate signature
|
||||
|
||||
is_signed_by_next_sync_committee*: bool ##\
|
||||
## Whether the signature was produced by `attested_header`'s next sync committee
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#lightclientstore
|
||||
LightClientStore* = object
|
||||
finalized_header*: BeaconBlockHeader ##\
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# References to `vFuture` refer to the pre-release proposal of the libp2p based
|
||||
# light client sync protocol. Conflicting release versions are not in use.
|
||||
# https://github.com/ethereum/consensus-specs/pull/2802
|
||||
|
||||
import
|
||||
stew/[bitops2, objects],
|
||||
datatypes/altair,
|
||||
|
@ -59,34 +63,89 @@ func get_safety_threshold(store: LightClientStore): uint64 =
|
|||
store.current_max_active_participants
|
||||
) div 2
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#validate_light_client_update
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#initialize_light_client_store
|
||||
func initialize_light_client_store*(
|
||||
trusted_block_root: Eth2Digest,
|
||||
bootstrap: altair.LightClientBootstrap
|
||||
): Opt[LightClientStore] =
|
||||
if hash_tree_root(bootstrap.header) != trusted_block_root:
|
||||
return err()
|
||||
|
||||
if not is_valid_merkle_branch(
|
||||
hash_tree_root(bootstrap.current_sync_committee),
|
||||
bootstrap.current_sync_committee_branch,
|
||||
log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX),
|
||||
get_subtree_index(altair.CURRENT_SYNC_COMMITTEE_INDEX),
|
||||
bootstrap.header.state_root):
|
||||
return err()
|
||||
|
||||
return ok(LightClientStore(
|
||||
finalized_header: bootstrap.header,
|
||||
current_sync_committee: bootstrap.current_sync_committee,
|
||||
optimistic_header: bootstrap.header))
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#validate_light_client_update
|
||||
proc validate_light_client_update*(
|
||||
store: LightClientStore,
|
||||
update: altair.LightClientUpdate,
|
||||
current_slot: Slot,
|
||||
cfg: RuntimeConfig,
|
||||
genesis_validators_root: Eth2Digest): bool =
|
||||
# Verify update slot is larger than slot of current best finalized header
|
||||
# Verify sync committee has sufficient participants
|
||||
template sync_aggregate(): auto = update.sync_aggregate
|
||||
template sync_committee_bits(): auto = sync_aggregate.sync_committee_bits
|
||||
let num_active_participants = countOnes(sync_committee_bits).uint64
|
||||
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
return false
|
||||
|
||||
# Determine update header
|
||||
template attested_header(): auto = update.attested_header
|
||||
if current_slot < attested_header.slot:
|
||||
return false
|
||||
let active_header = get_active_header(update)
|
||||
if not (current_slot >= active_header.slot and
|
||||
active_header.slot > store.finalized_header.slot):
|
||||
if attested_header.slot < active_header.slot:
|
||||
return false
|
||||
|
||||
# Verify update is relevant
|
||||
let is_next_sync_committee_known = not store.next_sync_committee.isZeroMemory
|
||||
if is_next_sync_committee_known:
|
||||
if active_header.slot < store.finalized_header.slot:
|
||||
return false
|
||||
if active_header.slot == store.finalized_header.slot:
|
||||
if attested_header.slot <= store.optimistic_header.slot:
|
||||
return false
|
||||
|
||||
# Verify update does not skip a sync committee period
|
||||
let
|
||||
finalized_period = sync_committee_period(store.finalized_header.slot)
|
||||
update_period = sync_committee_period(active_header.slot)
|
||||
|
||||
finalized_period = store.finalized_header.slot.sync_committee_period
|
||||
update_period = active_header.slot.sync_committee_period
|
||||
if update_period notin [finalized_period, finalized_period + 1]:
|
||||
return false
|
||||
let
|
||||
is_signed_by_next_sync_committee =
|
||||
update.next_sync_committee.isZeroMemory
|
||||
signature_period =
|
||||
if is_signed_by_next_sync_committee:
|
||||
update_period + 1
|
||||
else:
|
||||
update_period
|
||||
current_period = current_slot.sync_committee_period
|
||||
if current_period < signature_period:
|
||||
return false
|
||||
if is_next_sync_committee_known:
|
||||
if signature_period notin [finalized_period, finalized_period + 1]:
|
||||
return false
|
||||
else:
|
||||
if signature_period != finalized_period:
|
||||
return false
|
||||
|
||||
# Verify fork version is acceptable
|
||||
let fork_version = update.fork_version
|
||||
if not cfg.period_contains_fork_version(update_period, fork_version):
|
||||
if not cfg.period_contains_fork_version(signature_period, fork_version):
|
||||
return false
|
||||
|
||||
# Verify that the `finalized_header`, if present, actually is the finalized
|
||||
# header saved in the state of the `attested header`
|
||||
# header saved in the state of the `attested_header`
|
||||
if not update.is_finality_update:
|
||||
if not update.finality_branch.isZeroMemory:
|
||||
return false
|
||||
|
@ -99,13 +158,15 @@ proc validate_light_client_update*(
|
|||
update.attested_header.state_root):
|
||||
return false
|
||||
|
||||
# Verify update next sync committee if the update period incremented
|
||||
# TODO: Use a view type instead of `unsafeAddr`
|
||||
let sync_committee = if update_period == finalized_period:
|
||||
# Verify that the `next_sync_committee`, if present, actually is the
|
||||
# next sync committee saved in the state of the `active_header`
|
||||
if is_signed_by_next_sync_committee:
|
||||
if not update.next_sync_committee_branch.isZeroMemory:
|
||||
return false
|
||||
unsafeAddr store.current_sync_committee
|
||||
else:
|
||||
if update_period == finalized_period and is_next_sync_committee_known:
|
||||
if update.next_sync_committee != store.next_sync_committee:
|
||||
return false
|
||||
if not is_valid_merkle_branch(
|
||||
hash_tree_root(update.next_sync_committee),
|
||||
update.next_sync_committee_branch,
|
||||
|
@ -113,25 +174,22 @@ proc validate_light_client_update*(
|
|||
get_subtree_index(altair.NEXT_SYNC_COMMITTEE_INDEX),
|
||||
active_header.state_root):
|
||||
return false
|
||||
unsafeAddr store.next_sync_committee
|
||||
|
||||
template sync_aggregate(): auto = update.sync_aggregate
|
||||
let sync_committee_participants_count = countOnes(sync_aggregate.sync_committee_bits)
|
||||
|
||||
# Verify sync committee has sufficient participants
|
||||
if sync_committee_participants_count < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
return false
|
||||
|
||||
# Verify sync committee aggregate signature
|
||||
# participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit]
|
||||
var participant_pubkeys = newSeqOfCap[ValidatorPubKey](sync_committee_participants_count)
|
||||
let sync_committee =
|
||||
if signature_period == finalized_period:
|
||||
unsafeAddr store.current_sync_committee
|
||||
else:
|
||||
unsafeAddr store.next_sync_committee
|
||||
var participant_pubkeys =
|
||||
newSeqOfCap[ValidatorPubKey](num_active_participants)
|
||||
for idx, bit in sync_aggregate.sync_committee_bits:
|
||||
if bit:
|
||||
participant_pubkeys.add(sync_committee.pubkeys[idx])
|
||||
let
|
||||
domain = compute_domain(
|
||||
DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root)
|
||||
signing_root = compute_signing_root(update.attested_header, domain)
|
||||
signing_root = compute_signing_root(attested_header, domain)
|
||||
if not blsFastAggregateVerify(
|
||||
participant_pubkeys, signing_root.data,
|
||||
sync_aggregate.sync_committee_signature):
|
||||
|
@ -139,59 +197,186 @@ proc validate_light_client_update*(
|
|||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#apply_light_client_update
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#validate_optimistic_light_client_update
|
||||
proc validate_optimistic_light_client_update*(
|
||||
store: LightClientStore,
|
||||
optimistic_update: OptimisticLightClientUpdate,
|
||||
current_slot: Slot,
|
||||
cfg: RuntimeConfig,
|
||||
genesis_validators_root: Eth2Digest): bool =
|
||||
# Verify sync committee has sufficient participants
|
||||
template sync_aggregate(): auto = optimistic_update.sync_aggregate
|
||||
template sync_committee_bits(): auto = sync_aggregate.sync_committee_bits
|
||||
let num_active_participants = countOnes(sync_committee_bits).uint64
|
||||
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
return false
|
||||
|
||||
# Determine update header
|
||||
template attested_header(): auto = optimistic_update.attested_header
|
||||
if current_slot < attested_header.slot:
|
||||
return false
|
||||
template active_header(): auto = attested_header
|
||||
|
||||
# Verify update is relevant
|
||||
if attested_header.slot <= store.optimistic_header.slot:
|
||||
return false
|
||||
|
||||
# Verify update does not skip a sync committee period
|
||||
let
|
||||
finalized_period = store.finalized_header.slot.sync_committee_period
|
||||
update_period = active_header.slot.sync_committee_period
|
||||
if update_period notin [finalized_period, finalized_period + 1]:
|
||||
return false
|
||||
let
|
||||
is_signed_by_next_sync_committee =
|
||||
optimistic_update.is_signed_by_next_sync_committee
|
||||
signature_period =
|
||||
if is_signed_by_next_sync_committee:
|
||||
update_period + 1
|
||||
else:
|
||||
update_period
|
||||
current_period = current_slot.sync_committee_period
|
||||
if current_period < signature_period:
|
||||
return false
|
||||
let is_next_sync_committee_known = not store.next_sync_committee.isZeroMemory
|
||||
if is_next_sync_committee_known:
|
||||
if signature_period notin [finalized_period, finalized_period + 1]:
|
||||
return false
|
||||
else:
|
||||
if signature_period != finalized_period:
|
||||
return false
|
||||
|
||||
# Verify fork version is acceptable
|
||||
let fork_version = optimistic_update.fork_version
|
||||
if not cfg.period_contains_fork_version(signature_period, fork_version):
|
||||
return false
|
||||
|
||||
# Verify sync committee aggregate signature
|
||||
let sync_committee =
|
||||
if signature_period == finalized_period:
|
||||
unsafeAddr store.current_sync_committee
|
||||
else:
|
||||
unsafeAddr store.next_sync_committee
|
||||
var participant_pubkeys =
|
||||
newSeqOfCap[ValidatorPubKey](num_active_participants)
|
||||
for idx, bit in sync_aggregate.sync_committee_bits:
|
||||
if bit:
|
||||
participant_pubkeys.add(sync_committee.pubkeys[idx])
|
||||
let
|
||||
domain = compute_domain(
|
||||
DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root)
|
||||
signing_root = compute_signing_root(attested_header, domain)
|
||||
if not blsFastAggregateVerify(
|
||||
participant_pubkeys, signing_root.data,
|
||||
sync_aggregate.sync_committee_signature):
|
||||
return false
|
||||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#apply_light_client_update
|
||||
func apply_light_client_update(
|
||||
store: var LightClientStore,
|
||||
update: altair.LightClientUpdate) =
|
||||
let
|
||||
active_header = get_active_header(update)
|
||||
finalized_period = sync_committee_period(store.finalized_header.slot)
|
||||
update_period = sync_committee_period(active_header.slot)
|
||||
if update_period == finalized_period + 1:
|
||||
finalized_period = store.finalized_header.slot.sync_committee_period
|
||||
update_period = active_header.slot.sync_committee_period
|
||||
if store.next_sync_committee.isZeroMemory:
|
||||
assert update_period == finalized_period
|
||||
store.next_sync_committee = update.next_sync_committee
|
||||
elif update_period == finalized_period + 1:
|
||||
store.previous_max_active_participants =
|
||||
store.current_max_active_participants
|
||||
store.current_max_active_participants = 0
|
||||
store.current_sync_committee = store.next_sync_committee
|
||||
store.next_sync_committee = update.next_sync_committee
|
||||
assert not store.next_sync_committee.isZeroMemory
|
||||
if active_header.slot > store.finalized_header.slot:
|
||||
store.finalized_header = active_header
|
||||
if store.finalized_header.slot > store.optimistic_header.slot:
|
||||
store.optimistic_header = store.finalized_header
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#process_light_client_update
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#apply_optimistic_light_client_header
|
||||
func apply_optimistic_light_client_header(
|
||||
store: var LightClientStore,
|
||||
attested_header: BeaconBlockHeader,
|
||||
num_active_participants: uint64) =
|
||||
if store.current_max_active_participants < num_active_participants:
|
||||
store.current_max_active_participants = num_active_participants
|
||||
|
||||
if num_active_participants > get_safety_threshold(store) and
|
||||
attested_header.slot > store.optimistic_header.slot:
|
||||
store.optimistic_header = attested_header
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#process_slot_for_light_client_store
|
||||
func process_slot_for_light_client_store*(
|
||||
store: var LightClientStore,
|
||||
current_slot: Slot) =
|
||||
if store.best_valid_update.isSome and
|
||||
current_slot > store.finalized_header.slot + UPDATE_TIMEOUT:
|
||||
apply_light_client_update(store, store.best_valid_update.get)
|
||||
store.best_valid_update = none(altair.LightClientUpdate)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#process_light_client_update
|
||||
proc process_light_client_update*(
|
||||
store: var LightClientStore,
|
||||
update: altair.LightClientUpdate,
|
||||
current_slot: Slot,
|
||||
cfg: RuntimeConfig,
|
||||
genesis_validators_root: Eth2Digest): bool =
|
||||
genesis_validators_root: Eth2Digest,
|
||||
allowForceUpdate = true): bool =
|
||||
if not validate_light_client_update(
|
||||
store, update, current_slot, cfg, genesis_validators_root):
|
||||
return false
|
||||
|
||||
let
|
||||
sync_committee_bits = update.sync_aggregate.sync_committee_bits
|
||||
sum_sync_committee_bits = countOnes(sync_committee_bits)
|
||||
|
||||
# Update the best update in case we have to force-update to it if the
|
||||
# timeout elapses
|
||||
if store.best_valid_update.isNone or
|
||||
sum_sync_committee_bits > countOnes(
|
||||
store.best_valid_update.get.sync_aggregate.sync_committee_bits):
|
||||
store.best_valid_update = some(update)
|
||||
|
||||
# Track the maximum number of active participants in the committee signatures
|
||||
store.current_max_active_participants = max(
|
||||
store.current_max_active_participants,
|
||||
sum_sync_committee_bits.uint64,
|
||||
)
|
||||
template sync_aggregate(): auto = update.sync_aggregate
|
||||
template sync_committee_bits(): auto = sync_aggregate.sync_committee_bits
|
||||
let num_active_participants = countOnes(sync_committee_bits).uint64
|
||||
|
||||
# Update the optimistic header
|
||||
if sum_sync_committee_bits.uint64 > get_safety_threshold(store) and
|
||||
update.attested_header.slot > store.optimistic_header.slot:
|
||||
store.optimistic_header = update.attested_header
|
||||
apply_optimistic_light_client_header(
|
||||
store, update.attested_header, num_active_participants)
|
||||
|
||||
# Update the best update in case we have to force-update to it
|
||||
# if the timeout elapses
|
||||
let best_active_participants =
|
||||
if store.best_valid_update.isNone:
|
||||
0.uint64
|
||||
else:
|
||||
template best_sync_aggregate(): auto =
|
||||
store.best_valid_update.get.sync_aggregate
|
||||
countOnes(best_sync_aggregate.sync_committee_bits).uint64
|
||||
if num_active_participants > best_active_participants:
|
||||
store.best_valid_update = some(update)
|
||||
|
||||
# Update finalized header
|
||||
if sum_sync_committee_bits * 3 >= len(sync_committee_bits) * 2 and
|
||||
if num_active_participants * 3 >= static(sync_committee_bits.len * 2) and
|
||||
update.is_finality_update:
|
||||
# Normal update through 2/3 threshold
|
||||
apply_light_client_update(store, update)
|
||||
store.best_valid_update = none(altair.LightClientUpdate)
|
||||
else:
|
||||
if allowForceUpdate:
|
||||
# Force-update to best update if the timeout elapsed
|
||||
process_slot_for_light_client_store(store, current_slot)
|
||||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#process_light_client_update
|
||||
proc process_optimistic_light_client_update*(
|
||||
store: var LightClientStore,
|
||||
optimistic_update: OptimisticLightClientUpdate,
|
||||
current_slot: Slot,
|
||||
cfg: RuntimeConfig,
|
||||
genesis_validators_root: Eth2Digest): bool =
|
||||
if not validate_optimistic_light_client_update(
|
||||
store, optimistic_update, current_slot, cfg, genesis_validators_root):
|
||||
return false
|
||||
|
||||
template sync_aggregate(): auto = optimistic_update.sync_aggregate
|
||||
template sync_committee_bits(): auto = sync_aggregate.sync_committee_bits
|
||||
let num_active_participants = countOnes(sync_committee_bits).uint64
|
||||
|
||||
# Update the optimistic header
|
||||
apply_optimistic_light_client_header(
|
||||
store, optimistic_update.attested_header, num_active_participants)
|
||||
|
||||
true
|
||||
|
|
|
@ -18,5 +18,6 @@ import
|
|||
./test_fixture_sanity_slots,
|
||||
./test_fixture_ssz_consensus_objects,
|
||||
./test_fixture_state_transition_epoch,
|
||||
./test_fixture_sync_protocol_light_client_sync,
|
||||
./test_fixture_sync_protocol,
|
||||
./test_fixture_transition
|
||||
|
|
|
@ -22,6 +22,10 @@ import
|
|||
# Test utilities
|
||||
../../testutil, ../../testblockutil
|
||||
|
||||
# References to `vFuture` refer to the pre-release proposal of the libp2p based
|
||||
# light client sync protocol. Conflicting release versions are not in use.
|
||||
# https://github.com/ethereum/consensus-specs/pull/2802
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py#L27-L44
|
||||
proc compute_aggregate_sync_committee_signature(
|
||||
forked: ForkedHashedBeaconState,
|
||||
|
@ -94,7 +98,7 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
res
|
||||
genesisState = newClone(initGenesisState(cfg = cfg))
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L27-L69
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L27-L77
|
||||
test "test_process_light_client_update_not_timeout":
|
||||
let forked = assignClone(genesisState[])
|
||||
template state: untyped {.inject.} = forked[].altairData.data
|
||||
|
@ -120,8 +124,10 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
sync_committee_signature: sync_committee_signature)
|
||||
|
||||
template next_sync_committee(): auto = state.next_sync_committee
|
||||
var next_sync_committee_branch:
|
||||
var next_sync_committee_branch {.noinit.}:
|
||||
array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
state.build_proof(
|
||||
altair.NEXT_SYNC_COMMITTEE_INDEX, next_sync_committee_branch)
|
||||
|
||||
# Ensure that finality checkpoint is genesis
|
||||
check: state.finalized_checkpoint.epoch == 0
|
||||
|
@ -151,7 +157,70 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
store.finalized_header == pre_store_finalized_header
|
||||
store.best_valid_update.get == update
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L72-L121
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L80-L136
|
||||
test "test_process_light_client_update_at_period_boundary":
|
||||
var forked = assignClone(genesisState[])
|
||||
template state: untyped {.inject.} = forked[].altairData.data
|
||||
var store = initialize_light_client_store(state)
|
||||
|
||||
# Forward to slot before next sync committee period so that next block is final one in period
|
||||
var
|
||||
cache = StateCache()
|
||||
info = ForkedEpochInfo()
|
||||
process_slots(
|
||||
cfg, forked[], Slot(UPDATE_TIMEOUT - 2), cache, info, flags = {}).expect("no failure")
|
||||
let
|
||||
snapshot_period = sync_committee_period(store.optimistic_header.slot)
|
||||
update_period = sync_committee_period(state.slot)
|
||||
check: snapshot_period == update_period
|
||||
|
||||
let
|
||||
signed_block = block_for_next_slot(cfg, forked[], cache).altairData
|
||||
block_header = signed_block.toBeaconBlockHeader
|
||||
|
||||
# Sync committee signing the block_header
|
||||
signature_slot = block_header.slot + 1
|
||||
all_pubkeys = state.validators.mapIt(it.pubkey)
|
||||
committee = state.next_sync_committee.pubkeys
|
||||
.mapIt(all_pubkeys.find(it).ValidatorIndex)
|
||||
sync_committee_bits = full_sync_committee_bits
|
||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||
forked[], signature_slot, committee, block_header.hash_tree_root())
|
||||
sync_aggregate = SyncAggregate(
|
||||
sync_committee_bits: sync_committee_bits,
|
||||
sync_committee_signature: sync_committee_signature)
|
||||
|
||||
# Sync committee is omitted (signed by next sync committee)
|
||||
next_sync_committee = SyncCommittee()
|
||||
var next_sync_committee_branch:
|
||||
array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
# Finality is unchanged
|
||||
let
|
||||
finality_header = BeaconBlockHeader()
|
||||
pre_store_finalized_header = store.finalized_header
|
||||
var finality_branch:
|
||||
array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
|
||||
let
|
||||
update = altair.LightClientUpdate(
|
||||
attested_header: block_header,
|
||||
next_sync_committee: 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: state.fork.current_version)
|
||||
res = process_light_client_update(
|
||||
store, update, signature_slot, cfg, state.genesis_validators_root)
|
||||
|
||||
check:
|
||||
res
|
||||
store.current_max_active_participants > 0
|
||||
store.optimistic_header == update.attested_header
|
||||
store.finalized_header == pre_store_finalized_header
|
||||
store.best_valid_update.get == update
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L139-L193
|
||||
test "process_light_client_update_timeout":
|
||||
let forked = assignClone(genesisState[])
|
||||
template state: untyped {.inject.} = forked[].altairData.data
|
||||
|
@ -196,7 +265,6 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
|
||||
let
|
||||
pre_store_finalized_header = store.finalized_header
|
||||
update = altair.LightClientUpdate(
|
||||
attested_header: block_header,
|
||||
next_sync_committee: next_sync_committee,
|
||||
|
@ -210,12 +278,12 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
|
||||
check:
|
||||
res
|
||||
store.current_max_active_participants > 0
|
||||
store.previous_max_active_participants > 0
|
||||
store.optimistic_header == update.attested_header
|
||||
store.finalized_header == pre_store_finalized_header
|
||||
store.best_valid_update.get == update
|
||||
store.finalized_header == update.attested_header
|
||||
store.best_valid_update.isNone
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L124-L179
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py#L196-L260
|
||||
test "process_light_client_update_finality_updated":
|
||||
let forked = assignClone(genesisState[])
|
||||
template state: untyped {.inject.} = forked[].altairData.data
|
||||
|
@ -251,8 +319,10 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
|||
|
||||
# Updated sync_committee and finality
|
||||
template next_sync_committee(): auto = finalized_state.next_sync_committee
|
||||
var next_sync_committee_branch:
|
||||
var next_sync_committee_branch {.noinit.}:
|
||||
array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
finalized_state.build_proof(
|
||||
altair.NEXT_SYNC_COMMITTEE_INDEX, next_sync_committee_branch)
|
||||
let
|
||||
finalized_block = blocks[SLOTS_PER_EPOCH - 1].altairData
|
||||
finalized_block_header = finalized_block.toBeaconBlockHeader
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2022 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.used.}
|
||||
|
||||
# This implements the pre-release proposal of the libp2p based light client sync
|
||||
# protocol. See https://github.com/ethereum/consensus-specs/pull/2802
|
||||
|
||||
import
|
||||
# Standard library
|
||||
std/[json, os, streams],
|
||||
# Status libraries
|
||||
stew/bitops2,
|
||||
# Third-party
|
||||
yaml,
|
||||
# Beacon chain internals
|
||||
../../../beacon_chain/spec/light_client_sync,
|
||||
../../../beacon_chain/spec/datatypes/altair,
|
||||
# Test utilities
|
||||
../../testutil,
|
||||
../fixtures_utils
|
||||
|
||||
const TestsDir =
|
||||
SszTestsDir/const_preset/"altair"/"sync_protocol"/"light_client_sync"/"pyspec_tests"
|
||||
|
||||
type
|
||||
TestMeta = object
|
||||
genesis_validators_root: string
|
||||
trusted_block_root: string
|
||||
|
||||
TestStepKind {.pure.} = enum
|
||||
ProcessSlot
|
||||
ProcessUpdate
|
||||
ProcessOptimisticUpdate
|
||||
|
||||
TestStep = object
|
||||
case kind: TestStepKind
|
||||
of TestStepKind.ProcessSlot:
|
||||
discard
|
||||
of TestStepKind.ProcessUpdate:
|
||||
update: altair.LightClientUpdate
|
||||
of TestStepKind.ProcessOptimisticUpdate:
|
||||
optimistic_update: OptimisticLightClientUpdate
|
||||
current_slot: Slot
|
||||
|
||||
proc loadSteps(path: string): seq[TestStep] =
|
||||
let stepsYAML = readFile(path/"steps.yaml")
|
||||
let steps = yaml.loadToJson(stepsYAML)
|
||||
|
||||
result = @[]
|
||||
for step in steps[0]:
|
||||
if step.hasKey"process_slot":
|
||||
let s = step["process_slot"]
|
||||
result.add TestStep(kind: TestStepKind.ProcessSlot,
|
||||
current_slot: s["current_slot"].getInt().Slot)
|
||||
elif step.hasKey"process_update":
|
||||
let
|
||||
s = step["process_update"]
|
||||
filename = s["update"].getStr()
|
||||
update = parseTest(path/filename & ".ssz_snappy", SSZ,
|
||||
altair.LightClientUpdate)
|
||||
result.add TestStep(kind: TestStepKind.ProcessUpdate,
|
||||
update: update,
|
||||
current_slot: s["current_slot"].getInt().Slot)
|
||||
elif step.hasKey"process_optimistic_update":
|
||||
let
|
||||
s = step["process_optimistic_update"]
|
||||
filename = s["optimistic_update"].getStr()
|
||||
optimistic_update = parseTest(path/filename & ".ssz_snappy", SSZ,
|
||||
OptimisticLightClientUpdate)
|
||||
result.add TestStep(kind: TestStepKind.ProcessOptimisticUpdate,
|
||||
optimistic_update: optimistic_update,
|
||||
current_slot: s["current_slot"].getInt().Slot)
|
||||
else:
|
||||
doAssert false, "Unreachable: " & $step
|
||||
|
||||
proc runTest(identifier: string) =
|
||||
let testDir = TestsDir / identifier
|
||||
|
||||
proc `testImpl _ sync_protocol_light_client_sync _ identifier`() =
|
||||
test identifier:
|
||||
let
|
||||
meta = block:
|
||||
var s = openFileStream(testDir/"meta.yaml")
|
||||
defer: close(s)
|
||||
var res: TestMeta
|
||||
yaml.load(s, res)
|
||||
res
|
||||
genesis_validators_root =
|
||||
Eth2Digest.fromHex(meta.genesis_validators_root)
|
||||
trusted_block_root =
|
||||
Eth2Digest.fromHex(meta.trusted_block_root)
|
||||
|
||||
bootstrap = parseTest(testDir/"bootstrap.ssz_snappy", SSZ,
|
||||
altair.LightClientBootstrap)
|
||||
steps = loadSteps(testDir)
|
||||
|
||||
expected_finalized_header =
|
||||
parseTest(testDir/"expected_finalized_header.ssz_snappy", SSZ,
|
||||
BeaconBlockHeader)
|
||||
expected_optimistic_header =
|
||||
parseTest(testDir/"expected_optimistic_header.ssz_snappy", SSZ,
|
||||
BeaconBlockHeader)
|
||||
|
||||
var cfg = defaultRuntimeConfig
|
||||
cfg.ALTAIR_FORK_EPOCH = GENESIS_EPOCH
|
||||
|
||||
var store =
|
||||
initialize_light_client_store(trusted_block_root, bootstrap).get
|
||||
|
||||
for step in steps:
|
||||
case step.kind
|
||||
of TestStepKind.ProcessSlot:
|
||||
process_slot_for_light_client_store(
|
||||
store, step.current_slot)
|
||||
of TestStepKind.ProcessUpdate:
|
||||
let res = process_light_client_update(
|
||||
store, step.update, step.current_slot,
|
||||
cfg, genesis_validators_root)
|
||||
check res
|
||||
of TestStepKind.ProcessOptimisticUpdate:
|
||||
let res = process_optimistic_light_client_update(
|
||||
store, step.optimistic_update, step.current_slot,
|
||||
cfg, genesis_validators_root)
|
||||
check res
|
||||
|
||||
check:
|
||||
store.finalized_header == expected_finalized_header
|
||||
store.optimistic_header == expected_optimistic_header
|
||||
|
||||
`testImpl _ sync_protocol_light_client_sync _ identifier`()
|
||||
|
||||
suite "EF - Altair - Sync protocol - Light client" & preset():
|
||||
try:
|
||||
for kind, path in walkDir(TestsDir, relative = true, checkDir = true):
|
||||
runTest(path)
|
||||
except OSError:
|
||||
# These tests are for the pre-release proposal of the libp2p based light
|
||||
# client sync protocol. Corresponding test vectors need manual integration.
|
||||
# https://github.com/ethereum/consensus-specs/pull/2802
|
||||
#
|
||||
# To locally integrate the test vectors, clone the pre-release spec repo
|
||||
# at latest commit of https://github.com/ethereum/consensus-specs/pull/2802
|
||||
# and place it next to the `nimbus-eth2` repo, so that `nimbus-eth2` and
|
||||
# `consensus-specs` are in the same directory.
|
||||
#
|
||||
# To generate the additional test vectors, from `consensus-specs`:
|
||||
# $ rm -rf ../consensus-spec-tests && \
|
||||
# doctoc specs && make lint && make gen_sync_protocol
|
||||
#
|
||||
# To integrate the additional test vectors into `nimbus-eth2`, first run
|
||||
# `make test` from `nimbus-eth2` to ensure that the regular test vectors
|
||||
# have been downloaded and extracted, then proceed from `nimbus-eth2` with:
|
||||
# $ rsync -r ../consensus-spec-tests/tests/ \
|
||||
# ../nimbus-eth2/vendor/nim-eth2-scenarios/tests-v1.1.10/
|
||||
test "All tests":
|
||||
skip()
|
Loading…
Reference in New Issue