Skip to content

Commit

Permalink
add option to collect light client data (#3474)
Browse files Browse the repository at this point in the history
Light clients require full nodes to serve additional data so that they
can stay in sync with the network. This patch adds a new launch option
`--import-light-client-data` to configure what data to make available.
For now, data is only kept in memory; it is not persisted at this time.
Note that data is only locally collected, a separate patch is needed to
actually make it availble over the network. `--serve-light-client-data`
will be used for serving data, but is not functional yet outside tests.
  • Loading branch information
etan-status authored Mar 11, 2022
1 parent 21b71bd commit ae408c2
Show file tree
Hide file tree
Showing 10 changed files with 1,273 additions and 10 deletions.
9 changes: 8 additions & 1 deletion AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ OK: 3/3 Fail: 0/3 Skip: 0/3
+ [SCRYPT] Network Keystore encryption OK
```
OK: 9/9 Fail: 0/9 Skip: 0/9
## Light client [Preset: mainnet]
```diff
+ Init from checkpoint OK
+ Light client sync OK
+ Pre-Altair OK
```
OK: 3/3 Fail: 0/3 Skip: 0/3
## ListKeys requests [Preset: mainnet]
```diff
+ Correct token provided [Preset: mainnet] OK
Expand Down Expand Up @@ -513,4 +520,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
OK: 1/1 Fail: 0/1 Skip: 0/1

---TOTAL---
OK: 282/287 Fail: 0/287 Skip: 5/287
OK: 285/290 Fail: 0/290 Skip: 5/290
16 changes: 16 additions & 0 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import
./validators/slashing_protection_common,
./filepath

from consensus_object_pools/block_pools_types_light_client
import ImportLightClientData

export
uri, nat, enr,
defaultEth2TcpPort, enabledLogLevel, ValidIpAddress,
Expand Down Expand Up @@ -419,6 +422,19 @@ type
desc: "A file specifying the authorization token required for accessing the keymanager API"
name: "keymanager-token-file" }: Option[InputFile]

serveLightClientData* {.
hidden
desc: "BETA: Serve data for enabling light clients to stay in sync with the network"
defaultValue: false
name: "serve-light-client-data"}: bool

importLightClientData* {.
hidden
desc: "BETA: Which classes of light client data to import. " &
"Must be one of: none, only-new, full (slow startup), on-demand (may miss validator duties)"
defaultValue: ImportLightClientData.None
name: "import-light-client-data"}: ImportLightClientData

