Skip to content

Commit

Permalink
integrate light client into beacon node (#3557)
Browse files Browse the repository at this point in the history
Adds a `LightClient` instance to the beacon node as preparation to
accelerate syncing in the future (optimistic sync).

- `--light-client-enable` turns on the feature
- `--light-client-trusted-block-root` configures block to start from

If no block root is configured, light client tracks DAG `finalizedHead`.
  • Loading branch information
etan-status authored Jun 7, 2022
1 parent 3bd9622 commit 72a46bd
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 260 deletions.
12 changes: 7 additions & 5 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2021 Status Research & Development GmbH
# Copyright (c) 2018-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).
Expand All @@ -14,7 +14,7 @@ import
chronos, json_rpc/servers/httpserver, presto,

# Local modules
"."/[beacon_clock, beacon_chain_db, conf],
"."/[beacon_clock, beacon_chain_db, conf, light_client],
./gossip_processing/[eth2_processor, block_processor, consensus_manager],
./networking/eth2_network,
./eth1/eth1_monitor,
Expand All @@ -27,9 +27,10 @@ import
./rpc/state_ttl_cache

export
osproc, chronos, httpserver, presto, action_tracker, beacon_clock,
beacon_chain_db, conf, attestation_pool, sync_committee_msg_pool,
validator_pool, eth2_network, eth1_monitor, request_manager, sync_manager,
osproc, chronos, httpserver, presto, action_tracker,
beacon_clock, beacon_chain_db, conf, light_client,
attestation_pool, sync_committee_msg_pool, validator_pool,
eth2_network, eth1_monitor, request_manager, sync_manager,
eth2_processor, blockchain_dag, block_quarantine, base, exit_pool,
validator_monitor, consensus_manager

Expand All @@ -44,6 +45,7 @@ type
db*: BeaconChainDB
config*: BeaconNodeConf
attachedValidators*: ref ValidatorPool
lightClient*: LightClient
dag*: ChainDAGRef
quarantine*: ref Quarantine
attestationPool*: ref AttestationPool
Expand Down
103 changes: 103 additions & 0 deletions beacon_chain/beacon_node_light_client.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 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,
./beacon_node

logScope: topics = "beacnde"

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

# Creating a light client is not dependent on `lightClientEnable`
# because the light client module also handles gossip subscriptions
# for broadcasting light client data as a server.

let lightClient = createLightClient(
node.network, rng, config, cfg,
forkDigests, getBeaconTime, genesis_validators_root)

if config.lightClientEnable.get:
lightClient.trustedBlockRoot = config.lightClientTrustedBlockRoot

elif config.lightClientTrustedBlockRoot.isSome:
warn "Ignoring `lightClientTrustedBlockRoot`, light client not enabled",
lightClientEnable = config.lightClientEnable.get,
lightClientTrustedBlockRoot = config.lightClientTrustedBlockRoot

node.lightClient = lightClient

proc startLightClient*(node: BeaconNode) =
if not node.config.lightClientEnable.get:
return

node.lightClient.start()

proc installLightClientMessageValidators*(node: BeaconNode) =
let eth2Processor =
if node.config.serveLightClientData.get:
# Process gossip using both full node and light client
node.processor
elif node.config.lightClientEnable.get:
# Only process gossip using light client
nil
else:
# Light client topics will never be subscribed to, no validators needed
return

node.lightClient.installMessageValidators(eth2Processor)

proc updateLightClientGossipStatus*(
node: BeaconNode, slot: Slot, dagIsBehind: bool) =
let isBehind =
if node.config.serveLightClientData.get:
# Forward DAG's readiness to handle light client gossip
dagIsBehind
else:
# Full node is not interested in gossip
true

node.lightClient.updateGossipStatus(slot, some isBehind)

proc updateLightClientFromDag*(node: BeaconNode) =
if not node.config.lightClientEnable.get:
return
if node.config.lightClientTrustedBlockRoot.isSome:
return

let
dagHead = node.dag.finalizedHead
dagPeriod = dagHead.slot.sync_committee_period
if dagHead.slot < node.dag.cfg.ALTAIR_FORK_EPOCH.start_slot:
return

let lcHeader = node.lightClient.finalizedHeader
if lcHeader.isSome:
if dagPeriod <= lcHeader.get.slot.sync_committee_period:
return

let
bdata = node.dag.getForkedBlock(dagHead.blck.bid).valueOr:
return
header = bdata.toBeaconBlockHeader
current_sync_committee = block:
var tmpState = assignClone(node.dag.headState)
node.dag.currentSyncCommitteeForPeriod(tmpState[], dagPeriod).valueOr:
return
node.lightClient.resetToFinalizedHeader(header, current_sync_committee)
17 changes: 17 additions & 0 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,16 @@ type
desc: "Weak subjectivity checkpoint in the format block_root:epoch_number"
name: "weak-subjectivity-checkpoint" }: Option[Checkpoint]

