Skip to content

Commit

Permalink
add LightClientStore and keep it in sync
Browse files Browse the repository at this point in the history
The `LightClientStore` represents client data when syncing using the
light client protocol. So far, Nimbus supported serving light client
data to other nodes but did not incorporate client functionality.
A new startup option is introduced, `light-client-trusted-block-root`,
to configure a block root from which light client sync can be started.
The `LightClientStore` is updated with new data from the network but is
not yet used for other purposes. Log messages are produced to reflect
applied updates ("Light client object processed").
  • Loading branch information
etan-status committed Apr 16, 2022
1 parent a86bb35 commit 8f9a874
Show file tree
Hide file tree
Showing 8 changed files with 518 additions and 22 deletions.
15 changes: 13 additions & 2 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import

# Local modules
"."/[beacon_clock, beacon_chain_db, conf],
./gossip_processing/[eth2_processor, block_processor, consensus_manager],
./gossip_processing/[
eth2_processor, block_processor, consensus_manager, light_client_processor],
./networking/eth2_network,
./eth1/eth1_monitor,
./consensus_object_pools/[
blockchain_dag, block_quarantine, exit_pool, attestation_pool,
sync_committee_msg_pool],
./spec/datatypes/base,
./sync/[sync_manager, request_manager],
./sync/[light_client_manager, sync_manager, request_manager],
./validators/[action_tracker, validator_monitor, validator_pool],
./rpc/state_ttl_cache

Expand All @@ -38,6 +39,12 @@ type

GossipState* = set[BeaconStateFork]

LightClient* = object
trustedBlockRoot*: Option[Eth2Digest]
store*: ref Option[LightClientStore]
processor*: ref LightClientProcessor
manager*: LightClientManager

BeaconNode* = ref object
nickname*: string
graffitiBytes*: GraffitiBytes
Expand All @@ -46,6 +53,7 @@ type
db*: BeaconChainDB
config*: BeaconNodeConf
attachedValidators*: ref ValidatorPool
lightClient*: LightClient
dag*: ChainDAGRef
quarantine*: ref Quarantine
attestationPool*: ref AttestationPool
Expand Down Expand Up @@ -87,3 +95,6 @@ template findIt*(s: openArray, predicate: untyped): int =

proc currentSlot*(node: BeaconNode): Slot =
node.beaconClock.now.slotOrZero

import beacon_node_light_client
export beacon_node_light_client
87 changes: 87 additions & 0 deletions beacon_chain/beacon_node_light_client.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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
"."/beacon_node,
./gossip_processing/light_client_processor,
./spec/datatypes/altair,
./sync/light_client_manager

logScope: topics = "beacnde"

proc initLightClient*(
node: BeaconNode,
cfg: RuntimeConfig,
rng: ref BrHmacDrbgContext,
genesis_validators_root: Eth2Digest,
getBeaconTime: GetBeaconTimeFn) =
template config(): auto = node.config

let store = (ref Option[LightClientStore])()

func getTrustedBlockRoot(): Option[Eth2Digest] =
node.lightClient.trustedBlockRoot

proc getLocalWallPeriod(): SyncCommitteePeriod =
node.beaconClock.now.slotOrZero.sync_committee_period

func getFinalizedPeriod(): SyncCommitteePeriod =
if store[].isSome:
store[].get.finalized_header.slot.sync_committee_period
else:
GENESIS_SLOT.sync_committee_period

func isLightClientStoreInitialized(): bool =
store[].isSome

func isNextSyncCommitteeKnown(): bool =
if store[].isSome:
not store[].get.next_sync_committee.isZeroMemory
else:
false

let lightClientProcessor = LightClientProcessor.new(
config.dumpEnabled, config.dumpDirInvalid, config.dumpDirIncoming,
cfg, genesis_validators_root, store, getBeaconTime, getTrustedBlockRoot)

proc lightClientVerifier(obj: SomeLightClientObject):
Future[Result[void, BlockError]] =
let resfut = newFuture[Result[void, BlockError]]("lightClientVerifier")
lightClientProcessor[].addObject(MsgSource.gossip, obj, resfut)
resfut
let
bootstrapVerifier = proc(obj: altair.LightClientBootstrap):
Future[Result[void, BlockError]] =
lightClientVerifier(obj)
updateVerifier = proc(obj: altair.LightClientUpdate):
Future[Result[void, BlockError]] =
lightClientVerifier(obj)
optimisticUpdateVerifier = proc(obj: OptimisticLightClientUpdate):
Future[Result[void, BlockError]] =
lightClientVerifier(obj)

node.lightClient.trustedBlockRoot = config.lightClientTrustedBlockRoot
node.lightClient.store = store
node.lightClient.processor = lightClientProcessor
node.lightClient.manager = LightClientManager.init(
node.network, rng, getTrustedBlockRoot,
bootstrapVerifier, updateVerifier, optimisticUpdateVerifier,
getLocalWallPeriod, getFinalizedPeriod,
isLightClientStoreInitialized, isNextSyncCommitteeKnown)

proc startLightClient*(node: BeaconNode) =
if node.lightClient.trustedBlockRoot.isNone:
return

notice "Starting light client",
trusted_block_root = node.lightClient.trustedBlockRoot.get
node.lightClient.manager.start()
12 changes: 12 additions & 0 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ type
desc: "Weak subjectivity checkpoint in the format block_root:epoch_number"
name: "weak-subjectivity-checkpoint" }: Option[Checkpoint]

