Merge branch 'stable' into dev/etan/lc-wasm4

This commit is contained in:
Etan Kissling 2024-03-12 01:13:56 +01:00
commit 43b3b5b8b1
No known key found for this signature in database
GPG Key ID: B21DA824C5A3D03D
21 changed files with 524 additions and 129 deletions

View File

@ -1,3 +1,25 @@
2023-02-27 v24.2.2
==================
Nimbus `v24.2.2` is a hotfix release addressing a consensus violation issue affecting Deneb-transitioned network such as Holešky. Please upgrade as soon as possible if your node is affected.
### Improvements
* Added metrics `validator_monitor_block_hit` and `validator_monitor_block_miss` tracking the number of successful and missed block proposals:
https://github.com/status-im/nimbus-eth2/pull/5913
### Fixes
* Nimbus had an incomplete implementation of EIP-7044 (Perpetually Valid Signed Voluntary Exits):
https://github.com/status-im/nimbus-eth2/pull/5953
https://github.com/status-im/nimbus-eth2/pull/5954
https://github.com/status-im/nimbus-eth2/pull/5959
https://github.com/status-im/nimbus-eth2/pull/5966
* The Nimbus `v24.2.1` validator client was crashing with a `RangeDefect` error message during block proposal when paired with a `v24.1.x` beacon node:
https://github.com/status-im/nim-stint/pull/148
2023-02-20 v24.2.1
==================

View File

