encapsulate LC data variables into single structure (#3777)

Combines the LC data configuration options (serve / importMode), the
callbacks (finality / optimistic LC update) as well as the cache storing
light client data, into a new `LightClientDataStore` structure.
Also moves the structure into a light client specific file.
This commit is contained in:
Etan Kissling 2022-06-24 16:57:50 +02:00 committed by GitHub
parent c45c017349
commit 2e98c7722f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 187 additions and 129 deletions

View File

@ -185,12 +185,6 @@ type
cfg*: RuntimeConfig cfg*: RuntimeConfig
lightClientDataServe*: bool
## Whether to make local light client data available or not
lightClientDataImportMode*: LightClientDataImportMode
## Which classes of light client data to import
epochRefs*: array[32, EpochRef] epochRefs*: array[32, EpochRef]
## Cached information about a particular epoch ending with the given ## Cached information about a particular epoch ending with the given
## block - we limit the number of held EpochRefs to put a cap on ## block - we limit the number of held EpochRefs to put a cap on
@ -209,9 +203,10 @@ type
## called several times. ## called several times.
# ----------------------------------- # -----------------------------------
# Data to enable light clients to stay in sync with the network # Light client data
lightClientCache*: LightClientCache lcDataStore*: LightClientDataStore
# Data store to enable light clients to sync with the network
# ----------------------------------- # -----------------------------------
# Callbacks # Callbacks
@ -224,10 +219,6 @@ type
## On beacon chain reorganization ## On beacon chain reorganization
onFinHappened*: OnFinalizedCallback onFinHappened*: OnFinalizedCallback
## On finalization callback ## On finalization callback
onLightClientFinalityUpdate*: OnLightClientFinalityUpdateCallback
## On new `LightClientFinalityUpdate` callback
onLightClientOptimisticUpdate*: OnLightClientOptimisticUpdateCallback
## On new `LightClientOptimisticUpdate` callback
headSyncCommittees*: SyncCommitteeCache headSyncCommittees*: SyncCommitteeCache
## A cache of the sync committees, as they appear in the head state - ## A cache of the sync committees, as they appear in the head state -

View File

@ -18,11 +18,6 @@ import
./block_dag ./block_dag
type type
OnLightClientFinalityUpdateCallback* =
proc(data: altair.LightClientFinalityUpdate) {.gcsafe, raises: [Defect].}
OnLightClientOptimisticUpdateCallback* =
proc(data: altair.LightClientOptimisticUpdate) {.gcsafe, raises: [Defect].}
LightClientDataImportMode* {.pure.} = enum LightClientDataImportMode* {.pure.} = enum
## Controls which classes of light client data are imported. ## Controls which classes of light client data are imported.
None = "none" None = "none"
@ -34,6 +29,11 @@ type
OnDemand = "on-demand" OnDemand = "on-demand"
## Don't precompute historic data. Slow, may miss validator duties. ## Don't precompute historic data. Slow, may miss validator duties.
OnLightClientFinalityUpdateCallback* =
proc(data: altair.LightClientFinalityUpdate) {.gcsafe, raises: [Defect].}
OnLightClientOptimisticUpdateCallback* =
proc(data: altair.LightClientOptimisticUpdate) {.gcsafe, raises: [Defect].}
CachedLightClientData* = object CachedLightClientData* = object
## Cached data from historical non-finalized states to improve speed when ## Cached data from historical non-finalized states to improve speed when
## creating future `LightClientUpdate` and `LightClientBootstrap` instances. ## creating future `LightClientUpdate` and `LightClientBootstrap` instances.
@ -52,7 +52,7 @@ type
current_sync_committee_branch*: current_sync_committee_branch*:
array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest] array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
LightClientCache* = object LightClientDataCache* = object
data*: Table[BlockId, CachedLightClientData] data*: Table[BlockId, CachedLightClientData]
## Cached data for creating future `LightClientUpdate` instances. ## Cached data for creating future `LightClientUpdate` instances.
## Key is the block ID of which the post state was used to get the data. ## Key is the block ID of which the post state was used to get the data.
@ -79,3 +79,26 @@ type
importTailSlot*: Slot importTailSlot*: Slot
## The earliest slot for which light client data is imported. ## The earliest slot for which light client data is imported.
LightClientDataStore* = object
# -----------------------------------
# Light client data
cache*: LightClientDataCache
## Cached data to accelerate serving light client data
# -----------------------------------
# Config
serve*: bool
## Whether to make local light client data available or not
importMode*: LightClientDataImportMode
## Which classes of light client data to import
# -----------------------------------
# Callbacks
onLightClientFinalityUpdate*: OnLightClientFinalityUpdateCallback
## On new `LightClientFinalityUpdate` callback
onLightClientOptimisticUpdate*: OnLightClientOptimisticUpdateCallback
## On new `LightClientOptimisticUpdate` callback

View File

@ -692,7 +692,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
vanityLogs = default(VanityLogs)): ChainDAGRef = vanityLogs = default(VanityLogs)): ChainDAGRef =
cfg.checkForkConsistency() cfg.checkForkConsistency()
doAssert updateFlags in [{}, {verifyFinalization}], doAssert updateFlags - {verifyFinalization, enableTestFeatures} == {},
"Other flags not supported in ChainDAG" "Other flags not supported in ChainDAG"
# TODO we require that the db contains both a head and a tail block - # TODO we require that the db contains both a head and a tail block -
@ -725,15 +725,16 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
vanityLogs: vanityLogs, vanityLogs: vanityLogs,
lightClientDataServe: lightClientDataServe, lcDataStore: initLightClientDataStore(
lightClientDataImportMode: lightClientDataImportMode, serve = lightClientDataServe,
importMode = lightClientDataImportMode,
onLCFinalityUpdateCb = onLCFinalityUpdateCb,
onLCOptimisticUpdateCb = onLCOptimisticUpdateCb),
onBlockAdded: onBlockCb, onBlockAdded: onBlockCb,
onHeadChanged: onHeadCb, onHeadChanged: onHeadCb,
onReorgHappened: onReorgCb, onReorgHappened: onReorgCb,
onFinHappened: onFinCb, onFinHappened: onFinCb
onLightClientFinalityUpdate: onLCFinalityUpdateCb,
onLightClientOptimisticUpdate: onLCOptimisticUpdateCb
) )
loadTick = Moment.now() loadTick = Moment.now()
@ -953,7 +954,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
frontfillDur = frontfillTick - finalizedTick, frontfillDur = frontfillTick - finalizedTick,
keysDur = Moment.now() - frontfillTick keysDur = Moment.now() - frontfillTick
dag.initLightClientCache() dag.initLightClientDataCache()
dag dag

