From 8a853fc4e4fc4178acf92a2de71422c49d0c3262 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 23 Jun 2023 08:19:51 +0200 Subject: [PATCH] extract LC sync task scheduling helper function (#5117) The helper function to compute delay until next light client sync task can be useful from more general purpose contexts. Move to helpers, and change it to return `Duration` instead of `BeaconTime` for flexibility. --- beacon_chain/sync/light_client_manager.nim | 74 +++++-------------- .../sync/light_client_sync_helpers.nim | 53 ++++++++++++- 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/beacon_chain/sync/light_client_manager.nim b/beacon_chain/sync/light_client_manager.nim index 82fd0cfd0..352887a5a 100644 --- a/beacon_chain/sync/light_client_manager.nim +++ b/beacon_chain/sync/light_client_manager.nim @@ -9,7 +9,6 @@ import chronos, chronicles import - eth/p2p/discoveryv5/random2, ../spec/network, ../networking/eth2_network, ../beacon_clock, @@ -107,13 +106,9 @@ proc isGossipSupported*( if not self.isLightClientStoreInitialized(): return false - let - finalizedPeriod = self.getFinalizedPeriod() - isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown() - if isNextSyncCommitteeKnown: - period <= finalizedPeriod + 1 - else: - period <= finalizedPeriod + period.isGossipSupported( + finalizedPeriod = self.getFinalizedPeriod(), + isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown()) # https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/altair/light-client/p2p-interface.md#getlightclientbootstrap proc doRequest( @@ -340,45 +335,13 @@ template query[E]( ): Future[bool] = self.query(e, Nothing()) -type SchedulingMode = enum - Now, - Soon, - CurrentPeriod, - NextPeriod - -func fetchTime( - self: LightClientManager, - wallTime: BeaconTime, - schedulingMode: SchedulingMode -): BeaconTime = - let - remainingTime = - case schedulingMode: - of Now: - return wallTime - of Soon: - chronos.seconds(0) - of CurrentPeriod: - let - wallPeriod = wallTime.slotOrZero().sync_committee_period - deadlineSlot = (wallPeriod + 1).start_slot - 1 - deadline = deadlineSlot.start_beacon_time() - chronos.nanoseconds((deadline - wallTime).nanoseconds) - of NextPeriod: - chronos.seconds( - (SLOTS_PER_SYNC_COMMITTEE_PERIOD * SECONDS_PER_SLOT).int64) - minDelay = max(remainingTime div 8, chronos.seconds(10)) - jitterSeconds = (minDelay * 2).seconds - jitterDelay = chronos.seconds(self.rng[].rand(jitterSeconds).int64) - return wallTime + minDelay + jitterDelay - # https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/altair/light-client/light-client.md#light-client-sync-process proc loop(self: LightClientManager) {.async.} = - var nextFetchTime = self.getBeaconTime() + var nextSyncTaskTime = self.getBeaconTime() while true: # Periodically wake and check for changes let wallTime = self.getBeaconTime() - if wallTime < nextFetchTime or + if wallTime < nextSyncTaskTime or self.network.peerPool.lenAvailable < 1: await sleepAsync(chronos.seconds(2)) continue @@ -391,8 +354,11 @@ proc loop(self: LightClientManager) {.async.} = continue let didProgress = await self.query(Bootstrap, trustedBlockRoot.get) - if not didProgress: - nextFetchTime = self.fetchTime(wallTime, Soon) + nextSyncTaskTime = + if didProgress: + wallTime + else: + wallTime + self.rng.computeDelayWithJitter(chronos.seconds(0)) continue # Fetch updates @@ -400,9 +366,9 @@ proc loop(self: LightClientManager) {.async.} = current = wallTime.slotOrZero().sync_committee_period syncTask = nextLightClientSyncTask( + current = current, finalized = self.getFinalizedPeriod(), optimistic = self.getOptimisticPeriod(), - current = current, isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown()) didProgress = @@ -415,18 +381,12 @@ proc loop(self: LightClientManager) {.async.} = of LcSyncKind.OptimisticUpdate: await self.query(OptimisticUpdate) - schedulingMode = - if not self.isGossipSupported(current): - if didProgress: - Now - else: - Soon - elif self.getFinalizedPeriod() != self.getOptimisticPeriod(): - CurrentPeriod - else: - NextPeriod - - nextFetchTime = self.fetchTime(wallTime, schedulingMode) + nextSyncTaskTime = wallTime + self.rng.nextLcSyncTaskDelay( + wallTime, + finalized = self.getFinalizedPeriod(), + optimistic = self.getOptimisticPeriod(), + isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown(), + didLatestSyncTaskProgress = didProgress) proc start*(self: var LightClientManager) = ## Start light client manager's loop. diff --git a/beacon_chain/sync/light_client_sync_helpers.nim b/beacon_chain/sync/light_client_sync_helpers.nim index 1d36efad3..8517a7703 100644 --- a/beacon_chain/sync/light_client_sync_helpers.nim +++ b/beacon_chain/sync/light_client_sync_helpers.nim @@ -11,7 +11,9 @@ import std/typetraits, chronos, stew/base10, - ../spec/[forks_light_client, network] + eth/p2p/discoveryv5/random2, + ../spec/[forks_light_client, network], + ../beacon_clock func checkLightClientUpdates*( updates: openArray[ForkedLightClientUpdate], @@ -49,6 +51,15 @@ func checkLightClientUpdates*( return err("Invalid context bytes") ok() +func isGossipSupported*( + period: SyncCommitteePeriod, + finalizedPeriod: SyncCommitteePeriod, + isNextSyncCommitteeKnown: bool): bool = + if isNextSyncCommitteeKnown: + period <= finalizedPeriod + 1 + else: + period <= finalizedPeriod + type LcSyncKind* {.pure.} = enum UpdatesByRange @@ -64,9 +75,9 @@ type discard func nextLightClientSyncTask*( + current: SyncCommitteePeriod, finalized: SyncCommitteePeriod, optimistic: SyncCommitteePeriod, - current: SyncCommitteePeriod, isNextSyncCommitteeKnown: bool): LcSyncTask = if finalized == optimistic and not isNextSyncCommitteeKnown: if finalized >= current: @@ -88,3 +99,41 @@ func nextLightClientSyncTask*( LcSyncTask(kind: LcSyncKind.FinalityUpdate) else: LcSyncTask(kind: LcSyncKind.OptimisticUpdate) + +func computeDelayWithJitter*( + rng: ref HmacDrbgContext, duration: Duration): Duration = + let + minDelay = max(duration div 8, chronos.seconds(10)) + jitterSeconds = (minDelay * 2).seconds + jitterDelay = chronos.seconds(rng[].rand(jitterSeconds).int64) + minDelay + jitterDelay + +func nextLcSyncTaskDelay*( + rng: ref HmacDrbgContext, + wallTime: BeaconTime, + finalized: SyncCommitteePeriod, + optimistic: SyncCommitteePeriod, + isNextSyncCommitteeKnown: bool, + didLatestSyncTaskProgress: bool +): Duration = + let + current = wallTime.slotOrZero().sync_committee_period + remainingDuration = + if not current.isGossipSupported(finalized, isNextSyncCommitteeKnown): + if didLatestSyncTaskProgress: + # Now + return chronos.seconds(0) + # Soon + chronos.seconds(0) + elif finalized != optimistic: + # Current sync committee period + let + wallPeriod = wallTime.slotOrZero().sync_committee_period + deadlineSlot = (wallPeriod + 1).start_slot - 1 + deadline = deadlineSlot.start_beacon_time() + chronos.nanoseconds((deadline - wallTime).nanoseconds) + else: + # Next sync committee period + chronos.seconds( + (SLOTS_PER_SYNC_COMMITTEE_PERIOD * SECONDS_PER_SLOT).int64) + rng.computeDelayWithJitter(remainingDuration)