inProcessValidators* {.
desc: "Disable the push model (the beacon node tells a signing process with the private keys of the validators what to sign and when) and load the validators in the beacon node itself"
defaultValue: true # the use of the nimbus_signing_process binary by default will be delayed until async I/O over stdin/stdout is developed for the child process.
Expand Down
5 changes: 4 additions & 1 deletion beacon_chain/consensus_object_pools/block_clearance.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import
chronicles,
stew/[assign2, results],
../spec/[forks, signatures, signatures_batch, state_transition],
"."/[block_dag, blockchain_dag]
"."/[block_dag, blockchain_dag, blockchain_dag_light_client]

export results, signatures_batch, block_dag, blockchain_dag

Expand Down Expand Up @@ -84,6 +84,9 @@ proc addResolvedHeadBlock(
putBlockDur = putBlockTick - startTick,
epochRefDur = epochRefTick - putBlockTick

# Update light client data
dag.processNewBlockForLightClient(state, trustedBlock, parent)

# Notify others of the new block before processing the quarantine, such that
# notifications for parents happens before those of the children
if onBlockAdded != nil:
Expand Down
20 changes: 18 additions & 2 deletions beacon_chain/consensus_object_pools/block_pools_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import
../spec/datatypes/[phase0, altair, bellatrix],
".."/beacon_chain_db,
../validators/validator_monitor,
./block_dag
./block_dag, block_pools_types_light_client

export
options, sets, tables, hashes, helpers, beacon_chain_db, block_dag,
validator_monitor
block_pools_types_light_client, validator_monitor

# ChainDAG and types related to forming a DAG of blocks, keeping track of their
# relationships and allowing various forms of lookups
Expand Down Expand Up @@ -178,6 +178,12 @@ type

cfg*: RuntimeConfig

serveLightClientData*: bool
## Whether to make local light client data available or not

importLightClientData*: ImportLightClientData
## Which classes of light client data to import

epochRefs*: array[32, EpochRef]
## Cached information about a particular epoch ending with the given
## block - we limit the number of held EpochRefs to put a cap on
Expand All @@ -189,6 +195,14 @@ type
## value with other components which don't have access to the
## full ChainDAG.

# -----------------------------------
# Data to enable light clients to stay in sync with the network

lightClientCache*: LightClientCache

# -----------------------------------
# Callbacks

onBlockAdded*: OnBlockCallback
## On block added callback
onHeadChanged*: OnHeadCallback
Expand All @@ -197,6 +211,8 @@ type
## On beacon chain reorganization
onFinHappened*: OnFinalizedCallback
## On finalization callback
onOptimisticLightClientUpdate*: OnOptimisticLightClientUpdateCallback
## On `OptimisticLightClientUpdate` updated callback

headSyncCommittees*: SyncCommitteeCache
## A cache of the sync committees, as they appear in the head state -
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 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
# Status libraries
stew/bitops2,
# Beacon chain internals
../spec/datatypes/altair,
./block_dag

type
OnOptimisticLightClientUpdateCallback* =
proc(data: OptimisticLightClientUpdate) {.gcsafe, raises: [Defect].}

ImportLightClientData* {.pure.} = enum
## Controls which classes of light client data are imported.
None = "none"
## Import no light client data.
OnlyNew = "only-new"
## Import only new light client data.
Full = "full"
## Import light client data for entire weak subjectivity period.
OnDemand = "on-demand"
## No precompute of historic data. Is slow and may miss validator duties.

CachedLightClientData* = object
## Cached data from historical non-finalized states to improve speed when
## creating future `LightClientUpdate` and `LightClientBootstrap` instances.
current_sync_committee_branch*:
array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]

next_sync_committee_branch*:
array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]

finalized_bid*: BlockId
finality_branch*:
array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest]

CachedLightClientBootstrap* = object
## Cached data from historical finalized epoch boundary blocks to improve
## speed when creating future `LightClientBootstrap` instances.
current_sync_committee_branch*:
array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]

LightClientCache* = object
data*: Table[BlockId, CachedLightClientData]
## Cached data for creating future `LightClientUpdate` instances.
## Key is the block ID of which the post state was used to get the data.
## Data is stored for the most recent 4 finalized checkpoints, as well as
## for all non-finalized blocks.

bootstrap*: Table[Slot, CachedLightClientBootstrap]
## Cached data for creating future `LightClientBootstrap` instances.
## Key is the block slot of which the post state was used to get the data.
## Data is stored for finalized epoch boundary blocks.

latestCheckpoints*: array[4, Checkpoint]
## Keeps track of the latest four `finalized_checkpoint` references
## leading to `finalizedHead`. Used to prune `data`.
## Non-finalized states may only refer to these checkpoints.

lastCheckpointIndex*: int
## Last index that was modified in `latestCheckpoints`.

bestUpdates*: Table[SyncCommitteePeriod, altair.LightClientUpdate]
## Stores the `LightClientUpdate` with the most `sync_committee_bits` per
## `SyncCommitteePeriod`. Updates with a finality proof have precedence.

pendingBestUpdates*:
Table[(SyncCommitteePeriod, Eth2Digest), altair.LightClientUpdate]
## Same as `bestUpdates`, but for `SyncCommitteePeriod` with
## `next_sync_committee` that are not finalized. Key is `(period,
## hash_tree_root(current_sync_committee | next_sync_committee)`.

