mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-10 06:16:25 +00:00
01efa93cf6
Introduces a new library for syncing using libp2p based light client sync protocol, and adds a new `nimbus_light_client` executable that uses this library for syncing. The new executable emits log messages when new beacon block headers are received, and is integrated into testing.
257 lines
8.7 KiB
Nim
257 lines
8.7 KiB
Nim
# 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.
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
# This implements the pre-release proposal of the libp2p based light client sync
|
|
# protocol. See https://github.com/ethereum/consensus-specs/pull/2802
|
|
|
|
import
|
|
chronicles,
|
|
eth/keys,
|
|
./gossip_processing/light_client_processor,
|
|
./networking/eth2_network,
|
|
./spec/datatypes/altair,
|
|
./spec/helpers,
|
|
./sync/light_client_manager,
|
|
"."/[beacon_clock, conf_light_client]
|
|
|
|
export eth2_network, conf_light_client
|
|
|
|
logScope: topics = "lightcl"
|
|
|
|
type
|
|
LightClientCallback* =
|
|
proc(lightClient: LightClient) {.gcsafe, raises: [Defect].}
|
|
|
|
LightClient* = ref object
|
|
network: Eth2Node
|
|
cfg: RuntimeConfig
|
|
forkDigests: ref ForkDigests
|
|
getBeaconTime: GetBeaconTimeFn
|
|
store: ref Option[LightClientStore]
|
|
processor: ref LightClientProcessor
|
|
manager: LightClientManager
|
|
gossipState: GossipState
|
|
onFinalizedHeader*, onOptimisticHeader*: LightClientCallback
|
|
trustedBlockRoot*: Option[Eth2Digest]
|
|
|
|
func finalizedHeader*(lightClient: LightClient): Opt[BeaconBlockHeader] =
|
|
if lightClient.store[].isSome:
|
|
ok lightClient.store[].get.finalized_header
|
|
else:
|
|
err()
|
|
|
|
func optimisticHeader*(lightClient: LightClient): Opt[BeaconBlockHeader] =
|
|
if lightClient.store[].isSome:
|
|
ok lightClient.store[].get.optimistic_header
|
|
else:
|
|
err()
|
|
|
|
proc createLightClient(
|
|
network: Eth2Node,
|
|
rng: ref BrHmacDrbgContext,
|
|
dumpEnabled: bool,
|
|
dumpDirInvalid, dumpDirIncoming: string,
|
|
cfg: RuntimeConfig,
|
|
forkDigests: ref ForkDigests,
|
|
getBeaconTime: GetBeaconTimeFn,
|
|
genesis_validators_root: Eth2Digest
|
|
): LightClient =
|
|
let lightClient = LightClient(
|
|
network: network,
|
|
cfg: cfg,
|
|
forkDigests: forkDigests,
|
|
getBeaconTime: getBeaconTime,
|
|
store: (ref Option[LightClientStore])())
|
|
|
|
func getTrustedBlockRoot(): Option[Eth2Digest] =
|
|
lightClient.trustedBlockRoot
|
|
|
|
proc onStoreInitialized() =
|
|
discard
|
|
|
|
proc onFinalizedHeader() =
|
|
if lightClient.onFinalizedHeader != nil:
|
|
lightClient.onFinalizedHeader(lightClient)
|
|
|
|
proc onOptimisticHeader() =
|
|
if lightClient.onOptimisticHeader != nil:
|
|
lightClient.onOptimisticHeader(lightClient)
|
|
|
|
lightClient.processor = LightClientProcessor.new(
|
|
dumpEnabled, dumpDirInvalid, dumpDirIncoming,
|
|
cfg, genesis_validators_root,
|
|
lightClient.store, getBeaconTime, getTrustedBlockRoot,
|
|
onStoreInitialized, onFinalizedHeader, onOptimisticHeader)
|
|
|
|
proc lightClientVerifier(obj: SomeLightClientObject):
|
|
Future[Result[void, BlockError]] =
|
|
let resfut = newFuture[Result[void, BlockError]]("lightClientVerifier")
|
|
lightClient.processor[].addObject(MsgSource.gossip, obj, resfut)
|
|
resfut
|
|
proc bootstrapVerifier(obj: altair.LightClientBootstrap): auto =
|
|
lightClientVerifier(obj)
|
|
proc updateVerifier(obj: altair.LightClientUpdate): auto =
|
|
lightClientVerifier(obj)
|
|
proc finalityVerifier(obj: altair.LightClientFinalityUpdate): auto =
|
|
lightClientVerifier(obj)
|
|
proc optimisticVerifier(obj: altair.LightClientOptimisticUpdate): auto =
|
|
lightClientVerifier(obj)
|
|
|
|
func isLightClientStoreInitialized(): bool =
|
|
lightClient.store[].isSome
|
|
|
|
func isNextSyncCommitteeKnown(): bool =
|
|
if lightClient.store[].isSome:
|
|
lightClient.store[].get.is_next_sync_committee_known
|
|
else:
|
|
false
|
|
|
|
func getFinalizedPeriod(): SyncCommitteePeriod =
|
|
if lightClient.store[].isSome:
|
|
lightClient.store[].get.finalized_header.slot.sync_committee_period
|
|
else:
|
|
GENESIS_SLOT.sync_committee_period
|
|
|
|
func getOptimisticPeriod(): SyncCommitteePeriod =
|
|
if lightClient.store[].isSome:
|
|
lightClient.store[].get.optimistic_header.slot.sync_committee_period
|
|
else:
|
|
GENESIS_SLOT.sync_committee_period
|
|
|
|
lightClient.manager = LightClientManager.init(
|
|
lightClient.network, rng, getTrustedBlockRoot,
|
|
bootstrapVerifier, updateVerifier, finalityVerifier, optimisticVerifier,
|
|
isLightClientStoreInitialized, isNextSyncCommitteeKnown,
|
|
getFinalizedPeriod, getOptimisticPeriod, getBeaconTime)
|
|
|
|
lightClient.gossipState = {}
|
|
|
|
lightClient
|
|
|
|
proc createLightClient*(
|
|
network: Eth2Node,
|
|
rng: ref BrHmacDrbgContext,
|
|
config: BeaconNodeConf,
|
|
cfg: RuntimeConfig,
|
|
forkDigests: ref ForkDigests,
|
|
getBeaconTime: GetBeaconTimeFn,
|
|
genesis_validators_root: Eth2Digest
|
|
): LightClient =
|
|
createLightClient(
|
|
network, rng,
|
|
config.dumpEnabled, config.dumpDirInvalid, config.dumpDirIncoming,
|
|
cfg, forkDigests, getBeaconTime, genesis_validators_root)
|
|
|
|
proc createLightClient*(
|
|
network: Eth2Node,
|
|
rng: ref BrHmacDrbgContext,
|
|
config: LightClientConf,
|
|
cfg: RuntimeConfig,
|
|
forkDigests: ref ForkDigests,
|
|
getBeaconTime: GetBeaconTimeFn,
|
|
genesis_validators_root: Eth2Digest
|
|
): LightClient =
|
|
createLightClient(
|
|
network, rng,
|
|
dumpEnabled = false, dumpDirInvalid = ".", dumpDirIncoming = ".",
|
|
cfg, forkDigests, getBeaconTime, genesis_validators_root)
|
|
|
|
proc start*(lightClient: LightClient) =
|
|
notice "Starting light client",
|
|
trusted_block_root = lightClient.trustedBlockRoot
|
|
lightClient.manager.start()
|
|
|
|
from
|
|
libp2p/protocols/pubsub/gossipsub
|
|
import
|
|
TopicParams, validateParameters, init
|
|
|
|
proc installMessageValidators*(lightClient: LightClient) =
|
|
template getLocalWallPeriod(): auto =
|
|
lightClient.getBeaconTime().slotOrZero().sync_committee_period
|
|
|
|
let forkDigests = lightClient.forkDigests
|
|
for digest in [forkDigests.altair, forkDigests.bellatrix]:
|
|
lightClient.network.addValidator(
|
|
getLightClientFinalityUpdateTopic(digest),
|
|
proc(msg: altair.LightClientFinalityUpdate): ValidationResult =
|
|
if lightClient.manager.isGossipSupported(getLocalWallPeriod()):
|
|
toValidationResult(
|
|
lightClient.processor[].lightClientFinalityUpdateValidator(
|
|
MsgSource.gossip, msg))
|
|
else:
|
|
ValidationResult.Ignore)
|
|
|
|
lightClient.network.addValidator(
|
|
getLightClientOptimisticUpdateTopic(digest),
|
|
proc(msg: altair.LightClientOptimisticUpdate): ValidationResult =
|
|
if lightClient.manager.isGossipSupported(getLocalWallPeriod()):
|
|
toValidationResult(
|
|
lightClient.processor[].lightClientOptimisticUpdateValidator(
|
|
MsgSource.gossip, msg))
|
|
else:
|
|
ValidationResult.Ignore)
|
|
|
|
const lightClientTopicParams = TopicParams.init()
|
|
static: lightClientTopicParams.validateParameters().tryGet()
|
|
|
|
proc updateGossipStatus*(
|
|
lightClient: LightClient, slot: Slot, dagIsBehind = default(Option[bool])) =
|
|
let
|
|
isBehind =
|
|
if lightClient.manager.isGossipSupported(slot.sync_committee_period):
|
|
false
|
|
elif dagIsBehind.isSome:
|
|
# While separate message validators can be installed for both
|
|
# full node and light client (both are called unless one rejects msg),
|
|
# only a single subscription can be installed per topic for now.
|
|
# The full node subscription is also handled here, even though it
|
|
# does not directly relate to the client side of the LC sync protocol
|
|
dagIsBehind.get
|
|
else:
|
|
true # Force `targetGossipState` to `{}`
|
|
targetGossipState =
|
|
getTargetGossipState(
|
|
slot.epoch,
|
|
lightClient.cfg.ALTAIR_FORK_EPOCH,
|
|
lightClient.cfg.BELLATRIX_FORK_EPOCH,
|
|
isBehind)
|
|
template currentGossipState(): auto = lightClient.gossipState
|
|
if currentGossipState == targetGossipState:
|
|
return
|
|
|
|
if currentGossipState.card == 0 and targetGossipState.card > 0:
|
|
debug "Enabling LC topic subscriptions",
|
|
wallSlot = slot, targetGossipState
|
|
elif currentGossipState.card > 0 and targetGossipState.card == 0:
|
|
debug "Disabling LC topic subscriptions",
|
|
wallSlot = slot
|
|
else:
|
|
# Individual forks added / removed
|
|
discard
|
|
|
|
let
|
|
newGossipForks = targetGossipState - currentGossipState
|
|
oldGossipForks = currentGossipState - targetGossipState
|
|
|
|
for gossipFork in oldGossipForks:
|
|
if gossipFork >= BeaconStateFork.Altair:
|
|
let forkDigest = lightClient.forkDigests[].atStateFork(gossipFork)
|
|
lightClient.network.unsubscribe(
|
|
getLightClientFinalityUpdateTopic(forkDigest))
|
|
|
|
for gossipFork in newGossipForks:
|
|
if gossipFork >= BeaconStateFork.Altair:
|
|
let forkDigest = lightClient.forkDigests[].atStateFork(gossipFork)
|
|
lightClient.network.subscribe(
|
|
getLightClientOptimisticUpdateTopic(forkDigest),
|
|
lightClientTopicParams)
|
|
|
|
lightClient.gossipState = targetGossipState
|