mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-09 22:06:21 +00:00
efbd4e395a
When the epoch boundary block is missed, we incorrectly assume that the next couple blocks improve finality, leading to repeated pushes of the same light client finality update and incorrectly ignoring some gossip.
566 lines
22 KiB
Nim
566 lines
22 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2022-2023 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: [].}
|
|
|
|
import
|
|
stew/objects,
|
|
chronos, metrics,
|
|
../spec/light_client_sync,
|
|
../consensus_object_pools/block_pools_types,
|
|
".."/[beacon_clock, sszdump],
|
|
"."/[eth2_processor, gossip_validation]
|
|
|
|
export sszdump, eth2_processor, gossip_validation
|
|
|
|
logScope: topics = "gossip_lc"
|
|
|
|
# Light Client Processor
|
|
# ------------------------------------------------------------------------------
|
|
# The light client processor handles received light client objects
|
|
|
|
declareHistogram light_client_store_object_duration_seconds,
|
|
"storeObject() duration", buckets = [0.25, 0.5, 1, 2, 4, 8, Inf]
|
|
|
|
type
|
|
Nothing = object
|
|
|
|
GetTrustedBlockRootCallback* =
|
|
proc(): Option[Eth2Digest] {.gcsafe, raises: [Defect].}
|
|
VoidCallback* =
|
|
proc() {.gcsafe, raises: [Defect].}
|
|
|
|
ValueObserver[V] =
|
|
proc(v: V) {.gcsafe, raises: [Defect].}
|
|
BootstrapObserver* =
|
|
ValueObserver[ForkedLightClientBootstrap]
|
|
UpdateObserver* =
|
|
ValueObserver[ForkedLightClientUpdate]
|
|
FinalityUpdateObserver* =
|
|
ValueObserver[ForkedLightClientFinalityUpdate]
|
|
OptimisticUpdateObserver* =
|
|
ValueObserver[ForkedLightClientOptimisticUpdate]
|
|
|
|
LightClientFinalizationMode* {.pure.} = enum
|
|
Strict
|
|
## Only finalize light client data that:
|
|
## - has been signed by a supermajority (2/3) of the sync committee
|
|
## - has a valid finality proof
|
|
##
|
|
## Optimizes for security, but may become stuck if there is any of:
|
|
## - non-finality for an entire sync committee period
|
|
## - low sync committee participation for an entire sync committee period
|
|
## Such periods need to be covered by an out-of-band syncing mechanism.
|
|
##
|
|
## Note that a compromised supermajority of the sync committee is able to
|
|
## sign arbitrary light client data, even after being slashed. The light
|
|
## client cannot validate the slashing status of sync committee members.
|
|
## Likewise, voluntarily exited validators may sign bad light client data
|
|
## for the sync committee periods in which they used to be selected.
|
|
|
|
Optimistic
|
|
## Attempt to finalize light client data not satisfying strict conditions
|
|
## if there is no progress for an extended period of time and if there are
|
|
## repeated messages indicating that it is the best available data on the
|
|
## network for the affected time period.
|
|
##
|
|
## Optimizes for availability of recent data, but may end up on incorrect
|
|
## forks if run in a hostile network environment (no honest peers), or if
|
|
## the low sync committee participation is being exploited by bad actors.
|
|
|
|
LightClientProcessor* = object
|
|
## This manages the processing of received light client objects
|
|
##
|
|
## from:
|
|
## - Gossip:
|
|
## - `LightClientFinalityUpdate`
|
|
## - `LightClientOptimisticUpdate`
|
|
## - `LightClientManager`:
|
|
## - `GetLightClientBootstrap`
|
|
## - `LightClientUpdatesByRange`
|
|
## - `GetLightClientFinalityUpdate`
|
|
## - `GetLightClientOptimisticUpdate`
|
|
##
|
|
## are then verified and added to:
|
|
## - `LightClientStore`
|
|
|
|
# Config
|
|
# ----------------------------------------------------------------
|
|
dumpEnabled: bool
|
|
dumpDirInvalid: string
|
|
dumpDirIncoming: string
|
|
|
|
# Consumer
|
|
# ----------------------------------------------------------------
|
|
store: ref ForkedLightClientStore
|
|
getBeaconTime: GetBeaconTimeFn
|
|
getTrustedBlockRoot: GetTrustedBlockRootCallback
|
|
onStoreInitialized, onFinalizedHeader, onOptimisticHeader: VoidCallback
|
|
bootstrapObserver: BootstrapObserver
|
|
updateObserver: UpdateObserver
|
|
finalityUpdateObserver: FinalityUpdateObserver
|
|
optimisticUpdateObserver: OptimisticUpdateObserver
|
|
|
|
cfg: RuntimeConfig
|
|
genesis_validators_root: Eth2Digest
|
|
|
|
case finalizationMode: LightClientFinalizationMode
|
|
of LightClientFinalizationMode.Strict:
|
|
discard
|
|
of LightClientFinalizationMode.Optimistic:
|
|
lastProgressTick: BeaconTime # Moment when last update made progress
|
|
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
|
|
numDupsSinceProgress: int # Number of duplicates since last progress
|
|
|
|
latestFinalityUpdate: ForkedLightClientOptimisticUpdate
|
|
|
|
const
|
|
# These constants have been chosen empirically and are not backed by spec
|
|
duplicateRateLimit = chronos.seconds(5) # Rate limit for counting duplicates
|
|
duplicateCountDelay = chronos.minutes(15) # Delay to start counting duplicates
|
|
minForceUpdateDelay = chronos.minutes(30) # Minimum delay until forced-update
|
|
minForceUpdateDuplicates = 100 # Minimum duplicates until forced-update
|
|
|
|
# Initialization
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc new*(
|
|
T: type LightClientProcessor,
|
|
dumpEnabled: bool,
|
|
dumpDirInvalid, dumpDirIncoming: string,
|
|
cfg: RuntimeConfig,
|
|
genesis_validators_root: Eth2Digest,
|
|
finalizationMode: LightClientFinalizationMode,
|
|
store: ref ForkedLightClientStore,
|
|
getBeaconTime: GetBeaconTimeFn,
|
|
getTrustedBlockRoot: GetTrustedBlockRootCallback,
|
|
onStoreInitialized: VoidCallback = nil,
|
|
onFinalizedHeader: VoidCallback = nil,
|
|
onOptimisticHeader: VoidCallback = nil,
|
|
bootstrapObserver: BootstrapObserver = nil,
|
|
updateObserver: UpdateObserver = nil,
|
|
finalityUpdateObserver: FinalityUpdateObserver = nil,
|
|
optimisticUpdateObserver: OptimisticUpdateObserver = nil
|
|
): ref LightClientProcessor =
|
|
(ref LightClientProcessor)(
|
|
dumpEnabled: dumpEnabled,
|
|
dumpDirInvalid: dumpDirInvalid,
|
|
dumpDirIncoming: dumpDirIncoming,
|
|
store: store,
|
|
getBeaconTime: getBeaconTime,
|
|
getTrustedBlockRoot: getTrustedBlockRoot,
|
|
onStoreInitialized: onStoreInitialized,
|
|
onFinalizedHeader: onFinalizedHeader,
|
|
onOptimisticHeader: onOptimisticHeader,
|
|
bootstrapObserver: bootstrapObserver,
|
|
updateObserver: updateObserver,
|
|
finalityUpdateObserver: finalityUpdateObserver,
|
|
optimisticUpdateObserver: optimisticUpdateObserver,
|
|
cfg: cfg,
|
|
genesis_validators_root: genesis_validators_root,
|
|
finalizationMode: finalizationMode)
|
|
|
|
# Storage
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc dumpInvalidObject(
|
|
self: LightClientProcessor,
|
|
obj: SomeForkyLightClientObject) =
|
|
if self.dumpEnabled:
|
|
dump(self.dumpDirInvalid, obj)
|
|
|
|
proc dumpObject[T](
|
|
self: LightClientProcessor,
|
|
obj: SomeForkyLightClientObject,
|
|
res: Result[T, VerifierError]) =
|
|
if self.dumpEnabled and res.isErr:
|
|
case res.error
|
|
of VerifierError.Invalid:
|
|
self.dumpInvalidObject(obj)
|
|
of VerifierError.MissingParent:
|
|
dump(self.dumpDirIncoming, obj)
|
|
else:
|
|
discard
|
|
|
|
proc tryForceUpdate(
|
|
self: var LightClientProcessor,
|
|
wallTime: BeaconTime) =
|
|
## Try to force-update to the next sync committee period.
|
|
let wallSlot = wallTime.slotOrZero()
|
|
doAssert self.finalizationMode == LightClientFinalizationMode.Optimistic
|
|
|
|
withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
case forkyStore.process_light_client_store_force_update(wallSlot)
|
|
of NoUpdate:
|
|
discard
|
|
of DidUpdateWithoutSupermajority:
|
|
warn "Light client force-updated without supermajority",
|
|
finalizedSlot = forkyStore.finalized_header.beacon.slot,
|
|
optimisticSlot = forkyStore.optimistic_header.beacon.slot
|
|
of DidUpdateWithoutFinality:
|
|
warn "Light client force-updated without finality proof",
|
|
finalizedSlot = forkyStore.finalized_header.beacon.slot,
|
|
optimisticSlot = forkyStore.optimistic_header.beacon.slot
|
|
|
|
proc processObject(
|
|
self: var LightClientProcessor,
|
|
obj: SomeForkedLightClientObject,
|
|
wallTime: BeaconTime): Result[void, VerifierError] =
|
|
let
|
|
wallSlot = wallTime.slotOrZero()
|
|
res = withForkyObject(obj):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
when forkyObject is ForkyLightClientBootstrap:
|
|
if self.store[].kind > LightClientDataFork.None:
|
|
err(VerifierError.Duplicate)
|
|
else:
|
|
let trustedBlockRoot = self.getTrustedBlockRoot()
|
|
if trustedBlockRoot.isNone:
|
|
err(VerifierError.MissingParent)
|
|
else:
|
|
let initRes = initialize_light_client_store(
|
|
trustedBlockRoot.get, forkyObject, self.cfg)
|
|
if initRes.isErr:
|
|
err(initRes.error)
|
|
else:
|
|
self.store[] = ForkedLightClientStore(kind: lcDataFork)
|
|
self.store[].forky(lcDataFork) = initRes.get
|
|
ok()
|
|
elif forkyObject is SomeForkyLightClientUpdate:
|
|
if self.store[].kind == LightClientDataFork.None:
|
|
err(VerifierError.MissingParent)
|
|
else:
|
|
if lcDataFork > self.store[].kind:
|
|
info "Upgrading light client",
|
|
oldFork = self.store[].kind, newFork = lcDataFork
|
|
self.store[].migrateToDataFork(lcDataFork)
|
|
withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
let upgradedObject = obj.migratingToDataFork(lcDataFork)
|
|
process_light_client_update(
|
|
forkyStore, upgradedObject.forky(lcDataFork), wallSlot,
|
|
self.cfg, self.genesis_validators_root)
|
|
else: raiseAssert "Unreachable"
|
|
else:
|
|
err(VerifierError.Invalid)
|
|
|
|
withForkyObject(obj):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
self.dumpObject(forkyObject, res)
|
|
|
|
if res.isErr:
|
|
when obj is ForkedLightClientUpdate:
|
|
if self.finalizationMode == LightClientFinalizationMode.Optimistic and
|
|
obj.kind <= self.store[].kind:
|
|
withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
if forkyStore.best_valid_update.isSome:
|
|
# `best_valid_update` is set when supermajority / finality proof
|
|
# is unavailable. In that case, we will wait for a better update.
|
|
# If none is made available within reasonable time, light client
|
|
# is force-updated with best known data to ensure sync progress.
|
|
case res.error
|
|
of VerifierError.Duplicate:
|
|
if wallTime >= self.lastDuplicateTick + duplicateRateLimit:
|
|
if self.numDupsSinceProgress < minForceUpdateDuplicates:
|
|
let upgradedObj = obj.migratingToDataFork(lcDataFork)
|
|
if upgradedObj.forky(lcDataFork).matches(
|
|
forkyStore.best_valid_update.get):
|
|
self.lastDuplicateTick = wallTime
|
|
inc self.numDupsSinceProgress
|
|
if self.numDupsSinceProgress >= minForceUpdateDuplicates and
|
|
wallTime >= self.lastProgressTick + minForceUpdateDelay:
|
|
self.tryForceUpdate(wallTime)
|
|
self.lastProgressTick = wallTime
|
|
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
|
self.numDupsSinceProgress = 0
|
|
else: discard
|
|
|
|
return res
|
|
|
|
when obj is ForkedLightClientBootstrap | ForkedLightClientUpdate:
|
|
if self.finalizationMode == LightClientFinalizationMode.Optimistic:
|
|
self.lastProgressTick = wallTime
|
|
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
|
self.numDupsSinceProgress = 0
|
|
|
|
res
|
|
|
|
template withReportedProgress(
|
|
obj: SomeForkedLightClientObject | Nothing, body: untyped): bool =
|
|
block:
|
|
let
|
|
oldIsInitialized = self.store[].kind > LightClientDataFork.None
|
|
oldNextCommitteeKnown = withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
forkyStore.is_next_sync_committee_known
|
|
else:
|
|
false
|
|
var
|
|
oldFinalized = withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
var header = ForkedLightClientHeader(kind: lcDataFork)
|
|
header.forky(lcDataFork) = forkyStore.finalized_header
|
|
header
|
|
else:
|
|
default(ForkedLightClientHeader)
|
|
oldOptimistic = withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
var header = ForkedLightClientHeader(kind: lcDataFork)
|
|
header.forky(lcDataFork) = forkyStore.optimistic_header
|
|
header
|
|
else:
|
|
default(ForkedLightClientHeader)
|
|
|
|
body
|
|
|
|
var
|
|
didProgress = false
|
|
didSignificantProgress = false
|
|
|
|
let newIsInitialized = self.store[].kind > LightClientDataFork.None
|
|
if newIsInitialized > oldIsInitialized:
|
|
didProgress = true
|
|
didSignificantProgress = true
|
|
if self.onStoreInitialized != nil:
|
|
self.onStoreInitialized()
|
|
self.onStoreInitialized = nil
|
|
|
|
withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
if oldOptimistic.kind <= lcDataFork:
|
|
oldOptimistic.migrateToDataFork(lcDataFork)
|
|
if forkyStore.optimistic_header != oldOptimistic.forky(lcDataFork):
|
|
didProgress = true
|
|
when obj isnot SomeForkedLightClientUpdateWithFinality:
|
|
didSignificantProgress = true
|
|
if self.onOptimisticHeader != nil:
|
|
self.onOptimisticHeader()
|
|
|
|
if oldFinalized.kind <= lcDataFork:
|
|
oldFinalized.migrateToDataFork(lcDataFork)
|
|
if forkyStore.finalized_header != oldFinalized.forky(lcDataFork):
|
|
didProgress = true
|
|
didSignificantProgress = true
|
|
if self.onFinalizedHeader != nil:
|
|
self.onFinalizedHeader()
|
|
|
|
if forkyStore.is_next_sync_committee_known != oldNextCommitteeKnown:
|
|
didProgress = true
|
|
|
|
if didProgress:
|
|
when obj is Nothing:
|
|
discard
|
|
elif obj is ForkedLightClientBootstrap:
|
|
if self.bootstrapObserver != nil:
|
|
self.bootstrapObserver(obj)
|
|
elif obj is ForkedLightClientUpdate:
|
|
if self.updateObserver != nil:
|
|
self.updateObserver(obj)
|
|
elif obj is ForkedLightClientFinalityUpdate:
|
|
if self.finalityUpdateObserver != nil:
|
|
self.finalityUpdateObserver(obj)
|
|
elif obj is ForkedLightClientOptimisticUpdate:
|
|
if self.optimisticUpdateObserver != nil:
|
|
self.optimisticUpdateObserver(obj)
|
|
else: raiseAssert "Unreachable"
|
|
|
|
didSignificantProgress
|
|
|
|
template withReportedProgress(body: untyped): bool =
|
|
withReportedProgress(Nothing(), body)
|
|
|
|
proc storeObject*(
|
|
self: var LightClientProcessor,
|
|
src: MsgSource, wallTime: BeaconTime,
|
|
obj: SomeForkedLightClientObject): Result[bool, VerifierError] =
|
|
## storeObject is the main entry point for unvalidated light client objects -
|
|
## all untrusted objects pass through here. When storing an object, we will
|
|
## update the `LightClientStore` accordingly
|
|
let
|
|
startTick = Moment.now()
|
|
didSignificantProgress =
|
|
withReportedProgress(obj):
|
|
? self.processObject(obj, wallTime)
|
|
|
|
let
|
|
storeObjectTick = Moment.now()
|
|
storeObjectDur = storeObjectTick - startTick
|
|
|
|
light_client_store_object_duration_seconds.observe(
|
|
storeObjectDur.toFloatSeconds())
|
|
|
|
let objSlot = withForkyObject(obj):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
when forkyObject is ForkyLightClientBootstrap:
|
|
forkyObject.header.beacon.slot
|
|
elif forkyObject is SomeForkyLightClientUpdateWithFinality:
|
|
forkyObject.finalized_header.beacon.slot
|
|
else:
|
|
forkyObject.attested_header.beacon.slot
|
|
else:
|
|
GENESIS_SLOT
|
|
withForkyStore(self.store[]):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
debug "LC object processed",
|
|
finalizedSlot = forkyStore.finalized_header.beacon.slot,
|
|
optimisticSlot = forkyStore.optimistic_header.beacon.slot,
|
|
kind = typeof(obj).name,
|
|
objectSlot = objSlot,
|
|
storeObjectDur
|
|
ok didSignificantProgress
|
|
|
|
proc resetToFinalizedHeader*(
|
|
self: var LightClientProcessor,
|
|
header: ForkedLightClientHeader,
|
|
current_sync_committee: SyncCommittee) =
|
|
discard withReportedProgress:
|
|
withForkyHeader(header):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
self.store[] = ForkedLightClientStore(kind: lcDataFork)
|
|
template forkyStore: untyped = self.store[].forky(lcDataFork)
|
|
forkyStore = lcDataFork.LightClientStore(
|
|
finalized_header: forkyHeader,
|
|
current_sync_committee: current_sync_committee,
|
|
optimistic_header: forkyHeader)
|
|
debug "LC reset to finalized header",
|
|
finalizedSlot = forkyStore.finalized_header.beacon.slot,
|
|
optimisticSlot = forkyStore.optimistic_header.beacon.slot
|
|
else:
|
|
self.store[].reset()
|
|
debug "LC reset"
|
|
|
|
# Enqueue
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc addObject*(
|
|
self: var LightClientProcessor,
|
|
src: MsgSource,
|
|
obj: SomeForkedLightClientObject,
|
|
resfut: Future[Result[void, VerifierError]] = nil) =
|
|
## Enqueue a Gossip-validated light client object for verification
|
|
# Backpressure:
|
|
# Only one object is validated at any time -
|
|
# Light client objects are always "fast" to process
|
|
# Producers:
|
|
# - Gossip:
|
|
# - `LightClientFinalityUpdate`
|
|
# - `LightClientOptimisticUpdate`
|
|
# - `LightClientManager`:
|
|
# - `GetLightClientBootstrap`
|
|
# - `LightClientUpdatesByRange`
|
|
# - `GetLightClientFinalityUpdate`
|
|
# - `GetLightClientOptimisticUpdate`
|
|
|
|
let
|
|
wallTime = self.getBeaconTime()
|
|
(afterGenesis, wallSlot) = wallTime.toSlot()
|
|
|
|
if not afterGenesis:
|
|
error "Processing LC object before genesis, clock turned back?"
|
|
quit 1
|
|
|
|
let res = self.storeObject(src, wallTime, obj)
|
|
|
|
if resfut != nil:
|
|
if res.isOk:
|
|
resfut.complete(Result[void, VerifierError].ok())
|
|
else:
|
|
resfut.complete(Result[void, VerifierError].err(res.error))
|
|
|
|
# Message validators
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func toValidationError(
|
|
self: var LightClientProcessor,
|
|
r: Result[bool, VerifierError],
|
|
wallTime: BeaconTime,
|
|
obj: SomeForkedLightClientObject): Result[void, ValidationError] =
|
|
if r.isOk:
|
|
let didSignificantProgress = r.get
|
|
if didSignificantProgress:
|
|
let
|
|
signature_slot = withForkyObject(obj):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
forkyObject.signature_slot
|
|
else:
|
|
GENESIS_SLOT
|
|
currentTime = wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY
|
|
forwardTime = signature_slot.light_client_finality_update_time
|
|
if currentTime < forwardTime:
|
|
# [IGNORE] The `finality_update` is received after the block
|
|
# at `signature_slot` was given enough time to propagate through
|
|
# the network.
|
|
# [IGNORE] The `optimistic_update` is received after the block
|
|
# at `signature_slot` was given enough time to propagate through
|
|
# the network.
|
|
return errIgnore(typeof(obj).name & ": received too early")
|
|
ok()
|
|
else:
|
|
when obj is ForkedLightClientOptimisticUpdate:
|
|
# [IGNORE] The `optimistic_update` either matches corresponding fields
|
|
# of the most recently forwarded `LightClientFinalityUpdate` (if any),
|
|
# or it advances the `optimistic_header` of the local `LightClientStore`
|
|
if obj.matches(self.latestFinalityUpdate):
|
|
return ok()
|
|
# [IGNORE] The `finality_update` advances the `finalized_header` of the
|
|
# local `LightClientStore`.
|
|
errIgnore(typeof(obj).name & ": no significant progress")
|
|
else:
|
|
case r.error
|
|
of VerifierError.Invalid:
|
|
# [REJECT] The `finality_update` is valid.
|
|
# [REJECT] The `optimistic_update` is valid.
|
|
errReject($r.error)
|
|
of VerifierError.MissingParent,
|
|
VerifierError.UnviableFork,
|
|
VerifierError.Duplicate:
|
|
# [IGNORE] The `finalized_header.beacon.slot` is greater than that of
|
|
# all previously forwarded `finality_update`s
|
|
# [IGNORE] The `attested_header.beacon.slot` is greater than that of all
|
|
# previously forwarded `optimistic_update`s
|
|
errIgnore($r.error)
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/sync-protocol.md#process_light_client_finality_update
|
|
proc processLightClientFinalityUpdate*(
|
|
self: var LightClientProcessor, src: MsgSource,
|
|
finality_update: ForkedLightClientFinalityUpdate
|
|
): Result[void, ValidationError] =
|
|
let
|
|
wallTime = self.getBeaconTime()
|
|
r = self.storeObject(src, wallTime, finality_update)
|
|
v = self.toValidationError(r, wallTime, finality_update)
|
|
if v.isOk:
|
|
self.latestFinalityUpdate = finality_update.toOptimistic
|
|
v
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/sync-protocol.md#process_light_client_finality_update
|
|
proc processLightClientOptimisticUpdate*(
|
|
self: var LightClientProcessor, src: MsgSource,
|
|
optimistic_update: ForkedLightClientOptimisticUpdate
|
|
): Result[void, ValidationError] =
|
|
let
|
|
wallTime = self.getBeaconTime()
|
|
r = self.storeObject(src, wallTime, optimistic_update)
|
|
v = self.toValidationError(r, wallTime, optimistic_update)
|
|
if v.isOk:
|
|
let
|
|
latestFinalitySlot = withForkyOptimisticUpdate(self.latestFinalityUpdate):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
forkyOptimisticUpdate.attested_header.beacon.slot
|
|
else:
|
|
GENESIS_SLOT
|
|
attestedSlot = withForkyOptimisticUpdate(optimistic_update):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
forkyOptimisticUpdate.attested_header.beacon.slot
|
|
else:
|
|
GENESIS_SLOT
|
|
if attestedSlot >= latestFinalitySlot:
|
|
self.latestFinalityUpdate.reset() # Only forward once
|
|
v
|