View File

@ -39,7 +39,7 @@ func computeEarliestLightClientSlot(dag: ChainDAGRef): Slot =
let let
minSupportedSlot = max( minSupportedSlot = max(
dag.cfg.ALTAIR_FORK_EPOCH.start_slot, dag.cfg.ALTAIR_FORK_EPOCH.start_slot,
dag.lightClientCache.importTailSlot) dag.lcDataStore.cache.importTailSlot)
currentSlot = getStateField(dag.headState, slot) currentSlot = getStateField(dag.headState, slot)
if currentSlot < minSupportedSlot: if currentSlot < minSupportedSlot:
return minSupportedSlot return minSupportedSlot
@ -150,7 +150,7 @@ proc getLightClientData(
bid: BlockId): CachedLightClientData = bid: BlockId): CachedLightClientData =
## Fetch cached light client data about a given block. ## Fetch cached light client data about a given block.
## Data must be cached (`cacheLightClientData`) before calling this function. ## Data must be cached (`cacheLightClientData`) before calling this function.
try: dag.lightClientCache.data[bid] try: dag.lcDataStore.cache.data[bid]
except KeyError: raiseAssert "Unreachable" except KeyError: raiseAssert "Unreachable"
proc cacheLightClientData( proc cacheLightClientData(
@ -170,24 +170,24 @@ proc cacheLightClientData(
state.data.build_proof( state.data.build_proof(
altair.FINALIZED_ROOT_INDEX, altair.FINALIZED_ROOT_INDEX,
cachedData.finality_branch) cachedData.finality_branch)
if dag.lightClientCache.data.hasKeyOrPut(bid, cachedData): if dag.lcDataStore.cache.data.hasKeyOrPut(bid, cachedData):
doAssert false, "Redundant `cacheLightClientData` call" doAssert false, "Redundant `cacheLightClientData` call"
proc deleteLightClientData*(dag: ChainDAGRef, bid: BlockId) = proc deleteLightClientData*(dag: ChainDAGRef, bid: BlockId) =
## Delete cached light client data for a given block. This needs to be called ## Delete cached light client data for a given block. This needs to be called
## when a block becomes unreachable due to finalization of a different fork. ## when a block becomes unreachable due to finalization of a different fork.
if dag.lightClientDataImportMode == LightClientDataImportMode.None: if dag.lcDataStore.importMode == LightClientDataImportMode.None:
return return
dag.lightClientCache.data.del bid dag.lcDataStore.cache.data.del bid
func handleUnexpectedLightClientError(dag: ChainDAGRef, buggedSlot: Slot) = func handleUnexpectedLightClientError(dag: ChainDAGRef, buggedSlot: Slot) =
## If there is an unexpected error, adjust `importTailSlot` to keep track of ## If there is an unexpected error, adjust `importTailSlot` to keep track of
## section for which complete light client data is available, and to avoid ## section for which complete light client data is available, and to avoid
## failed lookups of cached light client data. ## failed lookups of cached light client data.
doAssert verifyFinalization notin dag.updateFlags doAssert verifyFinalization notin dag.updateFlags
if buggedSlot >= dag.lightClientCache.importTailSlot: if buggedSlot >= dag.lcDataStore.cache.importTailSlot:
dag.lightClientCache.importTailSlot = buggedSlot + 1 dag.lcDataStore.cache.importTailSlot = buggedSlot + 1
template lazy_header(name: untyped): untyped {.dirty.} = template lazy_header(name: untyped): untyped {.dirty.} =
## `createLightClientUpdates` helper to lazily load a known block header. ## `createLightClientUpdates` helper to lazily load a known block header.
@ -262,7 +262,7 @@ proc createLightClientUpdates(
lazy_header(finalized_header) lazy_header(finalized_header)
# Update latest light client data # Update latest light client data
template latest(): auto = dag.lightClientCache.latest template latest(): auto = dag.lcDataStore.cache.latest
var var
newFinality = false newFinality = false
newOptimistic = false newOptimistic = false
@ -303,10 +303,10 @@ proc createLightClientUpdates(
let isCommitteeFinalized = dag.isNextSyncCommitteeFinalized(attested_period) let isCommitteeFinalized = dag.isNextSyncCommitteeFinalized(attested_period)
var best = var best =
if isCommitteeFinalized: if isCommitteeFinalized:
dag.lightClientCache.best.getOrDefault(attested_period) dag.lcDataStore.cache.best.getOrDefault(attested_period)
else: else:
let key = (attested_period, state.syncCommitteeRoot) let key = (attested_period, state.syncCommitteeRoot)
dag.lightClientCache.pendingBest.getOrDefault(key) dag.lcDataStore.cache.pendingBest.getOrDefault(key)
load_attested_data(attested_bid) load_attested_data(attested_bid)
let let
@ -339,19 +339,19 @@ proc createLightClientUpdates(
best.signature_slot = signature_slot best.signature_slot = signature_slot
if isCommitteeFinalized: if isCommitteeFinalized:
dag.lightClientCache.best[attested_period] = best dag.lcDataStore.cache.best[attested_period] = best
debug "Best LC update for period improved", debug "Best LC update for period improved",
period = attested_period, update = best period = attested_period, update = best
else: else:
let key = (attested_period, state.syncCommitteeRoot) let key = (attested_period, state.syncCommitteeRoot)
dag.lightClientCache.pendingBest[key] = best dag.lcDataStore.cache.pendingBest[key] = best
debug "Best LC update for period improved", debug "Best LC update for period improved",
period = key, update = best period = key, update = best
if newFinality and dag.onLightClientFinalityUpdate != nil: if newFinality and dag.lcDataStore.onLightClientFinalityUpdate != nil:
dag.onLightClientFinalityUpdate(latest) dag.lcDataStore.onLightClientFinalityUpdate(latest)
if newOptimistic and dag.onLightClientOptimisticUpdate != nil: if newOptimistic and dag.lcDataStore.onLightClientOptimisticUpdate != nil:
dag.onLightClientOptimisticUpdate(latest.toOptimistic) dag.lcDataStore.onLightClientOptimisticUpdate(latest.toOptimistic)
proc processNewBlockForLightClient*( proc processNewBlockForLightClient*(
dag: ChainDAGRef, dag: ChainDAGRef,
@ -359,7 +359,7 @@ proc processNewBlockForLightClient*(
signedBlock: ForkyTrustedSignedBeaconBlock, signedBlock: ForkyTrustedSignedBeaconBlock,
parentBid: BlockId) = parentBid: BlockId) =
## Update light client data with information from a new block. ## Update light client data with information from a new block.
if dag.lightClientDataImportMode == LightClientDataImportMode.None: if dag.lcDataStore.importMode == LightClientDataImportMode.None:
return return
if signedBlock.message.slot < dag.computeEarliestLightClientSlot: if signedBlock.message.slot < dag.computeEarliestLightClientSlot:
return return
@ -378,7 +378,7 @@ proc processNewBlockForLightClient*(
proc processHeadChangeForLightClient*(dag: ChainDAGRef) = proc processHeadChangeForLightClient*(dag: ChainDAGRef) =
## Update light client data to account for a new head block. ## Update light client data to account for a new head block.
## Note that `dag.finalizedHead` is not yet updated when this is called. ## Note that `dag.finalizedHead` is not yet updated when this is called.
if dag.lightClientDataImportMode == LightClientDataImportMode.None: if dag.lcDataStore.importMode == LightClientDataImportMode.None:
return return
let earliestSlot = dag.computeEarliestLightClientSlot let earliestSlot = dag.computeEarliestLightClientSlot
if dag.head.slot < earliestSlot: if dag.head.slot < earliestSlot:
@ -400,13 +400,13 @@ proc processHeadChangeForLightClient*(dag: ChainDAGRef) =
dag.handleUnexpectedLightClientError(period.start_slot) dag.handleUnexpectedLightClientError(period.start_slot)
continue continue
key = (period, syncCommitteeRoot) key = (period, syncCommitteeRoot)
dag.lightClientCache.best[period] = dag.lcDataStore.cache.best[period] =
dag.lightClientCache.pendingBest.getOrDefault(key) dag.lcDataStore.cache.pendingBest.getOrDefault(key)
withState(dag.headState): # Common case separate to avoid `tmpState` copy withState(dag.headState): # Common case separate to avoid `tmpState` copy
when stateFork >= BeaconStateFork.Altair: when stateFork >= BeaconStateFork.Altair:
let key = (headPeriod, state.syncCommitteeRoot) let key = (headPeriod, state.syncCommitteeRoot)
dag.lightClientCache.best[headPeriod] = dag.lcDataStore.cache.best[headPeriod] =
dag.lightClientCache.pendingBest.getOrDefault(key) dag.lcDataStore.cache.pendingBest.getOrDefault(key)
else: raiseAssert "Unreachable" else: raiseAssert "Unreachable"
proc processFinalizationForLightClient*( proc processFinalizationForLightClient*(
@ -414,7 +414,7 @@ proc processFinalizationForLightClient*(
## Prune cached data that is no longer useful for creating future ## Prune cached data that is no longer useful for creating future
## `LightClientUpdate` and `LightClientBootstrap` instances. ## `LightClientUpdate` and `LightClientBootstrap` instances.
## This needs to be called whenever `finalized_checkpoint` changes. ## This needs to be called whenever `finalized_checkpoint` changes.
if dag.lightClientDataImportMode == LightClientDataImportMode.None: if dag.lcDataStore.importMode == LightClientDataImportMode.None:
return return
let let
earliestSlot = dag.computeEarliestLightClientSlot earliestSlot = dag.computeEarliestLightClientSlot
@ -432,7 +432,7 @@ proc processFinalizationForLightClient*(
break break
bid = bsi.bid bid = bsi.bid
if bid.slot >= lowSlot: if bid.slot >= lowSlot:
dag.lightClientCache.bootstrap[bid.slot] = dag.lcDataStore.cache.bootstrap[bid.slot] =
CachedLightClientBootstrap( CachedLightClientBootstrap(
current_sync_committee_branch: current_sync_committee_branch:
dag.getLightClientData(bid).current_sync_committee_branch) dag.getLightClientData(bid).current_sync_committee_branch)
@ -443,39 +443,51 @@ proc processFinalizationForLightClient*(
# Prune light client data that is no longer referrable by future updates # Prune light client data that is no longer referrable by future updates
var bidsToDelete: seq[BlockId] var bidsToDelete: seq[BlockId]
for bid, data in dag.lightClientCache.data: for bid, data in dag.lcDataStore.cache.data:
if bid.slot >= dag.finalizedHead.blck.slot: if bid.slot >= dag.finalizedHead.blck.slot:
continue continue
bidsToDelete.add bid bidsToDelete.add bid
for bid in bidsToDelete: for bid in bidsToDelete:
dag.lightClientCache.data.del bid dag.lcDataStore.cache.data.del bid
# Prune bootstrap data that is no longer relevant # Prune bootstrap data that is no longer relevant
var slotsToDelete: seq[Slot] var slotsToDelete: seq[Slot]
for slot in dag.lightClientCache.bootstrap.keys: for slot in dag.lcDataStore.cache.bootstrap.keys:
if slot < earliestSlot: if slot < earliestSlot:
slotsToDelete.add slot slotsToDelete.add slot
for slot in slotsToDelete: for slot in slotsToDelete:
dag.lightClientCache.bootstrap.del slot dag.lcDataStore.cache.bootstrap.del slot
# Prune best `LightClientUpdate` that are no longer relevant # Prune best `LightClientUpdate` that are no longer relevant
let earliestPeriod = earliestSlot.sync_committee_period let earliestPeriod = earliestSlot.sync_committee_period
var periodsToDelete: seq[SyncCommitteePeriod] var periodsToDelete: seq[SyncCommitteePeriod]
for period in dag.lightClientCache.best.keys: for period in dag.lcDataStore.cache.best.keys:
if period < earliestPeriod: if period < earliestPeriod:
periodsToDelete.add period periodsToDelete.add period
for period in periodsToDelete: for period in periodsToDelete:
dag.lightClientCache.best.del period dag.lcDataStore.cache.best.del period
# Prune best `LightClientUpdate` referring to non-finalized sync committees # Prune best `LightClientUpdate` referring to non-finalized sync committees
# that are no longer relevant, i.e., orphaned or too old # that are no longer relevant, i.e., orphaned or too old
let firstNonFinalizedPeriod = dag.firstNonFinalizedPeriod let firstNonFinalizedPeriod = dag.firstNonFinalizedPeriod
var keysToDelete: seq[(SyncCommitteePeriod, Eth2Digest)] var keysToDelete: seq[(SyncCommitteePeriod, Eth2Digest)]
for (period, committeeRoot) in dag.lightClientCache.pendingBest.keys: for (period, committeeRoot) in dag.lcDataStore.cache.pendingBest.keys:
if period < firstNonFinalizedPeriod: if period < firstNonFinalizedPeriod:
keysToDelete.add (period, committeeRoot) keysToDelete.add (period, committeeRoot)
for key in keysToDelete: for key in keysToDelete:
dag.lightClientCache.pendingBest.del key dag.lcDataStore.cache.pendingBest.del key
func initLightClientDataStore*(
serve: bool, importMode: LightClientDataImportMode,
onLCFinalityUpdateCb: OnLightClientFinalityUpdateCallback = nil,
onLCOptimisticUpdateCb: OnLightClientOptimisticUpdateCallback = nil
): LightClientDataStore =
## Initialize light client data collector.
LightClientDataStore(
serve: serve,
importMode: importMode,
onLightClientFinalityUpdate: onLCFinalityUpdateCb,
onLightClientOptimisticUpdate: onLCOptimisticUpdateCb)
proc initLightClientBootstrapForPeriod( proc initLightClientBootstrapForPeriod(
dag: ChainDAGRef, dag: ChainDAGRef,
@ -516,7 +528,7 @@ proc initLightClientBootstrapForPeriod(
bid = bsi.bid bid = bsi.bid
boundarySlot = bid.slot.nextEpochBoundarySlot boundarySlot = bid.slot.nextEpochBoundarySlot
if boundarySlot == nextBoundarySlot and bid.slot >= lowSlot and if boundarySlot == nextBoundarySlot and bid.slot >= lowSlot and
not dag.lightClientCache.bootstrap.hasKey(bid.slot): not dag.lcDataStore.cache.bootstrap.hasKey(bid.slot):
var cachedBootstrap {.noinit.}: CachedLightClientBootstrap var cachedBootstrap {.noinit.}: CachedLightClientBootstrap
if not dag.updateExistingState( if not dag.updateExistingState(
tmpState[], bid.atSlot, save = false, tmpCache): tmpState[], bid.atSlot, save = false, tmpCache):
@ -528,7 +540,7 @@ proc initLightClientBootstrapForPeriod(
altair.CURRENT_SYNC_COMMITTEE_INDEX, altair.CURRENT_SYNC_COMMITTEE_INDEX,
cachedBootstrap.current_sync_committee_branch) cachedBootstrap.current_sync_committee_branch)
else: raiseAssert "Unreachable" else: raiseAssert "Unreachable"
dag.lightClientCache.bootstrap[bid.slot] = cachedBootstrap dag.lcDataStore.cache.bootstrap[bid.slot] = cachedBootstrap
proc initLightClientUpdateForPeriod( proc initLightClientUpdateForPeriod(
dag: ChainDAGRef, period: SyncCommitteePeriod) = dag: ChainDAGRef, period: SyncCommitteePeriod) =
@ -543,7 +555,7 @@ proc initLightClientUpdateForPeriod(
periodEndSlot = periodStartSlot + SLOTS_PER_SYNC_COMMITTEE_PERIOD - 1 periodEndSlot = periodStartSlot + SLOTS_PER_SYNC_COMMITTEE_PERIOD - 1
if periodEndSlot < earliestSlot: if periodEndSlot < earliestSlot:
return return
if dag.lightClientCache.best.hasKey(period): if dag.lcDataStore.cache.best.hasKey(period):
return return
let startTick = Moment.now() let startTick = Moment.now()
@ -553,7 +565,7 @@ proc initLightClientUpdateForPeriod(
# replicated on every `return`, and the log statement allocates another # replicated on every `return`, and the log statement allocates another
# copy of the arguments on the stack for each instantiation (~1 MB stack!) # copy of the arguments on the stack for each instantiation (~1 MB stack!)
debug "Best LC update for period computed", debug "Best LC update for period computed",
period, update = dag.lightClientCache.best.getOrDefault(period), period, update = dag.lcDataStore.cache.best.getOrDefault(period),
computeDur = endTick - startTick computeDur = endTick - startTick
defer: logBest() defer: logBest()
@ -605,7 +617,7 @@ proc initLightClientUpdateForPeriod(
maxParticipantsRes = dag.maxParticipantsBlock(highBid, lowSlot) maxParticipantsRes = dag.maxParticipantsBlock(highBid, lowSlot)
maxParticipantsBid = maxParticipantsRes.bid.valueOr: maxParticipantsBid = maxParticipantsRes.bid.valueOr:
if maxParticipantsRes.ok: # No single valid block exists in the period if maxParticipantsRes.ok: # No single valid block exists in the period
dag.lightClientCache.best[period] = default(altair.LightClientUpdate) dag.lcDataStore.cache.best[period] = default(altair.LightClientUpdate)
return return
# The block with highest participation may refer to a `finalized_checkpoint` # The block with highest participation may refer to a `finalized_checkpoint`
@ -696,15 +708,15 @@ proc initLightClientUpdateForPeriod(
update.sync_aggregate = blck.asSigned().message.body.sync_aggregate update.sync_aggregate = blck.asSigned().message.body.sync_aggregate
else: raiseAssert "Unreachable" else: raiseAssert "Unreachable"
update.signature_slot = signatureBid.slot update.signature_slot = signatureBid.slot
dag.lightClientCache.best[period] = update dag.lcDataStore.cache.best[period] = update
proc initLightClientCache*(dag: ChainDAGRef) = proc initLightClientDataCache*(dag: ChainDAGRef) =
## Initialize cached light client data ## Initialize cached light client data
if dag.lightClientDataImportMode == LightClientDataImportMode.None: if dag.lcDataStore.importMode == LightClientDataImportMode.None:
return return
dag.lightClientCache.importTailSlot = dag.tail.slot dag.lcDataStore.cache.importTailSlot = dag.tail.slot
if dag.lightClientDataImportMode == LightClientDataImportMode.OnlyNew: if dag.lcDataStore.importMode == LightClientDataImportMode.OnlyNew:
dag.lightClientCache.importTailSlot = dag.head.slot dag.lcDataStore.cache.importTailSlot = dag.head.slot
var earliestSlot = dag.computeEarliestLightClientSlot var earliestSlot = dag.computeEarliestLightClientSlot
if dag.head.slot < earliestSlot: if dag.head.slot < earliestSlot:
return return
@ -726,7 +738,7 @@ proc initLightClientCache*(dag: ChainDAGRef) =
# be updated incrementally, because those blocks / states are passed in # be updated incrementally, because those blocks / states are passed in
# directly. It is only historical blocks (or sync committees) that depend # directly. It is only historical blocks (or sync committees) that depend
# on a potentially corrupted database. # on a potentially corrupted database.
doAssert buggedBid.slot > dag.lightClientCache.importTailSlot doAssert buggedBid.slot > dag.lcDataStore.cache.importTailSlot
dag.handleUnexpectedLightClientError(buggedBid.slot) dag.handleUnexpectedLightClientError(buggedBid.slot)
earliestSlot = dag.computeEarliestLightClientSlot earliestSlot = dag.computeEarliestLightClientSlot
@ -775,7 +787,7 @@ proc initLightClientCache*(dag: ChainDAGRef) =
initDur = lightClientEndTick - lightClientStartTick initDur = lightClientEndTick - lightClientStartTick
# Import historic data # Import historic data
if dag.lightClientDataImportMode == LightClientDataImportMode.Full: if dag.lcDataStore.importMode == LightClientDataImportMode.Full:
let earliestPeriod = earliestSlot.sync_committee_period let earliestPeriod = earliestSlot.sync_committee_period
for period in earliestPeriod ..< finalizedPeriod: for period in earliestPeriod ..< finalizedPeriod:
dag.initLightClientBootstrapForPeriod(period) dag.initLightClientBootstrapForPeriod(period)
@ -784,7 +796,7 @@ proc initLightClientCache*(dag: ChainDAGRef) =
proc getLightClientBootstrap*( proc getLightClientBootstrap*(
dag: ChainDAGRef, dag: ChainDAGRef,
blockRoot: Eth2Digest): Opt[altair.LightClientBootstrap] = blockRoot: Eth2Digest): Opt[altair.LightClientBootstrap] =
if not dag.lightClientDataServe: if not dag.lcDataStore.serve:
return err() return err()
let bdata = dag.getForkedBlock(blockRoot).valueOr: let bdata = dag.getForkedBlock(blockRoot).valueOr:
@ -801,9 +813,9 @@ proc getLightClientBootstrap*(
if slot > dag.finalizedHead.blck.slot: if slot > dag.finalizedHead.blck.slot:
debug "LC bootstrap unavailable: Not finalized", blockRoot debug "LC bootstrap unavailable: Not finalized", blockRoot
return err() return err()
var cachedBootstrap = dag.lightClientCache.bootstrap.getOrDefault(slot) var cachedBootstrap = dag.lcDataStore.cache.bootstrap.getOrDefault(slot)
if cachedBootstrap.current_sync_committee_branch.isZeroMemory: if cachedBootstrap.current_sync_committee_branch.isZeroMemory:
if dag.lightClientDataImportMode == LightClientDataImportMode.OnDemand: if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
let bsi = ? dag.getExistingBlockIdAtSlot(slot) let bsi = ? dag.getExistingBlockIdAtSlot(slot)
var tmpState = assignClone(dag.headState) var tmpState = assignClone(dag.headState)
dag.withUpdatedExistingState(tmpState[], bsi) do: dag.withUpdatedExistingState(tmpState[], bsi) do:
@ -814,7 +826,7 @@ proc getLightClientBootstrap*(
cachedBootstrap.current_sync_committee_branch) cachedBootstrap.current_sync_committee_branch)
else: raiseAssert "Unreachable" else: raiseAssert "Unreachable"
do: return err() do: return err()
dag.lightClientCache.bootstrap[slot] = cachedBootstrap dag.lcDataStore.cache.bootstrap[slot] = cachedBootstrap
else: else:
debug "LC bootstrap unavailable: Data not cached", slot debug "LC bootstrap unavailable: Data not cached", slot
return err() return err()
@ -836,32 +848,32 @@ proc getLightClientBootstrap*(
proc getLightClientUpdateForPeriod*( proc getLightClientUpdateForPeriod*(
dag: ChainDAGRef, dag: ChainDAGRef,
period: SyncCommitteePeriod): Option[altair.LightClientUpdate] = period: SyncCommitteePeriod): Option[altair.LightClientUpdate] =
if not dag.lightClientDataServe: if not dag.lcDataStore.serve:
return return
if dag.lightClientDataImportMode == LightClientDataImportMode.OnDemand: if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
dag.initLightClientUpdateForPeriod(period) dag.initLightClientUpdateForPeriod(period)
result = some(dag.lightClientCache.best.getOrDefault(period)) result = some(dag.lcDataStore.cache.best.getOrDefault(period))
let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits) let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits)
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS: if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset() result.reset()
proc getLightClientFinalityUpdate*( proc getLightClientFinalityUpdate*(
dag: ChainDAGRef): Option[altair.LightClientFinalityUpdate] = dag: ChainDAGRef): Option[altair.LightClientFinalityUpdate] =
if not dag.lightClientDataServe: if not dag.lcDataStore.serve:
return return
result = some(dag.lightClientCache.latest) result = some(dag.lcDataStore.cache.latest)
let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits) let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits)
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS: if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset() result.reset()
proc getLightClientOptimisticUpdate*( proc getLightClientOptimisticUpdate*(
dag: ChainDAGRef): Option[altair.LightClientOptimisticUpdate] = dag: ChainDAGRef): Option[altair.LightClientOptimisticUpdate] =
if not dag.lightClientDataServe: if not dag.lcDataStore.serve:
return return
result = some(dag.lightClientCache.latest.toOptimistic) result = some(dag.lcDataStore.cache.latest.toOptimistic)
let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits) let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits)
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS: if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset() result.reset()

View File

@ -1,5 +1,5 @@
# beacon_chain # beacon_chain
# Copyright (c) 2018-2021 Status Research & Development GmbH # Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -34,5 +34,7 @@ type
## When process_slots() is being called as part of a state_transition(), ## When process_slots() is being called as part of a state_transition(),
## the hash_tree_root() from the block will fill in the state.root so it ## the hash_tree_root() from the block will fill in the state.root so it
## should skip calculating that last state root. ## should skip calculating that last state root.
enableTestFeatures ##\
## Whether to enable extra features for testing.
UpdateFlags* = set[UpdateFlag] UpdateFlags* = set[UpdateFlag]

View File

@ -1057,7 +1057,7 @@ proc validateLightClientFinalityUpdate*(
# `signature_slot` was given enough time to propagate through the network. # `signature_slot` was given enough time to propagate through the network.
return errIgnore("LightClientFinalityUpdate: received too early") return errIgnore("LightClientFinalityUpdate: received too early")
if finality_update != dag.lightClientCache.latest: if finality_update != dag.lcDataStore.cache.latest:
# [IGNORE] The received `finality_update` matches the locally computed one # [IGNORE] The received `finality_update` matches the locally computed one
# exactly. # exactly.
return errIgnore("LightClientFinalityUpdate: not matching local") return errIgnore("LightClientFinalityUpdate: not matching local")
@ -1085,7 +1085,7 @@ proc validateLightClientOptimisticUpdate*(
# `signature_slot` was given enough time to propagate through the network. # `signature_slot` was given enough time to propagate through the network.
return errIgnore("LightClientOptimisticUpdate: received too early") return errIgnore("LightClientOptimisticUpdate: received too early")
if not optimistic_update.matches(dag.lightClientCache.latest): if not optimistic_update.matches(dag.lcDataStore.cache.latest):
# [IGNORE] The received `optimistic_update` matches the locally computed one # [IGNORE] The received `optimistic_update` matches the locally computed one
# exactly. # exactly.
return errIgnore("LightClientOptimisticUpdate: not matching local") return errIgnore("LightClientOptimisticUpdate: not matching local")

View File

@ -87,6 +87,56 @@ type
else: else:
incompatibilityDesc*: string incompatibilityDesc*: string
type DeploymentPhase* {.pure.} = enum
None,
Devnet,
Testnet,
Mainnet
func deploymentPhase*(genesisData: string): DeploymentPhase =
# SSZ processing at compile time does not work well.
#
# `BeaconState` layout:
# ```
# - genesis_time: uint64
# - genesis_validators_root: Eth2Digest
# - ...
# ```
#
# Comparing the first 40 bytes covers those two fields,
# which should identify the network with high likelihood.
# ''.join('%02X'%b for b in open("network_name/genesis.ssz", "rb").read()[:40])
if genesisData.len < 40:
return DeploymentPhase.None
const
mainnets = [
# Mainnet
"5730C65F000000004B363DB94E286120D76EB905340FDD4E54BFE9F06BF33FF6CF5AD27F511BFE95",
]
testnets = [
# Kiln
"0C572B620000000099B09FCD43E5905236C370F184056BEC6E6638CFC31A323B304FC4AA789CB4AD",
# Ropsten
"F0DB94620000000044F1E56283CA88B35C789F7F449E52339BC1FEFE3A45913A43A6D16EDCD33CF1",
# Prater
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB",
# Sepolia
"607DB06200000000D8EA171F3C94AEA21EBC42A1ED61052ACF3F9209C00E4EFBAADDAC09ED9B8078",
]
devnets = [
"placeholder",
]
let data = (genesisData[0 ..< 40].toHex())
if data in mainnets:
return DeploymentPhase.Mainnet
if data in testnets:
return DeploymentPhase.Testnet
if data in devnets:
return DeploymentPhase.Devnet
DeploymentPhase.None
const const
eth2NetworksDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor/eth2-networks" eth2NetworksDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor/eth2-networks"
mergeTestnetsDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor/merge-testnets" mergeTestnetsDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor/merge-testnets"
@ -166,42 +216,16 @@ proc loadEth2NetworkMetadata*(path: string, eth1Network = none(Eth1Network)): Et
else: else:
"" ""
shouldSupportLightClient = deploymentPhase = genesisData.deploymentPhase
if genesisData.len >= 40:
# SSZ processing at compile time does not work well.
#
# `BeaconState` layout:
# ```
# - genesis_time: uint64
# - genesis_validators_root: Eth2Digest
# - ...
# ```
#
# Comparing the first 40 bytes covers those two fields,
# which should identify the network with high likelihood.
# ''.join('%02X'%b for b in open("network_name/genesis.ssz", "rb").read()[:40])
let data = (genesisData[0 ..< 40].toHex())
data in [
# Kiln
"0C572B620000000099B09FCD43E5905236C370F184056BEC6E6638CFC31A323B304FC4AA789CB4AD",
# Ropsten
"F0DB94620000000044F1E56283CA88B35C789F7F449E52339BC1FEFE3A45913A43A6D16EDCD33CF1",
# Prater
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB",
# Sepolia
"607DB06200000000D8EA171F3C94AEA21EBC42A1ED61052ACF3F9209C00E4EFBAADDAC09ED9B8078",
]
else:
false
configDefaults = configDefaults =
Eth2NetworkConfigDefaults( Eth2NetworkConfigDefaults(
lightClientEnable: lightClientEnable:
false, # Only produces debug logs so far false, # Only produces debug logs so far
lightClientDataServe: lightClientDataServe:
shouldSupportLightClient, deploymentPhase <= DeploymentPhase.Testnet,
lightClientDataImportMode: lightClientDataImportMode:
if shouldSupportLightClient: if deploymentPhase <= DeploymentPhase.Testnet:
LightClientDataImportMode.OnlyNew LightClientDataImportMode.OnlyNew
else: else:
LightClientDataImportMode.None LightClientDataImportMode.None

View File

@ -151,7 +151,8 @@ proc loadChainDag(
db: BeaconChainDB, db: BeaconChainDB,
eventBus: EventBus, eventBus: EventBus,
validatorMonitor: ref ValidatorMonitor, validatorMonitor: ref ValidatorMonitor,
networkGenesisValidatorsRoot: Option[Eth2Digest]): ChainDAGRef = networkGenesisValidatorsRoot: Option[Eth2Digest],
shouldEnableTestFeatures: bool): ChainDAGRef =
var dag: ChainDAGRef var dag: ChainDAGRef
info "Loading block DAG from database", path = config.databaseDir info "Loading block DAG from database", path = config.databaseDir
@ -173,6 +174,9 @@ proc loadChainDag(
eventBus.optUpdateQueue.emit(data) eventBus.optUpdateQueue.emit(data)
let let
extraFlags =
if shouldEnableTestFeatures: {enableTestFeatures}
else: {}
chainDagFlags = chainDagFlags =
if config.verifyFinalization: {verifyFinalization} if config.verifyFinalization: {verifyFinalization}
else: {} else: {}
@ -184,7 +188,7 @@ proc loadChainDag(
else: nil else: nil
dag = ChainDAGRef.init( dag = ChainDAGRef.init(
cfg, db, validatorMonitor, chainDagFlags, config.eraDir, cfg, db, validatorMonitor, extraFlags + chainDagFlags, config.eraDir,
onBlockAdded, onHeadChanged, onChainReorg, onBlockAdded, onHeadChanged, onChainReorg,
onLCFinalityUpdateCb = onLightClientFinalityUpdateCb, onLCFinalityUpdateCb = onLightClientFinalityUpdateCb,
onLCOptimisticUpdateCb = onLightClientOptimisticUpdateCb, onLCOptimisticUpdateCb = onLightClientOptimisticUpdateCb,
@ -580,7 +584,8 @@ proc init*(T: type BeaconNode,
none(Eth2Digest) none(Eth2Digest)
dag = loadChainDag( dag = loadChainDag(
config, cfg, db, eventBus, config, cfg, db, eventBus,
validatorMonitor, networkGenesisValidatorsRoot) validatorMonitor, networkGenesisValidatorsRoot,
genesisStateContents.deploymentPhase <= DeploymentPhase.Devnet)
genesisTime = getStateField(dag.headState, genesis_time) genesisTime = getStateField(dag.headState, genesis_time)
beaconClock = BeaconClock.init(genesisTime) beaconClock = BeaconClock.init(genesisTime)
getBeaconTime = beaconClock.getBeaconTimeFn() getBeaconTime = beaconClock.getBeaconTimeFn()
@ -1329,7 +1334,7 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
restServer.router.installNimbusApiHandlers(node) restServer.router.installNimbusApiHandlers(node)
restServer.router.installNodeApiHandlers(node) restServer.router.installNodeApiHandlers(node)
restServer.router.installValidatorApiHandlers(node) restServer.router.installValidatorApiHandlers(node)
if node.dag.lightClientDataServe: if node.dag.lcDataStore.serve:
restServer.router.installLightClientApiHandlers(node) restServer.router.installLightClientApiHandlers(node)
proc installMessageValidators(node: BeaconNode) = proc installMessageValidators(node: BeaconNode) =

View File

@ -92,7 +92,7 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, "Invalid topics value", return RestApiResponse.jsonError(Http400, "Invalid topics value",
$topics.error()) $topics.error())
let res = validateEventTopics(topics.get(), let res = validateEventTopics(topics.get(),
node.dag.lightClientDataServe) node.dag.lcDataStore.serve)
if res.isErr(): if res.isErr():
return RestApiResponse.jsonError(Http400, "Invalid topics value", return RestApiResponse.jsonError(Http400, "Invalid topics value",
$res.error()) $res.error())
@ -145,12 +145,12 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
"contribution_and_proof") "contribution_and_proof")
res.add(handler) res.add(handler)
if EventTopic.LightClientFinalityUpdate in eventTopics: if EventTopic.LightClientFinalityUpdate in eventTopics:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let handler = response.eventHandler(node.eventBus.finUpdateQueue, let handler = response.eventHandler(node.eventBus.finUpdateQueue,
"light_client_finality_update_v0") "light_client_finality_update_v0")
res.add(handler) res.add(handler)
if EventTopic.LightClientOptimisticUpdate in eventTopics: if EventTopic.LightClientOptimisticUpdate in eventTopics:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let handler = response.eventHandler(node.eventBus.optUpdateQueue, let handler = response.eventHandler(node.eventBus.optUpdateQueue,
"light_client_optimistic_update_v0") "light_client_optimistic_update_v0")
res.add(handler) res.add(handler)

View File

@ -18,7 +18,7 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet, router.api(MethodGet,
"/eth/v0/beacon/light_client/bootstrap/{block_root}") do ( "/eth/v0/beacon/light_client/bootstrap/{block_root}") do (
block_root: Eth2Digest) -> RestApiResponse: block_root: Eth2Digest) -> RestApiResponse:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let vroot = block: let vroot = block:
if block_root.isErr(): if block_root.isErr():
return RestApiResponse.jsonError(Http400, InvalidBlockRootValueError, return RestApiResponse.jsonError(Http400, InvalidBlockRootValueError,
@ -36,7 +36,7 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v0/beacon/light_client/updates") do ( "/eth/v0/beacon/light_client/updates") do (
start_period: Option[SyncCommitteePeriod], count: Option[uint64] start_period: Option[SyncCommitteePeriod], count: Option[uint64]
) -> RestApiResponse: ) -> RestApiResponse:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let vstart = block: let vstart = block:
if start_period.isNone(): if start_period.isNone():
return RestApiResponse.jsonError(Http400, MissingStartPeriodValueError) return RestApiResponse.jsonError(Http400, MissingStartPeriodValueError)
@ -75,7 +75,7 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet, router.api(MethodGet,
"/eth/v0/beacon/light_client/finality_update") do ( "/eth/v0/beacon/light_client/finality_update") do (
) -> RestApiResponse: ) -> RestApiResponse:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let finality_update = node.dag.getLightClientFinalityUpdate() let finality_update = node.dag.getLightClientFinalityUpdate()
if finality_update.isSome: if finality_update.isSome:
return RestApiResponse.jsonResponse(finality_update) return RestApiResponse.jsonResponse(finality_update)
@ -86,7 +86,7 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet, router.api(MethodGet,
"/eth/v0/beacon/light_client/optimistic_update") do ( "/eth/v0/beacon/light_client/optimistic_update") do (
) -> RestApiResponse: ) -> RestApiResponse:
doAssert node.dag.lightClientDataServe doAssert node.dag.lcDataStore.serve
let optimistic_update = node.dag.getLightClientOptimisticUpdate() let optimistic_update = node.dag.getLightClientOptimisticUpdate()
if optimistic_update.isSome: if optimistic_update.isSome:
return RestApiResponse.jsonResponse(optimistic_update) return RestApiResponse.jsonResponse(optimistic_update)

View File

@ -536,7 +536,7 @@ p2pProtocol BeaconSync(version = 1,
isLightClientRequest = true).} = isLightClientRequest = true).} =
trace "Received LC bootstrap request", peer, blockRoot trace "Received LC bootstrap request", peer, blockRoot
let dag = peer.networkState.dag let dag = peer.networkState.dag
doAssert dag.lightClientDataServe doAssert dag.lcDataStore.serve
peer.updateRequestQuota(lightClientBootstrapLookupCost) peer.updateRequestQuota(lightClientBootstrapLookupCost)
peer.awaitNonNegativeRequestQuota() peer.awaitNonNegativeRequestQuota()
@ -565,7 +565,7 @@ p2pProtocol BeaconSync(version = 1,
isLightClientRequest = true).} = isLightClientRequest = true).} =
trace "Received LC updates by range request", peer, startPeriod, reqCount trace "Received LC updates by range request", peer, startPeriod, reqCount
let dag = peer.networkState.dag let dag = peer.networkState.dag
doAssert dag.lightClientDataServe doAssert dag.lcDataStore.serve
let let
headPeriod = dag.head.slot.sync_committee_period headPeriod = dag.head.slot.sync_committee_period
@ -605,7 +605,7 @@ p2pProtocol BeaconSync(version = 1,
isLightClientRequest = true).} = isLightClientRequest = true).} =
trace "Received LC finality update request", peer trace "Received LC finality update request", peer
let dag = peer.networkState.dag let dag = peer.networkState.dag
doAssert dag.lightClientDataServe doAssert dag.lcDataStore.serve
peer.awaitNonNegativeRequestQuota() peer.awaitNonNegativeRequestQuota()
@ -631,7 +631,7 @@ p2pProtocol BeaconSync(version = 1,
isLightClientRequest = true).} = isLightClientRequest = true).} =
trace "Received LC optimistic update request", peer trace "Received LC optimistic update request", peer
let dag = peer.networkState.dag let dag = peer.networkState.dag
doAssert dag.lightClientDataServe doAssert dag.lcDataStore.serve
peer.awaitNonNegativeRequestQuota() peer.awaitNonNegativeRequestQuota()

View File

@ -255,7 +255,7 @@ proc handleLightClientUpdates(node: BeaconNode, slot: Slot) {.async.} =
debug "Waiting to send LC updates", slot, delay = shortLog(sendTime.offset) debug "Waiting to send LC updates", slot, delay = shortLog(sendTime.offset)
await sleepAsync(sendTime.offset) await sleepAsync(sendTime.offset)
template latest(): auto = node.dag.lightClientCache.latest template latest(): auto = node.dag.lcDataStore.cache.latest
let signature_slot = latest.signature_slot let signature_slot = latest.signature_slot
if slot != signature_slot: if slot != signature_slot:
return return