2022-02-27 16:55:02 +00:00
|
|
|
# beacon_chain
|
|
|
|
# Copyright (c) 2021-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.
|
|
|
|
|
2022-03-02 10:44:42 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2021-09-08 16:57:00 +00:00
|
|
|
import
|
2021-09-13 16:47:39 +00:00
|
|
|
stew/[bitops2, objects],
|
2021-09-08 16:57:00 +00:00
|
|
|
datatypes/altair,
|
|
|
|
helpers
|
|
|
|
|
2022-03-04 16:09:33 +00:00
|
|
|
func period_contains_fork_version(
|
|
|
|
cfg: RuntimeConfig,
|
|
|
|
period: SyncCommitteePeriod,
|
|
|
|
fork_version: Version): bool =
|
|
|
|
## Determine whether a given `fork_version` is used during a given `period`.
|
|
|
|
let
|
|
|
|
periodStartEpoch = period.start_epoch
|
|
|
|
periodEndEpoch = periodStartEpoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1
|
|
|
|
return
|
|
|
|
if fork_version == cfg.SHARDING_FORK_VERSION:
|
|
|
|
periodEndEpoch >= cfg.SHARDING_FORK_EPOCH
|
|
|
|
elif fork_version == cfg.BELLATRIX_FORK_VERSION:
|
|
|
|
periodStartEpoch < cfg.SHARDING_FORK_EPOCH and
|
|
|
|
cfg.SHARDING_FORK_EPOCH != cfg.BELLATRIX_FORK_EPOCH and
|
|
|
|
periodEndEpoch >= cfg.BELLATRIX_FORK_EPOCH
|
|
|
|
elif fork_version == cfg.ALTAIR_FORK_VERSION:
|
|
|
|
periodStartEpoch < cfg.BELLATRIX_FORK_EPOCH and
|
|
|
|
cfg.BELLATRIX_FORK_EPOCH != cfg.ALTAIR_FORK_EPOCH and
|
|
|
|
periodEndEpoch >= cfg.ALTAIR_FORK_EPOCH
|
|
|
|
elif fork_version == cfg.GENESIS_FORK_VERSION:
|
|
|
|
# Light client sync protocol requires Altair
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
# Unviable fork
|
|
|
|
false
|
|
|
|
|
2022-03-03 13:03:08 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#get_active_header
|
|
|
|
func is_finality_update(update: altair.LightClientUpdate): bool =
|
|
|
|
not update.finalized_header.isZeroMemory
|
|
|
|
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#get_active_header
|
2022-03-02 10:44:42 +00:00
|
|
|
func get_active_header(update: altair.LightClientUpdate): BeaconBlockHeader =
|
2022-01-03 13:06:14 +00:00
|
|
|
# The "active header" is the header that the update is trying to convince
|
|
|
|
# us to accept. If a finalized header is present, it's the finalized
|
|
|
|
# header, otherwise it's the attested header
|
2022-03-03 13:03:08 +00:00
|
|
|
if update.is_finality_update:
|
2022-01-03 13:06:14 +00:00
|
|
|
update.finalized_header
|
|
|
|
else:
|
|
|
|
update.attested_header
|
|
|
|
|
2022-03-03 13:03:08 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#get_safety_threshold
|
2022-03-02 10:44:42 +00:00
|
|
|
func get_safety_threshold(store: LightClientStore): uint64 =
|
|
|
|
max(
|
|
|
|
store.previous_max_active_participants,
|
|
|
|
store.current_max_active_participants
|
|
|
|
) div 2
|
|
|
|
|
2022-03-03 13:03:08 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#validate_light_client_update
|
2022-03-02 10:44:42 +00:00
|
|
|
proc validate_light_client_update*(
|
|
|
|
store: LightClientStore,
|
|
|
|
update: altair.LightClientUpdate,
|
|
|
|
current_slot: Slot,
|
2022-03-04 16:09:33 +00:00
|
|
|
cfg: RuntimeConfig,
|
2022-03-02 10:44:42 +00:00
|
|
|
genesis_validators_root: Eth2Digest): bool =
|
2022-01-03 13:06:14 +00:00
|
|
|
# Verify update slot is larger than slot of current best finalized header
|
|
|
|
let active_header = get_active_header(update)
|
|
|
|
if not (current_slot >= active_header.slot and
|
|
|
|
active_header.slot > store.finalized_header.slot):
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
|
|
|
|
|
|
|
# Verify update does not skip a sync committee period
|
2021-11-02 20:32:34 +00:00
|
|
|
let
|
2022-01-11 10:01:54 +00:00
|
|
|
finalized_period = sync_committee_period(store.finalized_header.slot)
|
|
|
|
update_period = sync_committee_period(active_header.slot)
|
2022-01-03 13:06:14 +00:00
|
|
|
|
|
|
|
if update_period notin [finalized_period, finalized_period + 1]:
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
|
|
|
|
2022-03-04 16:09:33 +00:00
|
|
|
# Verify fork version is acceptable
|
|
|
|
let fork_version = update.fork_version
|
|
|
|
if not cfg.period_contains_fork_version(update_period, fork_version):
|
|
|
|
return false
|
|
|
|
|
2022-01-03 13:06:14 +00:00
|
|
|
# Verify that the `finalized_header`, if present, actually is the finalized
|
|
|
|
# header saved in the state of the `attested header`
|
2022-03-03 13:03:08 +00:00
|
|
|
if not update.is_finality_update:
|
2021-09-13 16:47:39 +00:00
|
|
|
if not update.finality_branch.isZeroMemory:
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
|
|
|
else:
|
2022-03-02 10:44:42 +00:00
|
|
|
if not is_valid_merkle_branch(
|
|
|
|
hash_tree_root(update.finalized_header),
|
|
|
|
update.finality_branch,
|
|
|
|
log2trunc(altair.FINALIZED_ROOT_INDEX),
|
|
|
|
get_subtree_index(altair.FINALIZED_ROOT_INDEX),
|
|
|
|
update.attested_header.state_root):
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
|
|
|
|
|
|
|
# Verify update next sync committee if the update period incremented
|
|
|
|
# TODO: Use a view type instead of `unsafeAddr`
|
2022-01-03 13:06:14 +00:00
|
|
|
let sync_committee = if update_period == finalized_period:
|
2021-09-13 16:47:39 +00:00
|
|
|
if not update.next_sync_committee_branch.isZeroMemory:
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
2022-01-03 13:06:14 +00:00
|
|
|
unsafeAddr store.current_sync_committee
|
2021-09-08 16:57:00 +00:00
|
|
|
else:
|
2022-03-02 10:44:42 +00:00
|
|
|
if not is_valid_merkle_branch(
|
|
|
|
hash_tree_root(update.next_sync_committee),
|
|
|
|
update.next_sync_committee_branch,
|
|
|
|
log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX),
|
|
|
|
get_subtree_index(altair.NEXT_SYNC_COMMITTEE_INDEX),
|
|
|
|
active_header.state_root):
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
2022-01-03 13:06:14 +00:00
|
|
|
unsafeAddr store.next_sync_committee
|
|
|
|
|
2022-02-01 07:31:53 +00:00
|
|
|
template sync_aggregate(): auto = update.sync_aggregate
|
2022-01-03 13:06:14 +00:00
|
|
|
let sync_committee_participants_count = countOnes(sync_aggregate.sync_committee_bits)
|
2021-09-08 16:57:00 +00:00
|
|
|
|
|
|
|
# Verify sync committee has sufficient participants
|
2021-09-13 16:47:39 +00:00
|
|
|
if sync_committee_participants_count < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
|
|
|
|
|
|
|
# Verify sync committee aggregate signature
|
2022-01-03 13:06:14 +00:00
|
|
|
# participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit]
|
2021-09-13 16:47:39 +00:00
|
|
|
var participant_pubkeys = newSeqOfCap[ValidatorPubKey](sync_committee_participants_count)
|
2022-01-03 13:06:14 +00:00
|
|
|
for idx, bit in sync_aggregate.sync_committee_bits:
|
2021-09-08 16:57:00 +00:00
|
|
|
if bit:
|
|
|
|
participant_pubkeys.add(sync_committee.pubkeys[idx])
|
2022-03-04 16:09:33 +00:00
|
|
|
let
|
|
|
|
domain = compute_domain(
|
|
|
|
DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root)
|
|
|
|
signing_root = compute_signing_root(update.attested_header, domain)
|
|
|
|
if not blsFastAggregateVerify(
|
|
|
|
participant_pubkeys, signing_root.data,
|
|
|
|
sync_aggregate.sync_committee_signature):
|
|
|
|
return false
|
2021-09-08 16:57:00 +00:00
|
|
|
|
2022-03-04 16:09:33 +00:00
|
|
|
true
|
2021-09-08 16:57:00 +00:00
|
|
|
|
2022-03-03 13:03:08 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#apply_light_client_update
|
2022-01-03 13:06:14 +00:00
|
|
|
func apply_light_client_update(
|
2022-03-02 10:44:42 +00:00
|
|
|
store: var LightClientStore,
|
|
|
|
update: altair.LightClientUpdate) =
|
2021-11-02 20:32:34 +00:00
|
|
|
let
|
2022-01-03 13:06:14 +00:00
|
|
|
active_header = get_active_header(update)
|
2022-01-11 10:01:54 +00:00
|
|
|
finalized_period = sync_committee_period(store.finalized_header.slot)
|
|
|
|
update_period = sync_committee_period(active_header.slot)
|
2022-01-03 13:06:14 +00:00
|
|
|
if update_period == finalized_period + 1:
|
|
|
|
store.current_sync_committee = store.next_sync_committee
|
|
|
|
store.next_sync_committee = update.next_sync_committee
|
|
|
|
store.finalized_header = active_header
|
2022-03-03 13:03:08 +00:00
|
|
|
if store.finalized_header.slot > store.optimistic_header.slot:
|
|
|
|
store.optimistic_header = store.finalized_header
|
2022-01-03 13:06:14 +00:00
|
|
|
|
2022-03-03 13:03:08 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#process_light_client_update
|
2022-03-02 10:44:42 +00:00
|
|
|
proc process_light_client_update*(
|
|
|
|
store: var LightClientStore,
|
|
|
|
update: altair.LightClientUpdate,
|
|
|
|
current_slot: Slot,
|
2022-03-04 16:09:33 +00:00
|
|
|
cfg: RuntimeConfig,
|
2022-03-02 10:44:42 +00:00
|
|
|
genesis_validators_root: Eth2Digest): bool =
|
2022-01-03 13:06:14 +00:00
|
|
|
if not validate_light_client_update(
|
2022-03-04 16:09:33 +00:00
|
|
|
store, update, current_slot, cfg, genesis_validators_root):
|
2021-09-08 16:57:00 +00:00
|
|
|
return false
|
2022-01-03 13:06:14 +00:00
|
|
|
|
|
|
|
let
|
2022-02-01 07:31:53 +00:00
|
|
|
sync_committee_bits = update.sync_aggregate.sync_committee_bits
|
2022-01-03 13:06:14 +00:00
|
|
|
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(
|
2022-02-01 07:31:53 +00:00
|
|
|
store.best_valid_update.get.sync_aggregate.sync_committee_bits):
|
2022-01-03 13:06:14 +00:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# Update finalized header
|
|
|
|
if sum_sync_committee_bits * 3 >= len(sync_committee_bits) * 2 and
|
2022-03-03 13:03:08 +00:00
|
|
|
update.is_finality_update:
|
2022-01-03 13:06:14 +00:00
|
|
|
# Normal update through 2/3 threshold
|
|
|
|
apply_light_client_update(store, update)
|
2022-03-02 10:44:42 +00:00
|
|
|
store.best_valid_update = none(altair.LightClientUpdate)
|
2022-01-03 13:06:14 +00:00
|
|
|
|
2021-09-13 16:47:39 +00:00
|
|
|
true
|