@ -285,7 +285,7 @@ proc addHeadBlockWithParent*(
var sigs: seq[SignatureSet]
if (let e = sigs.collectSignatureSets(
signedBlock, dag.db.immutableValidators,
dag.clearanceState, dag.cfg.genesisFork(), dag.cfg.capellaFork(),
dag.clearanceState, dag.cfg.genesisFork(), dag.cfg.CAPELLA_FORK_VERSION,
cache); e.isErr()):
# A PublicKey or Signature isn't on the BLS12-381 curve
info "Unable to load signature sets",

View File

@ -956,9 +956,14 @@ proc advanceSlots*(
# which is an acceptable tradeoff for monitoring.
withState(state):
let postEpoch = forkyState.data.slot.epoch
if preEpoch != postEpoch:
if preEpoch != postEpoch and postEpoch >= 2:
var proposers: array[SLOTS_PER_EPOCH, Opt[ValidatorIndex]]
let epochRef = dag.findEpochRef(stateBid, postEpoch - 2)
if epochRef.isSome():
proposers = epochRef[][].beacon_proposers
dag.validatorMonitor[].registerEpochInfo(
postEpoch, info, forkyState.data)
forkyState.data, proposers, info)
proc applyBlock(
dag: ChainDAGRef, state: var ForkedHashedBeaconState, bid: BlockId,

View File

@ -220,33 +220,19 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
reason = exc.msg
quit 1
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#voluntary-exits
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.0/specs/deneb/beacon-chain.md#modified-process_voluntary_exit
let signingFork = try:
let response = await client.getSpecVC()
if response.status == 200:
let
spec = response.data.data
denebForkEpoch =
block:
let s = spec.getOrDefault("DENEB_FORK_EPOCH", $FAR_FUTURE_EPOCH)
Epoch(Base10.decode(uint64, s).get(uint64(FAR_FUTURE_EPOCH)))
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#voluntary-exits
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.0/specs/deneb/beacon-chain.md#modified-process_voluntary_exit
if currentEpoch >= denebForkEpoch:
let capellaForkVersion =
block:
var res: Version
# CAPELLA_FOR_VERSION has specific format - "0x01000000", so
# default empty string is invalid, so `hexToByteArrayStrict`
# will raise exception on empty string.
let s = spec.getOrDefault("CAPELLA_FORK_VERSION", "")
hexToByteArrayStrict(s, distinctBase(res))
res
Fork(
current_version: capellaForkVersion,
previous_version: capellaForkVersion,
epoch: GENESIS_EPOCH) # irrelevant when current/previous identical
else:
fork
let forkConfig = response.data.data.getConsensusForkConfig()
if forkConfig.isErr:
raise newException(RestError, "Invalid config: " & forkConfig.error)
let capellaForkVersion = forkConfig.get.capellaVersion.valueOr:
raise newException(RestError,
ConsensusFork.Capella.forkVersionConfigKey() & " missing")
voluntary_exit_signature_fork(
fork, capellaForkVersion, currentEpoch, forkConfig.get.denebEpoch)
else:
raise newException(RestError, "Error response (" & $response.status & ")")
except CatchableError as exc:
@ -254,7 +240,7 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
reason = exc.msg
quit 1
debug "Signing fork obtained", fork = fork
debug "Signing fork obtained", fork, signingFork
if not config.printData:
case askForExitConfirmation()
@ -292,7 +278,7 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
validatorKeyAsStr,
exitAtEpoch,
validatorIdx,
fork,
signingFork,
genesis_validators_root)
if config.printData:

View File

@ -755,6 +755,12 @@ proc init*(T: type BeaconNode,
withState(dag.headState):
getValidator(forkyState().data.validators.asSeq(), pubkey)
func getCapellaForkVersion(): Opt[Version] =
Opt.some(cfg.CAPELLA_FORK_VERSION)
func getDenebForkEpoch(): Opt[Epoch] =
Opt.some(cfg.DENEB_FORK_EPOCH)
proc getForkForEpoch(epoch: Epoch): Opt[Fork] =
Opt.some(dag.forkAtEpoch(epoch))
@ -784,6 +790,8 @@ proc init*(T: type BeaconNode,
config.getPayloadBuilderAddress,
getValidatorAndIdx,
getBeaconTime,
getCapellaForkVersion,
getDenebForkEpoch,
getForkForEpoch,
getGenesisRoot)
else: nil

View File

@ -4,6 +4,9 @@
# * 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/io2, presto, metrics, metrics/chronos_httpserver,
./rpc/rest_key_management_api,
@ -348,6 +351,18 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
let
keymanagerInitResult = initKeymanagerServer(vc.config, nil)
func getCapellaForkVersion(): Opt[Version] =
if vc.runtimeConfig.forkConfig.isSome():
vc.runtimeConfig.forkConfig.get().capellaVersion
else:
Opt.none(Version)
func getDenebForkEpoch(): Opt[Epoch] =
if vc.runtimeConfig.forkConfig.isSome():
Opt.some(vc.runtimeConfig.forkConfig.get().denebEpoch)
else:
Opt.none(Epoch)
proc getForkForEpoch(epoch: Epoch): Opt[Fork] =
if len(vc.forks) > 0:
Opt.some(vc.forkAtEpoch(epoch))
@ -379,6 +394,8 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
Opt.none(string),
nil,
vc.beaconClock.getBeaconTimeFn,
getCapellaForkVersion,
getDenebForkEpoch,
getForkForEpoch,
getGenesisRoot
)

View File

@ -241,6 +241,10 @@ const
"The given Merkle proof is invalid"
InvalidMerkleProofIndexError* =
"The given Merkle proof index is invalid"
FailedToObtainForkVersionError* =
"Failed to obtain fork version"
FailedToObtainConsensusForkError* =
"Failed to obtain consensus fork information"
FailedToObtainForkError* =
"Failed to obtain fork information"
InvalidTimestampValue* =

View File

@ -1,9 +1,12 @@
# beacon_chain
# Copyright (c) 2021-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: [].}
# NOTE: This module has been used in both `beacon_node` and `validator_client`,
# please keep imports clear of `rest_utils` or any other module which imports
# beacon node's specific networking code.
@ -561,6 +564,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
let
qpubkey = pubkey.valueOr:
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
currentEpoch = host.getBeaconTimeFn().slotOrZero().epoch()
qepoch =
if epoch.isSome():
let res = epoch.get()
@ -568,7 +572,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
return keymanagerApiError(Http400, InvalidEpochValueError)
res.get()
else:
host.getBeaconTimeFn().slotOrZero().epoch()
currentEpoch
validator =
block:
let res = host.validatorPool[].getValidator(qpubkey).valueOr:
@ -581,10 +585,16 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
validator_index: uint64(validator.index.get()))
fork = host.getForkFn(qepoch).valueOr:
return keymanagerApiError(Http500, FailedToObtainForkError)
capellaForkVersion = host.getCapellaForkVersionFn().valueOr:
return keymanagerApiError(Http500, FailedToObtainForkVersionError)
denebForkEpoch = host.getDenebForkEpochFn().valueOr:
return keymanagerApiError(Http500, FailedToObtainConsensusForkError)
signingFork = voluntary_exit_signature_fork(
fork, capellaForkVersion, currentEpoch, denebForkEpoch)
signature =
try:
let res = await validator.getValidatorExitSignature(
fork, host.getGenesisFn(), voluntaryExit)
signingFork, host.getGenesisFn(), voluntaryExit)
if res.isErr():
return keymanagerApiError(Http500, res.error())
res.get()

