diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 832d1edf56..f26794c194 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -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 @@ -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 @@ -46,6 +53,7 @@ type db*: BeaconChainDB config*: BeaconNodeConf attachedValidators*: ref ValidatorPool + lightClient*: LightClient dag*: ChainDAGRef quarantine*: ref Quarantine attestationPool*: ref AttestationPool @@ -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 diff --git a/beacon_chain/beacon_node_light_client.nim b/beacon_chain/beacon_node_light_client.nim new file mode 100644 index 0000000000..4d6e45eda5 --- /dev/null +++ b/beacon_chain/beacon_node_light_client.nim @@ -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() diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index 134989c8a1..171bc0e7e5 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -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] @@ -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) diff --git a/beacon_chain/gossip_processing/light_client_processor.nim b/beacon_chain/gossip_processing/light_client_processor.nim index 7aac56f190..92d5066162 100644 --- a/beacon_chain/gossip_processing/light_client_processor.nim +++ b/beacon_chain/gossip_processing/light_client_processor.nim @@ -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].} @@ -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 @@ -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)( @@ -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 # ------------------------------------------------------------------------------ @@ -160,13 +169,17 @@ 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) @@ -174,7 +187,7 @@ proc storeObject*( 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: diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 53b78c4d1a..30d8bc82e9 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -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(), @@ -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) = @@ -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() diff --git a/beacon_chain/sync/light_client_manager.nim b/beacon_chain/sync/light_client_manager.nim new file mode 100644 index 0000000000..83ade7c625 --- /dev/null +++ b/beacon_chain/sync/light_client_manager.nim @@ -0,0 +1,371 @@ +# 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 chronos, chronicles, stew/base10 +import + eth/p2p/discoveryv5/random2, + ../spec/datatypes/[altair], + ../networking/eth2_network, + "."/sync_protocol, "."/sync_manager +export sync_manager + +logScope: + topics = "lcman" + +type + Nothing = object + ResponseError = object of CatchableError + Endpoint[K, V] = + (K, V) # https://github.com/nim-lang/Nim/issues/19531 + BestLightClientUpdatesByRangeEndpoint = + Endpoint[Slice[SyncCommitteePeriod], altair.LightClientUpdate] + LatestLightClientUpdateEndpoint = + Endpoint[Nothing, altair.LightClientUpdate] + OptimisticLightClientUpdateEndpoint = + Endpoint[Nothing, OptimisticLightClientUpdate] + LightClientBootstrapEndpoint = + Endpoint[Eth2Digest, altair.LightClientBootstrap] + + ValueVerifier[V] = + proc(v: V): Future[Result[void, BlockError]] {.gcsafe, raises: [Defect].} + LightClientBootstrapVerifier* = + ValueVerifier[altair.LightClientBootstrap] + LightClientUpdateVerifier* = + ValueVerifier[altair.LightClientUpdate] + OptimisticLightClientUpdateVerifier* = + ValueVerifier[OptimisticLightClientUpdate] + + GetTrustedBlockRootCallback* = + proc(): Option[Eth2Digest] {.gcsafe, raises: [Defect].} + GetSyncCommitteePeriodCallback* = + proc(): SyncCommitteePeriod {.gcsafe, raises: [Defect].} + GetBoolCallback* = + proc(): bool {.gcsafe, raises: [Defect].} + + LightClientManager* = object + network: Eth2Node + rng: ref BrHmacDrbgContext + getTrustedBlockRoot: GetTrustedBlockRootCallback + bootstrapVerifier: LightClientBootstrapVerifier + updateVerifier: LightClientUpdateVerifier + optimisticUpdateVerifier: OptimisticLightClientUpdateVerifier + getLocalWallPeriod: GetSyncCommitteePeriodCallback + getFinalizedPeriod: GetSyncCommitteePeriodCallback + isLightClientStoreInitialized: GetBoolCallback + isNextSyncCommitteeKnown: GetBoolCallback + loopFuture: Future[void] + +func init*( + T: type LightClientManager, + network: Eth2Node, + rng: ref BrHmacDrbgContext, + getTrustedBlockRoot: GetTrustedBlockRootCallback, + bootstrapVerifier: LightClientBootstrapVerifier, + updateVerifier: LightClientUpdateVerifier, + optimisticUpdateVerifier: OptimisticLightClientUpdateVerifier, + getLocalWallPeriod: GetSyncCommitteePeriodCallback, + getFinalizedPeriod: GetSyncCommitteePeriodCallback, + isLightClientStoreInitialized: GetBoolCallback, + isNextSyncCommitteeKnown: GetBoolCallback +): LightClientManager = + ## Initialize light client manager. + LightClientManager( + network: network, + rng: rng, + getTrustedBlockRoot: getTrustedBlockRoot, + bootstrapVerifier: bootstrapVerifier, + updateVerifier: updateVerifier, + optimisticUpdateVerifier: optimisticUpdateVerifier, + getLocalWallPeriod: getLocalWallPeriod, + getFinalizedPeriod: getFinalizedPeriod, + isLightClientStoreInitialized: isLightClientStoreInitialized, + isNextSyncCommitteeKnown: isNextSyncCommitteeKnown + ) + +# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#bestlightclientupdatesbyrange +from ../spec/light_client_sync import get_active_header +from sync_protocol import MAX_REQUEST_LIGHT_CLIENT_UPDATES +type BestLightClientUpdatesByRangeResp = NetRes[seq[altair.LightClientUpdate]] +proc doRequest( + e: typedesc[BestLightClientUpdatesByRangeEndpoint], + peer: Peer, + periods: Slice[SyncCommitteePeriod] +): Future[BestLightClientUpdatesByRangeResp] {. + async, raises: [Defect, IOError].} = + let + startPeriod = periods.a + lastPeriod = periods.b + reqCount = min(periods.len, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + let response = + await peer.bestLightClientUpdatesByRange(startPeriod, reqCount.uint64) + if response.isOk: + if response.get.len > reqCount: + raise newException(ResponseError, "Too many values in response" & + " (" & Base10.toString(response.get.lenu64) & + " > " & Base10.toString(reqCount.uint) & ")") + var expectedPeriod = startPeriod + for update in response.get: + let period = update.get_active_header().slot.sync_committee_period + if period < expectedPeriod: + raise newException(ResponseError, "Unexpected sync committee period" & + " (" & Base10.toString(distinctBase(period)) & + " < " & Base10.toString(distinctBase(expectedPeriod)) & ")") + if period > expectedPeriod: + if period > lastPeriod: + raise newException(ResponseError, "Sync committee period too high" & + " (" & Base10.toString(distinctBase(period)) & + " > " & Base10.toString(distinctBase(lastPeriod)) & ")") + expectedPeriod = period + inc expectedPeriod + return response + +# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#getlatestlightclientupdate +proc doRequest( + e: typedesc[LatestLightClientUpdateEndpoint], + peer: Peer +): Future[NetRes[altair.LightClientUpdate]] {. + raises: [Defect, IOError].} = + peer.latestLightClientUpdate() + +# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#getoptimisticlightclientupdate +proc doRequest( + e: typedesc[OptimisticLightClientUpdateEndpoint], + peer: Peer +): Future[NetRes[OptimisticLightClientUpdate]] {. + raises: [Defect, IOError].} = + peer.optimisticLightClientUpdate() + +# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#getlightclientbootstrap +proc doRequest( + e: typedesc[LightClientBootstrapEndpoint], + peer: Peer, + blockRoot: Eth2Digest +): Future[NetRes[altair.LightClientBootstrap]] {. + raises: [Defect, IOError].} = + peer.lightClientBootstrap(blockRoot) + +template valueVerifier[E]( + self: LightClientManager, + e: typedesc[E] +): ValueVerifier[E.V] = + when E.V is altair.LightClientBootstrap: + self.bootstrapVerifier + elif E.V is altair.LightClientUpdate: + self.updateVerifier + elif E.V is OptimisticLightClientUpdate: + self.optimisticUpdateVerifier + else: static: doAssert false + +iterator values(v: auto): auto = + ## Local helper for `workerTask` to share the same implementation for both + ## scalar and aggregate values, by treating scalars as 1-length aggregates. + when v is seq: + for i in v: + yield i + else: + yield v + +proc workerTask[E]( + self: LightClientManager, + e: typedesc[E], + key: E.K +): Future[bool] {.async.} = + var + peer: Peer + didProgress = false + try: + peer = await self.network.peerPool.acquire() + let value = + when E.K is Nothing: + await E.doRequest(peer) + else: + await E.doRequest(peer, key) + if value.isOk: + var applyReward = false + for val in value.get.values: + let res = await self.valueVerifier(E)(val) + if res.isErr: + case res.error + of BlockError.MissingParent: + # Stop, requires different request to progress + return didProgress + of BlockError.Duplicate: + # Ignore, a concurrent request may have queried this already + when E.V is altair.LightClientBootstrap: + didProgress = true + else: + discard + of BlockError.UnviableFork: + # Descore, peer is on an incompatible fork version + notice "Received value from an unviable fork", value = val.shortLog, + endpoint = E.name, peer, peer_score = peer.getScore() + peer.updateScore(PeerScoreUnviableFork) + return didProgress + of BlockError.Invalid: + # Descore, received data is malformed + warn "Received invalid value", value = val.shortLog, + endpoint = E.name, peer, peer_score = peer.getScore() + peer.updateScore(PeerScoreBadBlocks) + return didProgress + else: + # Reward, peer returned something useful + applyReward = true + didProgress = true + if applyReward: + peer.updateScore(PeerScoreGoodBlocks) + else: + peer.updateScore(PeerScoreNoBlocks) + debug "Failed to receive value on request", value, + endpoint = E.name, peer, peer_score = peer.getScore() + except ResponseError as exc: + warn "Received invalid response", error = exc.msg, + endpoint = E.name, peer, peer_score = peer.getScore() + peer.updateScore(PeerScoreBadBlocks) + except CancelledError as exc: + raise exc + except CatchableError as exc: + peer.updateScore(PeerScoreNoBlocks) + debug "Unexpected exception while receiving value", exc = exc.msg, + endpoint = E.name, peer, peer_score = peer.getScore() + raise exc + finally: + if peer != nil: + self.network.peerPool.release(peer) + return didProgress + +proc query[E]( + self: LightClientManager, + e: typedesc[E], + key: E.K +): Future[bool] {.async.} = + let start = SyncMoment.now(0) + + const PARALLEL_REQUESTS = 2 + var workers: array[PARALLEL_REQUESTS, Future[bool]] + + # Start concurrent workers + for i in 0 ..< workers.len: + try: + workers[i] = self.workerTask(e, key) + except CatchableError as exc: + for j in 0 ..< i: + if not workers[j].finished: + workers[j].cancel() + return false + + # Wait for any worker to report progress, + # or for all workers to finish + proc cancelAll() = + for i in 0 ..< workers.len: + if not workers[i].finished: + workers[i].cancel() + + var anyDidProgress = false + proc handleFinishedWorker(future: pointer) = + try: + let didProgress = cast[Future[bool]](future).read() + if didProgress: + anyDidProgress = true + cancelAll() + except CatchableError as exc: + cancelAll() + + for i in 0 ..< workers.len: + workers[i].addCallback(handleFinishedWorker) + await allFutures(workers) + + return anyDidProgress + +template query[E]( + self: LightClientManager, + e: typedesc[E] +): Future[bool] = + self.query(e, Nothing()) + +# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#sync-via-libp2p +proc loop(self: LightClientManager) {.async.} = + var nextFetchTick = Moment.now() + while true: + # Obtain bootstrap data once a trusted block root is supplied + while not self.isLightClientStoreInitialized(): + let trustedBlockRoot = self.getTrustedBlockRoot() + if trustedBlockRoot.isNone: + await sleepAsync(chronos.seconds(2)) + continue + + let didProgress = await self.query( + LightClientBootstrapEndpoint, + trustedBlockRoot.get) + if not didProgress: + await sleepAsync(chronos.seconds(60)) + continue + + # Determine whether latest light client data can be applied + let + currentPeriod = self.getLocalWallPeriod() + finalizedPeriod = self.getFinalizedPeriod() + isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown() + isInSync = + if isNextSyncCommitteeKnown: + currentPeriod <= finalizedPeriod + 1 + else: + currentPeriod <= finalizedPeriod + + # If not in sync, request light client data for older periods + if not isInSync: + doAssert currentPeriod > finalizedPeriod + let didProgress = await self.query( + BestLightClientUpdatesByRangeEndpoint, + finalizedPeriod ..< currentPeriod) + if not didProgress: + await sleepAsync(chronos.seconds(60)) + continue + + if self.getFinalizedPeriod() >= (currentPeriod - 1): + # Fetch a single optimistic update to avoid waiting for gossip + discard await self.query(OptimisticLightClientUpdateEndpoint) + + nextFetchTick = Moment.now() + continue + + # Fetch a full update periodically to keep track of next sync committee + if Moment.now() >= nextFetchTick: + const SECONDS_PER_PERIOD = + SLOTS_PER_SYNC_COMMITTEE_PERIOD * SECONDS_PER_SLOT + let + didProgress = await self.query(LatestLightClientUpdateEndpoint) + delaySeconds = + if didProgress: + const + minDelaySeconds = SECONDS_PER_PERIOD div 8 + jitterSeconds = SECONDS_PER_PERIOD div 4 + (minDelaySeconds + self.rng[].rand(jitterSeconds).uint64).int64 + else: + const + minDelaySeconds = SECONDS_PER_PERIOD div 64 + jitterSeconds = SECONDS_PER_PERIOD div 32 + (minDelaySeconds + self.rng[].rand(jitterSeconds).uint64).int64 + nextFetchTick = Moment.fromNow(chronos.seconds(delaySeconds)) + + # Periodically wake and check for changes + await sleepAsync(chronos.seconds(2)) + +proc start*(self: var LightClientManager) = + ## Start light client manager's loop. + doAssert self.loopFuture == nil + self.loopFuture = self.loop() + +proc stop*(self: var LightClientManager) {.async.} = + ## Stop light client manager's loop. + if self.loopFuture != nil: + await self.loopFuture.cancelAndWait() + self.loopFuture = nil diff --git a/beacon_chain/sync/sync_protocol.nim b/beacon_chain/sync/sync_protocol.nim index baa881b4cd..2ada18b751 100644 --- a/beacon_chain/sync/sync_protocol.nim +++ b/beacon_chain/sync/sync_protocol.nim @@ -26,7 +26,7 @@ logScope: const MAX_REQUEST_BLOCKS = 1024 # https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#configuration - MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128 + MAX_REQUEST_LIGHT_CLIENT_UPDATES* = 128 blockByRootLookupCost = allowedOpsPerSecondCost(50) blockResponseCost = allowedOpsPerSecondCost(100) diff --git a/tests/test_light_client_processor.nim b/tests/test_light_client_processor.nim index 5b77489547..d9221ff7fd 100644 --- a/tests/test_light_client_processor.nim +++ b/tests/test_light_client_processor.nim @@ -83,14 +83,17 @@ suite "Light client processor" & preset(): func setTimeToSlot(slot: Slot) = time = chronos.seconds((slot * SECONDS_PER_SLOT).int64) + proc getTrustedBlockRoot(): Option[Eth2Digest] = + some trustedBlockRoot + var numDidInitializeStoreCalls = 0 proc didInitializeStore() = inc numDidInitializeStoreCalls let store = (ref Option[LightClientStore])() var processor = LightClientProcessor.new( - false, "", "", cfg, genesis_validators_root, trustedBlockRoot, - store, getBeaconTime, didInitializeStore) + false, "", "", cfg, genesis_validators_root, + store, getBeaconTime, getTrustedBlockRoot, didInitializeStore) res: Result[void, BlockError] test "Standard sync" & preset():