Skip to content

Commit

Permalink
extract LC sync task scheduling helper function (#5117)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
etan-status authored Jun 23, 2023
1 parent 1bc9f3a commit 8a853fc
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 59 deletions.
74 changes: 17 additions & 57 deletions beacon_chain/sync/light_client_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import chronos, chronicles
import
eth/p2p/discoveryv5/random2,
../spec/network,
../networking/eth2_network,
../beacon_clock,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -391,18 +354,21 @@ 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
let
current = wallTime.slotOrZero().sync_committee_period

syncTask = nextLightClientSyncTask(
current = current,
finalized = self.getFinalizedPeriod(),
optimistic = self.getOptimisticPeriod(),
current = current,
isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown())

didProgress =
Expand All @@ -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.
Expand Down
53 changes: 51 additions & 2 deletions beacon_chain/sync/light_client_sync_helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)

0 comments on commit 8a853fc

Please sign in to comment.