lightClientEnable* {.
hidden
desc: "BETA: Accelerate sync using light client."
name: "light-client-enable" .}: Option[bool]

lightClientTrustedBlockRoot* {.
hidden
desc: "BETA: Recent trusted finalized block root to initialize light client from."
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 @@ -906,6 +916,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
7 changes: 0 additions & 7 deletions beacon_chain/conf_light_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,3 @@ type LightClientConf* = object
desc: "The wall-time epoch at which to exit the program. (for testing purposes)"
defaultValue: 0
name: "stop-at-epoch" }: uint64

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

func completeCmdArg*(T: type Eth2Digest, input: string): seq[string] =
return @[]
20 changes: 20 additions & 0 deletions beacon_chain/consensus_object_pools/blockchain_dag.nim
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,26 @@ proc getForkedBlock*(
# In case we didn't have a summary - should be rare, but ..
dag.db.getForkedBlock(root)

proc currentSyncCommitteeForPeriod*(
dag: ChainDAGRef,
tmpState: var ForkedHashedBeaconState,
period: SyncCommitteePeriod): Opt[SyncCommittee] =
## Fetch a `SyncCommittee` for a given sync committee period.
## For non-finalized periods, follow the chain as selected by fork choice.
let earliestSlot = max(dag.tail.slot, dag.cfg.ALTAIR_FORK_EPOCH.start_slot)
if period < earliestSlot.sync_committee_period:
return err()
let
periodStartSlot = period.start_slot
syncCommitteeSlot = max(periodStartSlot, earliestSlot)
bsi = ? dag.getBlockIdAtSlot(syncCommitteeSlot)
dag.withUpdatedState(tmpState, bsi) do:
withState(state):
when stateFork >= BeaconStateFork.Altair:
ok state.data.current_sync_committee
else: err()
do: err()

proc updateBeaconMetrics(
state: ForkedHashedBeaconState, bid: BlockId, cache: var StateCache) =
# https://github.com/ethereum/eth2.0-metrics/blob/master/metrics.md#additional-metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,17 @@ proc getExistingForkedBlock*(
doAssert verifyFinalization notin dag.updateFlags
bdata

proc currentSyncCommitteeForPeriod(
proc existingCurrentSyncCommitteeForPeriod(
dag: ChainDAGRef,
tmpState: var ForkedHashedBeaconState,
period: SyncCommitteePeriod): Opt[SyncCommittee] =
## Fetch a `SyncCommittee` for a given sync committee period.
## For non-finalized periods, follow the chain as selected by fork choice.
let earliestSlot = dag.computeEarliestLightClientSlot
doAssert period >= earliestSlot.sync_committee_period
let
periodStartSlot = period.start_slot
syncCommitteeSlot = max(periodStartSlot, earliestSlot)
bsi = ? dag.getExistingBlockIdAtSlot(syncCommitteeSlot)
dag.withUpdatedExistingState(tmpState, bsi) do:
withState(state):
when stateFork >= BeaconStateFork.Altair:
ok state.data.current_sync_committee
else: raiseAssert "Unreachable"
do: err()
## Wrapper around `currentSyncCommitteeForPeriod` for states known to exist.
let syncCommittee = dag.currentSyncCommitteeForPeriod(tmpState, period)
if syncCommittee.isErr:
error "Current sync committee failed to load unexpectedly",
period, tail = dag.tail.slot
doAssert verifyFinalization notin dag.updateFlags
syncCommittee

template syncCommitteeRoot(
state: HashedBeaconStateWithSyncCommittee): Eth2Digest =
Expand Down Expand Up @@ -795,7 +788,7 @@ proc getLightClientBootstrap*(
bootstrap.header =
blck.toBeaconBlockHeader
bootstrap.current_sync_committee =
? dag.currentSyncCommitteeForPeriod(tmpState[], period)
? dag.existingCurrentSyncCommitteeForPeriod(tmpState[], period)
bootstrap.current_sync_committee_branch =
cachedBootstrap.current_sync_committee_branch
return ok bootstrap
Expand Down
36 changes: 2 additions & 34 deletions beacon_chain/gossip_processing/eth2_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export
light_client_pool, sync_committee_msg_pool, validator_pool, beacon_clock,
gossip_validation, block_processor, batch_validation, block_quarantine

logScope: topics = "gossip_eth2"

# Metrics for tracking attestation and beacon block loss
declareCounter beacon_attestations_received,
"Number of valid unaggregated attestations processed by this node"
Expand Down Expand Up @@ -62,14 +64,6 @@ declareCounter beacon_sync_committee_contributions_received,
"Number of valid sync committee contributions processed by this node"
declareCounter beacon_sync_committee_contributions_dropped,
"Number of invalid sync committee contributions dropped by this node", labels = ["reason"]
declareCounter beacon_light_client_finality_updates_received,
"Number of valid LC finality updates processed by this node"
declareCounter beacon_light_client_finality_updates_dropped,
"Number of invalid LC finality updates dropped by this node", labels = ["reason"]
declareCounter beacon_light_client_optimistic_updates_received,
"Number of valid LC optimistic updates processed by this node"
declareCounter beacon_light_client_optimistic_updates_dropped,
"Number of invalid LC optimistic updates dropped by this node", labels = ["reason"]

const delayBuckets = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, Inf]

Expand Down Expand Up @@ -553,45 +547,19 @@ proc lightClientFinalityUpdateValidator*(
self: var Eth2Processor, src: MsgSource,
finality_update: altair.LightClientFinalityUpdate
): Result[void, ValidationError] =
logScope:
finality_update

debug "LC finality update received"

let
wallTime = self.getCurrentBeaconTime()
v = validateLightClientFinalityUpdate(
self.lightClientPool[], self.dag, finality_update, wallTime)
if v.isOk():
trace "LC finality update validated"

beacon_light_client_finality_updates_received.inc()
else:
debug "Dropping LC finality update", error = v.error
beacon_light_client_finality_updates_dropped.inc(1, [$v.error[0]])

v

# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#light_client_optimistic_update
proc lightClientOptimisticUpdateValidator*(
self: var Eth2Processor, src: MsgSource,
optimistic_update: altair.LightClientOptimisticUpdate
): Result[void, ValidationError] =
logScope:
optimistic_update

debug "LC optimistic update received"

let
wallTime = self.getCurrentBeaconTime()
v = validateLightClientOptimisticUpdate(
self.lightClientPool[], self.dag, optimistic_update, wallTime)
if v.isOk():
trace "LC optimistic update validated"

beacon_light_client_optimistic_updates_received.inc()
else:
debug "Dropping LC optimistic update", error = v.error
beacon_light_client_optimistic_updates_dropped.inc(1, [$v.error[0]])

v
Loading

0 comments on commit 72a46bd

Please sign in to comment.