nimbus-eth2/beacon_chain/validator_client/doppelganger_service.nim

118 lines
3.9 KiB
Nim

# beacon_chain
# Copyright (c) 2022-2024 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 chronicles
import "."/[common, api]
const
ServiceName = "doppelganger_service"
logScope: service = ServiceName
proc getCheckingList*(vc: ValidatorClientRef, epoch: Epoch): seq[ValidatorIndex] =
var res: seq[ValidatorIndex]
for validator in vc.attachedValidators[]:
if validator.index.isSome and
(validator.doppelCheck.isNone or validator.doppelCheck.get() < epoch):
res.add validator.index.get()
res
proc processActivities(service: DoppelgangerServiceRef, epoch: Epoch,
activities: GetValidatorsLivenessResponse) =
let vc = service.client
if len(activities.data) == 0:
debug "Unable to monitor validator's activity for epoch", epoch = epoch
else:
for item in activities.data:
let vindex = item.index
for validator in vc.attachedValidators[]:
if validator.index == Opt.some(vindex):
validator.doppelgangerChecked(epoch)
if item.is_live and validator.triggersDoppelganger(epoch):
warn "Doppelganger detection triggered",
validator = validatorLog(validator), epoch
vc.doppelExit.fire()
return
break
proc mainLoop(service: DoppelgangerServiceRef) {.async: (raises: []).} =
let vc = service.client
service.state = ServiceState.Running
if service.enabled:
debug "Service started"
else:
debug "Service disabled because of configuration settings"
return
debug "Doppelganger detection loop is waiting for initialization"
try:
await allFutures(
vc.preGenesisEvent.wait(),
vc.genesisEvent.wait(),
vc.indicesAvailable.wait()
)
except CancelledError:
debug "Service interrupted"
return
# On (re)start, we skip the remainder of the epoch before we start monitoring
# for doppelgangers so we don't trigger on the attestations we produced before
# the epoch - there's no activity in the genesis slot, so if we start at or
# before that, we can safely perform the check for epoch 0 and thus keep
# validating in epoch 1
if vc.beaconClock.now().slotOrZero() > GENESIS_SLOT:
try:
await service.waitForNextEpoch(TIME_DELAY_FROM_SLOT)
except CancelledError:
debug "Service interrupted"
return
while try:
# Wait for the epoch to end - at the end (or really, the beginning of the
# next one, we ask what happened
await service.waitForNextEpoch(TIME_DELAY_FROM_SLOT)
let
currentEpoch = vc.currentSlot().epoch()
previousEpoch =
if currentEpoch == Epoch(0):
currentEpoch
else:
currentEpoch - 1'u64
validators = vc.getCheckingList(previousEpoch)
if len(validators) > 0:
let activities = await vc.getValidatorsLiveness(previousEpoch,
validators)
service.processActivities(previousEpoch, activities)
else:
debug "No validators found that require doppelganger protection"
discard
true
except CancelledError:
debug "Service interrupted"
false
: discard
proc init*(
t: type DoppelgangerServiceRef,
vc: ValidatorClientRef
): Future[DoppelgangerServiceRef] {.async: (raises: []).} =
logScope: service = ServiceName
let res = DoppelgangerServiceRef(name: ServiceName,
client: vc, state: ServiceState.Initialized,
enabled: vc.config.doppelgangerDetection)
debug "Initializing service"
res
proc start*(service: DoppelgangerServiceRef) =
service.lifeFut = mainLoop(service)