latestUpdate*: altair.LightClientUpdate
## Tracks the `LightClientUpdate` for the latest slot. This may be older
## than head for empty slots or if not signed by sync committee.

optimisticUpdate*: OptimisticLightClientUpdate
## Tracks the `OptimisticLightClientUpdate` for the latest slot. This may
## be older than head for empty slots or if not signed by sync committee.

importTailSlot*: Slot
## The earliest slot for which light client data is collected.
## Only relevant for `ImportLightClientData.OnlyNew`.
30 changes: 28 additions & 2 deletions beacon_chain/consensus_object_pools/blockchain_dag.nim
Original file line number Diff line number Diff line change
Expand Up @@ -477,11 +477,23 @@ proc updateBeaconMetrics(state: StateData, cache: var StateCache) =
beacon_active_validators.set(active_validators)
beacon_current_active_validators.set(active_validators)

import blockchain_dag_light_client

export
blockchain_dag_light_client.getBestLightClientUpdateForPeriod,
blockchain_dag_light_client.getLatestLightClientUpdate,
blockchain_dag_light_client.getOptimisticLightClientUpdate,
blockchain_dag_light_client.getLightClientBootstrap

proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
validatorMonitor: ref ValidatorMonitor, updateFlags: UpdateFlags,
onBlockCb: OnBlockCallback = nil, onHeadCb: OnHeadCallback = nil,
onReorgCb: OnReorgCallback = nil,
onFinCb: OnFinalizedCallback = nil): ChainDAGRef =
onFinCb: OnFinalizedCallback = nil,
onOptimisticLCUpdateCb: OnOptimisticLightClientUpdateCallback = nil,
serveLightClientData = false,
importLightClientData = ImportLightClientData.None
): ChainDAGRef =
# TODO we require that the db contains both a head and a tail block -
# asserting here doesn't seem like the right way to go about it however..

Expand Down Expand Up @@ -653,11 +665,14 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
# allow skipping some validation.
updateFlags: {verifyFinalization} * updateFlags,
cfg: cfg,
serveLightClientData: serveLightClientData,
importLightClientData: importLightClientData,

onBlockAdded: onBlockCb,
onHeadChanged: onHeadCb,
onReorgHappened: onReorgCb,
onFinHappened: onFinCb
onFinHappened: onFinCb,
onOptimisticLightClientUpdate: onOptimisticLCUpdateCb
)

block: # Initialize dag states
Expand Down Expand Up @@ -779,6 +794,8 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
stateDur = stateTick - summariesTick,
indexDur = Moment.now() - stateTick

dag.initLightClientCache()

dag

template genesisValidatorsRoot*(dag: ChainDAGRef): Eth2Digest =
Expand Down Expand Up @@ -1286,6 +1303,9 @@ proc pruneBlocksDAG(dag: ChainDAGRef) =

var cur = head.atSlot()
while not cur.blck.isAncestorOf(dag.finalizedHead.blck):
# Update light client data
dag.deleteLightClientData(cur.blck.bid)

dag.delState(cur) # TODO: should we move that disk I/O to `onSlotEnd`

if cur.isProposed():
Expand Down Expand Up @@ -1485,6 +1505,9 @@ proc updateHead*(
doAssert (not finalizedHead.blck.isNil),
"Block graph should always lead to a finalized block"

# Update light client data
dag.processHeadChangeForLightClient()

let (isAncestor, ancestorDepth) = lastHead.getDepth(newHead)
if not(isAncestor):
notice "Updated head block with chain reorg",
Expand Down Expand Up @@ -1571,6 +1594,9 @@ proc updateHead*(
# therefore no longer be considered as part of the chain we're following
dag.pruneBlocksDAG()

# Update light client data
dag.processFinalizationForLightClient()

# Send notification about new finalization point via callback.
if not(isNil(dag.onFinHappened)):
let stateRoot =
Expand Down
Loading

0 comments on commit ae408c2

Please sign in to comment.