Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate light client into beacon node #3557

Merged
merged 12 commits into from
Jun 7, 2022
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
99 changes: 99 additions & 0 deletions beacon_chain/beacon_node_light_client.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 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*(
etan-status marked this conversation as resolved.
Show resolved Hide resolved
node: BeaconNode,
rng: ref BrHmacDrbgContext,
cfg: RuntimeConfig,
forkDigests: ref ForkDigests,
getBeaconTime: GetBeaconTimeFn,
genesis_validators_root: Eth2Digest) =
template config(): auto = node.config

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 @@ -266,6 +266,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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this Option/applyConfigDefault help?

template applyConfigDefault(field: untyped): untyped =
if config.`field`.isNone:
if not metadata.configDefaults.`field`.isZeroMemory:
info "Applying network config default",
eth2Network = config.eth2Network,
`field` = metadata.configDefaults.`field`
config.`field` = some metadata.configDefaults.`field`
applyConfigDefault(lightClientEnable)
applyConfigDefault(serveLightClientData)
applyConfigDefault(importLightClientData)

show the mechanism, but it's not clear to me why this version (see also lightClientTrustedBlockRoot) is better than the more straightforward Option-free variation which sets some appropriate default and avoids the Option-.get() indirection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had it at default-on for kiln/ropsten/prater, but you are right that now with the default off the straight-forward bool would also work. I guess when optimistic sync is fully implemented, that this would need to be changed to detect the case of "no user preference" vs "user selected off" and apply network specific default in first case. I can get rid of it once more and it can then be reintroduced later, or we can just keep it as is, up to you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, keep as is.


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 @@ -904,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
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