View File

@ -13,7 +13,8 @@ import
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
rest_keymanager_calls, rest_light_client_calls,
rest_node_calls, rest_validator_calls,
rest_nimbus_calls, rest_event_calls, rest_common
rest_nimbus_calls, rest_event_calls, rest_common,
rest_fork_config
]
export
@ -21,4 +22,5 @@ export
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
rest_keymanager_calls, rest_light_client_calls,
rest_node_calls, rest_validator_calls,
rest_nimbus_calls, rest_event_calls, rest_common
rest_nimbus_calls, rest_event_calls, rest_common,
rest_fork_config

View File

@ -0,0 +1,97 @@
# beacon_chain
# Copyright (c) 2018-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
std/strutils,
stew/[base10, byteutils],
../forks
from ./rest_types import VCRuntimeConfig
export forks, rest_types
type VCForkConfig* = object
altairEpoch*: Epoch
capellaVersion*: Opt[Version]
capellaEpoch*: Epoch
denebEpoch*: Epoch
func forkVersionConfigKey*(consensusFork: ConsensusFork): string =
if consensusFork > ConsensusFork.Phase0:
($consensusFork).toUpperAscii() & "_FORK_VERSION"
else:
"GENESIS_FORK_VERSION"
func forkEpochConfigKey*(consensusFork: ConsensusFork): string =
doAssert consensusFork > ConsensusFork.Phase0
($consensusFork).toUpperAscii() & "_FORK_EPOCH"
proc getOrDefault*(info: VCRuntimeConfig, name: string,
default: uint64): uint64 =
let numstr = info.getOrDefault(name, "missing")
if numstr == "missing": return default
Base10.decode(uint64, numstr).valueOr:
return default
proc getOrDefault*(info: VCRuntimeConfig, name: string, default: Epoch): Epoch =
Epoch(info.getOrDefault(name, uint64(default)))
func getForkVersion(
info: VCRuntimeConfig,
consensusFork: Consensusfork): Result[Opt[Version], string] =
let key = consensusFork.forkVersionConfigKey()
let stringValue = info.getOrDefault(key, "missing")
if stringValue == "missing": return ok Opt.none(Version)
var value: Version
try:
hexToByteArrayStrict(stringValue, distinctBase(value))
except ValueError as exc:
return err(key & " is invalid: " & exc.msg)
ok Opt.some value
func getForkEpoch(info: VCRuntimeConfig, consensusFork: ConsensusFork): Epoch =
if consensusFork > ConsensusFork.Phase0:
let key = consensusFork.forkEpochConfigKey()
info.getOrDefault(key, FAR_FUTURE_EPOCH)
else:
GENESIS_EPOCH
func getConsensusForkConfig*(
info: VCRuntimeConfig): Result[VCForkConfig, string] =
## This extracts all `_FORK_VERSION` and `_FORK_EPOCH` constants
## that are relevant for Validator Client operation.
##
## Note that the fork schedule (`/eth/v1/config/fork_schedule`) cannot be used
## because it does not indicate whether the forks refer to `ConsensusFork` or
## to a different fork sequence from an incompatible network (e.g., devnet)
let
res = VCForkConfig(
altairEpoch: info.getForkEpoch(ConsensusFork.Altair),
capellaVersion: ? info.getForkVersion(ConsensusFork.Capella),
capellaEpoch: info.getForkEpoch(ConsensusFork.Capella),
denebEpoch: info.getForkEpoch(ConsensusFork.Deneb))
if res.capellaEpoch < res.altairEpoch:
return err(
"Fork epochs are inconsistent, " & $ConsensusFork.Capella &
" is scheduled at epoch " & $res.capellaEpoch &
" which is before prior fork epoch " & $res.altairEpoch)
if res.denebEpoch < res.capellaEpoch:
return err(
"Fork epochs are inconsistent, " & $ConsensusFork.Deneb &
" is scheduled at epoch " & $res.denebEpoch &
" which is before prior fork epoch " & $res.capellaEpoch)
if res.capellaEpoch != FAR_FUTURE_EPOCH and res.capellaVersion.isNone:
return err(
"Beacon node has scheduled " &
ConsensusFork.Capella.forkEpochConfigKey() &
" but does not report " &
ConsensusFork.Capella.forkVersionConfigKey())
ok res