lightClientTrustedBlockRoot* {.
hidden
desc: "BETA: Recent trusted finalized block root for accelerating sync using the light client protocol."
name: "light-client-trusted-block-root" }: Option[Eth2Digest]

finalizedCheckpointState* {.
desc: "SSZ file specifying a recent finalized state"
name: "finalized-checkpoint-state" }: Option[InputFile]
Expand Down Expand Up @@ -909,6 +914,13 @@ proc createDumpDirs*(config: BeaconNodeConf) =
warn "Could not create dump directory",
path = config.dumpDirOutgoing, err = ioErrorMsg(res.error)

func parseCmdArg*(T: type Eth2Digest, input: string): T
{.raises: [ValueError, Defect].} =
Eth2Digest.fromHex(input)

func completeCmdArg*(T: type Eth2Digest, input: string): seq[string] =
return @[]

func parseCmdArg*(T: type GraffitiBytes, input: string): T
{.raises: [ValueError, Defect].} =
GraffitiBytes.init(input)
Expand Down
37 changes: 25 additions & 12 deletions beacon_chain/gossip_processing/light_client_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ declareHistogram light_client_store_object_duration_seconds,
"storeObject() duration", buckets = [0.25, 0.5, 1, 2, 4, 8, Inf]

type
GetTrustedBlockRootCallback* =
proc(): Option[Eth2Digest] {.gcsafe, raises: [Defect].}
DidInitializeStoreCallback* =
proc() {.gcsafe, raises: [Defect].}

Expand Down Expand Up @@ -57,12 +59,12 @@ type
# Consumer
# ----------------------------------------------------------------
store: ref Option[LightClientStore]
getBeaconTime*: GetBeaconTimeFn
getBeaconTime: GetBeaconTimeFn
getTrustedBlockRoot: GetTrustedBlockRootCallback
didInitializeStoreCallback: DidInitializeStoreCallback

cfg: RuntimeConfig
genesis_validators_root: Eth2Digest
trustedBlockRoot: Eth2Digest

lastProgressTick: BeaconTime # Moment when last update made progress
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
Expand All @@ -83,9 +85,10 @@ proc new*(
dumpEnabled: bool,
dumpDirInvalid, dumpDirIncoming: string,
cfg: RuntimeConfig,
genesis_validators_root, trustedBlockRoot: Eth2Digest,
genesis_validators_root: Eth2Digest,
store: ref Option[LightClientStore],
getBeaconTime: GetBeaconTimeFn,
getTrustedBlockRoot: GetTrustedBlockRootCallback,
didInitializeStoreCallback: DidInitializeStoreCallback = nil
): ref LightClientProcessor =
(ref LightClientProcessor)(
Expand All @@ -94,12 +97,18 @@ proc new*(
dumpDirIncoming: dumpDirIncoming,
store: store,
getBeaconTime: getBeaconTime,
getTrustedBlockRoot: getTrustedBlockRoot,
didInitializeStoreCallback: didInitializeStoreCallback,
cfg: cfg,
genesis_validators_root: genesis_validators_root,
trustedBlockRoot: trustedBlockRoot
genesis_validators_root: genesis_validators_root
)

func resetStore*(self: var LightClientProcessor) =
self.store[].reset()
self.lastProgressTick.reset()
self.lastDuplicateTick.reset()
self.numDuplicatesSinceProgress.reset()

# Storage
# ------------------------------------------------------------------------------

Expand Down Expand Up @@ -160,21 +169,25 @@ proc storeObject*(
if store[].isSome:
err(BlockError.Duplicate)
else:
let initRes = initialize_light_client_store(
self.trustedBlockRoot, obj)
if initRes.isErr:
err(initRes.error)
let trustedBlockRoot = self.getTrustedBlockRoot()
if trustedBlockRoot.isNone:
err(BlockError.MissingParent)
else:
store[] = some(initRes.get)
ok()
let initRes =
initialize_light_client_store(trustedBlockRoot.get, obj)
if initRes.isErr:
err(initRes.error)
else:
store[] = some(initRes.get)
ok()
elif obj is altair.LightClientUpdate:
if store[].isNone:
err(BlockError.MissingParent)
else:
store[].get.process_light_client_update(
obj, wallSlot, self.cfg, self.genesis_validators_root,
allowForceUpdate = false)
elif obj is altair.OptimisticLightClientUpdate:
elif obj is OptimisticLightClientUpdate:
if store[].isNone:
err(BlockError.MissingParent)
else:
Expand Down
9 changes: 4 additions & 5 deletions beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ proc init*(T: type BeaconNode,
else:
nil

var node = BeaconNode(
let node = BeaconNode(
nickname: nickname,
graffitiBytes: if config.graffiti.isSome: config.graffiti.get
else: defaultGraffitiBytes(),
Expand All @@ -677,10 +677,8 @@ proc init*(T: type BeaconNode,
validatorMonitor: validatorMonitor,
stateTtlCache: stateTtlCache
)

node.initFullNode(
rng, dag, taskpool, getBeaconTime)

node.initLightClient(cfg, rng, dag.genesis_validators_root, getBeaconTime)
node.initFullNode(rng, dag, taskpool, getBeaconTime)
node

func verifyFinalization(node: BeaconNode, slot: Slot) =
Expand Down Expand Up @@ -1458,6 +1456,7 @@ proc run(node: BeaconNode) {.raises: [Defect, CatchableError].} =
wallTime = node.beaconClock.now()
wallSlot = wallTime.slotOrZero()

node.startLightClient()
node.requestManager.start()
node.syncManager.start()

Expand Down
Loading

0 comments on commit 8f9a874

Please sign in to comment.