View File

@ -216,6 +216,41 @@ func compute_voluntary_exit_signing_root*(
fork, DOMAIN_VOLUNTARY_EXIT, epoch, genesis_validators_root)
compute_signing_root(voluntary_exit, domain)
func voluntary_exit_signature_fork(
is_post_deneb: static bool,
state_fork: Fork,
capella_fork_version: Version): Fork =
when is_post_deneb:
# Always use Capella fork version, disregarding `VoluntaryExit` epoch
# [Modified in Deneb:EIP7044]
Fork(
previous_version: capella_fork_version,
current_version: capella_fork_version,
epoch: GENESIS_EPOCH) # irrelevant when current/previous identical
else:
state_fork
func voluntary_exit_signature_fork*(
consensusFork: static ConsensusFork,
state_fork: Fork,
capella_fork_version: Version): Fork =
const is_post_deneb = (consensusFork >= ConsensusFork.Deneb)
voluntary_exit_signature_fork(is_post_deneb, state_fork, capella_fork_version)
func voluntary_exit_signature_fork*(
state_fork: Fork,
capella_fork_version: Version,
current_epoch: Epoch,
deneb_fork_epoch: Epoch): Fork =
if current_epoch >= deneb_fork_epoch:
const is_post_deneb = true
voluntary_exit_signature_fork(
is_post_deneb, state_fork, capella_fork_version)
else:
const is_post_deneb = false
voluntary_exit_signature_fork(
is_post_deneb, state_fork, capella_fork_version)
func get_voluntary_exit_signature*(
fork: Fork, genesis_validators_root: Eth2Digest,
voluntary_exit: VoluntaryExit,

View File

@ -235,7 +235,7 @@ proc collectSignatureSets*(
validatorKeys: openArray[ImmutableValidatorData2],
state: ForkedHashedBeaconState,
genesis_fork: Fork,
capella_fork: Fork,
capella_fork_version: Version,
cache: var StateCache): Result[void, cstring] =
## Collect all signature verifications that process_block would normally do
## except deposits, in one go.
@ -385,25 +385,24 @@ proc collectSignatureSets*(
# SSZ deserialization guarantees that blocks received from random sources
# including peer or RPC
# have at most MAX_VOLUNTARY_EXITS voluntary exits.
for i in 0 ..< signed_block.message.body.voluntary_exits.len:
# don't use "items" for iterating over large type
# due to https://github.com/nim-lang/Nim/issues/14421
# fixed in 1.4.2
template volex: untyped = signed_block.message.body.voluntary_exits[i]
let key = validatorKeys.load(volex.message.validator_index).valueOr:
return err("collectSignatureSets: invalid voluntary exit")
if signed_block.message.body.voluntary_exits.len > 0:
let voluntary_exit_fork = withConsensusFork(state.kind):
consensusFork.voluntary_exit_signature_fork(fork, capella_fork_version)
for i in 0 ..< signed_block.message.body.voluntary_exits.len:
# don't use "items" for iterating over large type
# due to https://github.com/nim-lang/Nim/issues/14421
# fixed in 1.4.2
template volex: untyped = signed_block.message.body.voluntary_exits[i]
let key = validatorKeys.load(volex.message.validator_index).valueOr:
return err("collectSignatureSets: invalid voluntary exit")
sigs.add voluntary_exit_signature_set(
# https://eips.ethereum.org/EIPS/eip-7044
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/deneb/beacon-chain.md#modified-process_voluntary_exit
(if state.kind >= ConsensusFork.Capella:
capella_fork
else:
fork),
genesis_validators_root, volex.message, key,
volex.signature.load.valueOr do:
return err(
"collectSignatureSets: cannot load voluntary exit signature"))
sigs.add voluntary_exit_signature_set(
# https://eips.ethereum.org/EIPS/eip-7044
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.7/specs/deneb/beacon-chain.md#modified-process_voluntary_exit
voluntary_exit_fork, genesis_validators_root, volex.message, key,
volex.signature.load.valueOr do:
return err(
"collectSignatureSets: cannot load voluntary exit signature"))
block:
when signed_block is phase0.SignedBeaconBlock:

View File

@ -269,9 +269,23 @@ func findValidatorIndex*(state: ForkyBeaconState, pubkey: ValidatorPubKey):
# given that each block can hold no more than 16 deposits, it's slower to
# build the table and use it for lookups than to scan it like this.
# Once we have a reusable, long-lived cache, this should be revisited
for vidx in state.validators.vindices:
if state.validators.asSeq[vidx].pubkey == pubkey:
return Opt[ValidatorIndex].ok(vidx)
#
# For deposit processing purposes, two broad cases exist, either
#
# (a) someone has deposited all 32 required ETH as a single transaction,
# in which case the index doesn't yet exist so the search order does
# not matter so long as it's generally in an order memory controller
# prefetching can predict; or
#
# (b) the deposit has been split into multiple parts, typically not far
# apart from each other, such that on average one would expect this
# validator index to be nearer the maximal than minimal index.
#
# countdown() infinite-loops if the lower bound with uint32 is 0, so
# shift indices by 1, which avoids triggering unsigned wraparound.
for vidx in countdown(state.validators.len.uint32, 1):
if state.validators.asSeq[vidx - 1].pubkey == pubkey:
return Opt[ValidatorIndex].ok((vidx - 1).ValidatorIndex)
proc process_deposit*(cfg: RuntimeConfig,
state: var ForkyBeaconState,
@ -365,16 +379,11 @@ proc check_voluntary_exit*(
# Verify signature
if skipBlsValidation notin flags:
let exitSignatureFork =
when typeof(state).kind >= ConsensusFork.Deneb:
Fork(
previous_version: cfg.CAPELLA_FORK_VERSION,
current_version: cfg.CAPELLA_FORK_VERSION,
epoch: cfg.CAPELLA_FORK_EPOCH)
else:
state.fork
const consensusFork = typeof(state).kind
let voluntary_exit_fork = consensusFork.voluntary_exit_signature_fork(
state.fork, cfg.CAPELLA_FORK_VERSION)
if not verify_voluntary_exit_signature(
exitSignatureFork, state.genesis_validators_root, voluntary_exit,
voluntary_exit_fork, state.genesis_validators_root, voluntary_exit,
validator[].pubkey, signed_voluntary_exit.signature):
return err("Exit: invalid signature")

View File

@ -192,7 +192,7 @@ type
waiters*: seq[BlockWaiter]
ValidatorRuntimeConfig* = object
altairEpoch*: Opt[Epoch]
forkConfig*: Opt[VCForkConfig]
ValidatorClient* = object
config*: ValidatorClientConf
@ -518,16 +518,6 @@ proc equals*(info: VCRuntimeConfig, name: string, check: DomainType): bool =
proc equals*(info: VCRuntimeConfig, name: string, check: Epoch): bool =
info.equals(name, uint64(check))
proc getOrDefault*(info: VCRuntimeConfig, name: string,
default: uint64): uint64 =
let numstr = info.getOrDefault(name, "missing")
if numstr == "missing": return default
Base10.decode(uint64, numstr).valueOr:
return default
proc getOrDefault*(info: VCRuntimeConfig, name: string, default: Epoch): Epoch =
Epoch(info.getOrDefault(name, uint64(default)))
proc checkConfig*(c: VCRuntimeConfig): bool =
c.equals("MAX_VALIDATORS_PER_COMMITTEE", MAX_VALIDATORS_PER_COMMITTEE) and
c.equals("SLOTS_PER_EPOCH", SLOTS_PER_EPOCH) and
@ -1436,33 +1426,86 @@ func `==`*(a, b: SyncCommitteeDuty): bool =
proc updateRuntimeConfig*(vc: ValidatorClientRef,
node: BeaconNodeServerRef,
info: VCRuntimeConfig): Result[void, string] =
if not(info.hasKey("ALTAIR_FORK_EPOCH")):
debug "Beacon node's configuration missing ALTAIR_FORK_EPOCH value",
node = node
var forkConfig = ? info.getConsensusForkConfig()
let
res = info.getOrDefault("ALTAIR_FORK_EPOCH", FAR_FUTURE_EPOCH)
wallEpoch = vc.beaconClock.now().slotOrZero().epoch()
if vc.runtimeConfig.forkConfig.isNone():
vc.runtimeConfig.forkConfig = Opt.some(forkConfig)
else:
template localForkConfig: untyped = vc.runtimeConfig.forkConfig.get()
let wallEpoch = vc.beaconClock.now().slotOrZero().epoch()
return
if vc.runtimeConfig.altairEpoch.get(FAR_FUTURE_EPOCH) == FAR_FUTURE_EPOCH:
vc.runtimeConfig.altairEpoch = Opt.some(res)
ok()
else:
if res == vc.runtimeConfig.altairEpoch.get():
ok()
proc validateForkVersionCompatibility(
consensusFork: ConsensusFork,
localForkVersion: Opt[Version],
localForkEpoch: Epoch,
forkVersion: Opt[Version]): Result[void, string] =
if localForkVersion.isNone():
ok() # Potentially discovered new fork, save it at end of function
else:
if res == FAR_FUTURE_EPOCH:
if wallEpoch < vc.runtimeConfig.altairEpoch.get():
debug "Beacon node must be updated before Altair activates",
if forkVersion.isSome():
if forkVersion.get() == localForkVersion.get():
ok() # Already known
else:
err("Beacon node has conflicting " &
consensusFork.forkVersionConfigKey() & " value")
else:
if wallEpoch < localForkEpoch:
debug "Beacon node must be updated before fork activates",
node = node,
altairForkEpoch = vc.runtimeConfig.altairEpoch.get()
consensusFork,
forkEpoch = localForkEpoch
ok()
else:
err("Beacon node must be updated and report correct " &
"ALTAIR_FORK_EPOCH value")
$consensusFork & " config value")
? ConsensusFork.Capella.validateForkVersionCompatibility(
localForkConfig.capellaVersion,
localForkConfig.capellaEpoch,
forkConfig.capellaVersion)
proc validateForkEpochCompatibility(
consensusFork: ConsensusFork,
localForkEpoch: Epoch,
forkEpoch: Epoch): Result[void, string] =
if localForkEpoch == FAR_FUTURE_EPOCH:
ok() # Potentially discovered new fork, save it at end of function
else:
if forkEpoch != FAR_FUTURE_EPOCH:
if forkEpoch == localForkEpoch:
ok() # Already known
else:
err("Beacon node has conflicting " &
consensusFork.forkEpochConfigKey() & " value")
else:
err("Beacon node has conflicting ALTAIR_FORK_EPOCH value")
if wallEpoch < localForkEpoch:
debug "Beacon node must be updated before fork activates",
node = node,
consensusFork,
forkEpoch = localForkEpoch
ok()
else:
err("Beacon node must be updated and report correct " &
$consensusFork & " config value")
? ConsensusFork.Altair.validateForkEpochCompatibility(
localForkConfig.altairEpoch, forkConfig.altairEpoch)
? ConsensusFork.Capella.validateForkEpochCompatibility(
localForkConfig.capellaEpoch, forkConfig.capellaEpoch)
? ConsensusFork.Deneb.validateForkEpochCompatibility(
localForkConfig.denebEpoch, forkConfig.denebEpoch)
# Save newly discovered forks.
if localForkConfig.altairEpoch == FAR_FUTURE_EPOCH:
localForkConfig.altairEpoch = forkConfig.altairEpoch
if localForkConfig.capellaVersion.isNone():
localForkConfig.capellaVersion = forkConfig.capellaVersion
if localForkConfig.capellaEpoch == FAR_FUTURE_EPOCH:
localForkConfig.capellaEpoch = forkConfig.capellaEpoch
if localForkConfig.denebEpoch == FAR_FUTURE_EPOCH:
localForkConfig.denebEpoch = forkConfig.denebEpoch
ok()
proc `+`*(slot: Slot, epochs: Epoch): Slot =
slot + uint64(epochs) * SLOTS_PER_EPOCH

View File

@ -5,6 +5,8 @@
# * 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 std/[sets, sequtils]
import chronicles, metrics
import "."/[common, api, block_service, selection_proofs]
@ -210,7 +212,8 @@ proc pollForSyncCommitteeDuties*(
let
vc = service.client
indices = toSeq(vc.attachedValidators[].indices())
epoch = max(period.start_epoch(), vc.runtimeConfig.altairEpoch.get())
altairEpoch = vc.runtimeConfig.forkConfig.get().altairEpoch
epoch = max(period.start_epoch(), altairEpoch)
relevantDuties =
block:
var duties: seq[RestSyncCommitteeDuty]
@ -369,8 +372,11 @@ proc pollForSyncCommitteeDuties*(service: DutiesServiceRef) {.async.} =
let
currentSlot = vc.getCurrentSlot().get(Slot(0))
currentEpoch = currentSlot.epoch()
altairEpoch = vc.runtimeConfig.altairEpoch.valueOr:
return
altairEpoch =
if vc.runtimeConfig.forkConfig.isSome():
vc.runtimeConfig.forkConfig.get().altairEpoch
else:
return
if currentEpoch < altairEpoch:
# We are not going to poll for sync committee duties until `altairEpoch`.

View File

@ -73,6 +73,10 @@ type
proc (pubkey: ValidatorPubKey): Opt[ValidatorAndIndex]
{.raises: [], gcsafe.}
GetCapellaForkVersionFn* =
proc (): Opt[Version] {.raises: [], gcsafe.}
GetDenebForkEpochFn* =
proc (): Opt[Epoch] {.raises: [], gcsafe.}
GetForkFn* =
proc (epoch: Epoch): Opt[Fork] {.raises: [], gcsafe.}
GetGenesisFn* =
@ -90,6 +94,8 @@ type
defaultBuilderAddress*: Opt[string]
getValidatorAndIdxFn*: ValidatorPubKeyToDataFn
getBeaconTimeFn*: GetBeaconTimeFn
getCapellaForkVersionFn*: GetCapellaForkVersionFn
getDenebForkEpochFn*: GetDenebForkEpochFn
getForkFn*: GetForkFn
getGenesisFn*: GetGenesisFn
@ -122,6 +128,8 @@ func init*(T: type KeymanagerHost,
defaultBuilderAddress: Opt[string],
getValidatorAndIdxFn: ValidatorPubKeyToDataFn,
getBeaconTimeFn: GetBeaconTimeFn,
getCapellaForkVersionFn: GetCapellaForkVersionFn,
getDenebForkEpochFn: GetDenebForkEpochFn,
getForkFn: GetForkFn,
getGenesisFn: GetGenesisFn): T =
T(validatorPool: validatorPool,
@ -135,6 +143,8 @@ func init*(T: type KeymanagerHost,
defaultBuilderAddress: defaultBuilderAddress,
getValidatorAndIdxFn: getValidatorAndIdxFn,
getBeaconTimeFn: getBeaconTimeFn,
getCapellaForkVersionFn: getCapellaForkVersionFn,
getDenebForkEpochFn: getDenebForkEpochFn,
getForkFn: getForkFn,
getGenesisFn: getGenesisFn)

View File

@ -149,6 +149,11 @@ declareCounter validator_monitor_proposer_slashing,
declareCounter validator_monitor_attester_slashing,
"Number of attester slashings seen", labels = ["src", "validator"]
declareCounter validator_monitor_block_hit,
"Number of times a block proposed by the validator was included an epoch later", labels = ["validator"]
declareCounter validator_monitor_block_miss,
"Number of times the validator was expected to propose a block but no block was included", labels = ["validator"]
const
total = "total" # what we use for label when using "totals" mode
@ -405,12 +410,15 @@ func is_active_unslashed_in_previous_epoch(status: ParticipationInfo): bool =
ParticipationFlag.eligible in status.flags
proc registerEpochInfo*(
self: var ValidatorMonitor, epoch: Epoch, info: ForkedEpochInfo,
state: ForkyBeaconState) =
self: var ValidatorMonitor, state: ForkyBeaconState,
proposers: array[SLOTS_PER_EPOCH, Opt[ValidatorIndex]],
info: ForkedEpochInfo) =
# Register rewards, as computed during the epoch transition that lands in
# `epoch` - the rewards will be from attestations that were created at
# `epoch - 2`.
let epoch = state.slot.epoch
if epoch < 2 or self.monitors.len == 0:
return
@ -442,6 +450,24 @@ proc registerEpochInfo*(
# attestations.
continue
# Check that block proposals are sticky an epoch later
for i in 0..<SLOTS_PER_EPOCH:
let slot = prev_epoch.start_slot + i
if slot == 0:
continue
if proposers[i] == Opt.some(idx):
let hasBlock =
# When a block is missing in a slot, the beacon root repeats
get_block_root_at_slot(state, slot - 1) !=
get_block_root_at_slot(state, slot)
if hasBlock:
validator_monitor_block_hit.inc(1, [metricId])
info "Block proposal included", slot, validator = id
else:
validator_monitor_block_miss.inc(1, [metricId])
notice "Block proposal missing", slot, validator = id
let
previous_epoch_matched_source = status.is_previous_epoch_source_attester()
previous_epoch_matched_target = status.is_previous_epoch_target_attester()

View File

@ -22,6 +22,7 @@ import
export
streams, keystore, phase0, altair, tables, uri, crypto,
signatures.voluntary_exit_signature_fork,
rest_types, eth2_rest_serialization, rest_remote_signer_calls,
slashing_protection

View File

@ -19,7 +19,7 @@ const
versionMajor* = 24
versionMinor* = 2
versionBuild* = 1
versionBuild* = 2
versionBlob* = "stateofus" # Single word - ends up in the default graffiti

View File

@ -5997,7 +5997,7 @@
"x": 0,
"y": 111
},
"id": 89,
"id": 125,
"options": {
"legend": {
"calcs": [],
@ -6017,37 +6017,17 @@
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"editorMode": "code",
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_head_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"interval": "384s",
"legendFormat": "head",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_target_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"interval": "384s",
"legendFormat": "target",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_source_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"expr": "sum(rate(validator_monitor_block_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"hide": false,
"interval": "384s",
"legendFormat": "source",
"legendFormat": "miss ({{validator}})",
"range": true,
"refId": "C"
}
],
"title": "Attestation misses",
"title": "Block misses",
"type": "timeseries"
},
{
@ -6145,6 +6125,141 @@
"title": "Balance",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineStyle": {
"fill": "solid"
},
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "source"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 119
},
"id": 89,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"pluginVersion": "8.0.4",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_head_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"interval": "384s",
"legendFormat": "head",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_target_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"interval": "384s",
"legendFormat": "target",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS-PROXY}"
},
"exemplar": true,
"expr": "sum(rate(validator_monitor_prev_epoch_on_chain_source_attester_miss_total{instance=\"${instance}\",container=\"${container}\"}[$__rate_interval]))*384",
"hide": false,
"interval": "384s",
"legendFormat": "source",
"refId": "C"
}
],
"title": "Attestation misses",
"type": "timeseries"
},
{
"collapsed": true,
"datasource": {
@ -6155,7 +6270,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 119
"y": 127
},
"id": 75,
"panels": [
@ -6495,6 +6610,6 @@
"timezone": "",
"title": "NBC local testnet/sim (all nodes)",
"uid": "pgeNfj2Wz23",
"version": 15,
"version": 16,
"weekStart": ""
}

2
vendor/nim-stint vendored

@ -1 +1 @@
Subproject commit 711cda4456c32d3ba3c6c4524135b3453dffeb9c
Subproject commit e639ba700cb83a6b22e5b5a1053bea2820c8b4f6