VC: Fix forks management behavior. (#6698)

* Fix issue "ignore _VERSION configuration value if _EPOCH == FAR_FUTURE_EPOCH".
Add `OptionalForks` constant which should provide default values for _VERSION/_EPOCH.
Fix Fork schedule should update Fork's configuration with _EPOCH values.

* Fix compilation error.

* Add comment why and how `OptionalForks` should be maintained.
This commit is contained in:
Eugene Kabanov 2024-11-02 11:59:07 +02:00 committed by GitHub
parent 6cf388065d
commit 7726f39004
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 16 deletions

View File

@ -48,7 +48,8 @@ func getOrDefault*(info: VCRuntimeConfig, name: string, default: Epoch): Epoch =
func getForkVersion( func getForkVersion(
info: VCRuntimeConfig, info: VCRuntimeConfig,
consensusFork: ConsensusFork consensusFork: ConsensusFork,
optionalForks: set[ConsensusFork] = {}
): Result[Version, string] = ): Result[Version, string] =
let let
key = consensusFork.forkVersionConfigKey() key = consensusFork.forkVersionConfigKey()
@ -56,7 +57,10 @@ func getForkVersion(
try: try:
info[key] info[key]
except KeyError: except KeyError:
return err("Forks configuration missing value " & $consensusFork) if consensusFork in optionalForks:
"0xFFFFFFFF"
else:
return err("Forks configuration missing value " & $consensusFork)
var value: Version var value: Version
try: try:
hexToByteArrayStrict(stringValue, distinctBase(value)) hexToByteArrayStrict(stringValue, distinctBase(value))
@ -64,8 +68,11 @@ func getForkVersion(
return err(key & " is invalid, reason " & exc.msg) return err(key & " is invalid, reason " & exc.msg)
ok(value) ok(value)
func getForkEpoch(info: VCRuntimeConfig, func getForkEpoch(
consensusFork: ConsensusFork): Result[Epoch, string] = info: VCRuntimeConfig,
consensusFork: ConsensusFork,
optionalForks: set[ConsensusFork] = {}
): Result[Epoch, string] =
if consensusFork > ConsensusFork.Phase0: if consensusFork > ConsensusFork.Phase0:
let let
key = consensusFork.forkEpochConfigKey() key = consensusFork.forkEpochConfigKey()
@ -73,7 +80,10 @@ func getForkEpoch(info: VCRuntimeConfig,
try: try:
info[key] info[key]
except KeyError: except KeyError:
return err("Forks configuration missing value " & $consensusFork) if consensusFork in optionalForks:
"18446744073709551615"
else:
return err("Forks configuration missing value " & $consensusFork)
numValue = Base10.decode(uint64, stringValue).valueOr: numValue = Base10.decode(uint64, stringValue).valueOr:
return err(key & " is invalid, reason " & $error) return err(key & " is invalid, reason " & $error)
ok(Epoch(numValue)) ok(Epoch(numValue))
@ -84,7 +94,8 @@ template toString(epoch: Epoch): string =
Base10.toString(uint64(epoch)) Base10.toString(uint64(epoch))
func getConsensusForkConfig*( func getConsensusForkConfig*(
info: VCRuntimeConfig info: VCRuntimeConfig,
optionalForks: set[ConsensusFork] = {}
): Result[VCForkConfig, string] = ): Result[VCForkConfig, string] =
## This extracts all `_FORK_VERSION` and `_FORK_EPOCH` constants ## This extracts all `_FORK_VERSION` and `_FORK_EPOCH` constants
var var
@ -92,8 +103,8 @@ func getConsensusForkConfig*(
presence: set[ConsensusFork] presence: set[ConsensusFork]
for fork in ConsensusFork: for fork in ConsensusFork:
let let
forkVersion = ? info.getForkVersion(fork) forkVersion = ? info.getForkVersion(fork, optionalForks)
forkEpoch = ? info.getForkEpoch(fork) forkEpoch = ? info.getForkEpoch(fork, optionalForks)
config[fork] = ForkConfigItem(version: forkVersion, epoch: forkEpoch) config[fork] = ForkConfigItem(version: forkVersion, epoch: forkEpoch)
presence.incl(fork) presence.incl(fork)

View File

@ -46,6 +46,16 @@ const
ZeroTimeDiff* = TimeDiff(nanoseconds: 0'i64) ZeroTimeDiff* = TimeDiff(nanoseconds: 0'i64)
static: doAssert(high(ConsensusFork) == ConsensusFork.Electra,
"Update OptionalForks constant!")
const
OptionalForks* = {ConsensusFork.Electra}
## When a new ConsensusFork is added and before this fork is activated on
## `mainnet`, it should be part of `OptionalForks`.
## In this case, the client will ignore missing <FORKNAME>_VERSION
## and <FORKNAME>_EPOCH constants from the data reported by BN via
## `/eth/v1/config/spec` API call.
type type
ServiceState* {.pure.} = enum ServiceState* {.pure.} = enum
Initialized, Running, Error, Closing, Closed Initialized, Running, Error, Closing, Closed
@ -1487,24 +1497,25 @@ proc validateForkCompatibility(
forkConfig: VCForkConfig forkConfig: VCForkConfig
): Result[void, string] = ): Result[void, string] =
let let
item = storedConfig =
try: try:
vc.forkConfig.get()[consensusFork] vc.forkConfig.get()[consensusFork]
except KeyError: except KeyError:
raiseAssert "Fork should be present in configuration" raiseAssert "Fork should be present in configuration"
if forkVersion != item.version: if forkEpoch != storedConfig.epoch:
return err("Beacon node has conflicting " &
consensusFork.forkVersionConfigKey() & " value")
if forkEpoch != item.epoch:
if forkEpoch == FAR_FUTURE_EPOCH: if forkEpoch == FAR_FUTURE_EPOCH:
return err("Beacon node do not know about " & return err("Beacon node do not know about " &
$consensusFork & " starting epoch") $consensusFork & " starting epoch")
else: else:
if item.epoch != FAR_FUTURE_EPOCH: if storedConfig.epoch != FAR_FUTURE_EPOCH:
return err("Beacon node has conflicting " & return err("Beacon node has conflicting " &
consensusFork.forkEpochConfigKey() & " value") consensusFork.forkEpochConfigKey() & " value")
else:
if forkEpoch != FAR_FUTURE_EPOCH:
if forkVersion != storedConfig.version:
return err("Beacon node has conflicting " &
consensusFork.forkVersionConfigKey() & " value")
ok() ok()
proc updateRuntimeConfig*( proc updateRuntimeConfig*(
@ -1512,7 +1523,7 @@ proc updateRuntimeConfig*(
node: BeaconNodeServerRef, node: BeaconNodeServerRef,
info: VCRuntimeConfig info: VCRuntimeConfig
): Result[void, string] = ): Result[void, string] =
let forkConfig = ? info.getConsensusForkConfig() let forkConfig = ? info.getConsensusForkConfig(OptionalForks)
if vc.forkConfig.isNone(): if vc.forkConfig.isNone():
vc.forkConfig = Opt.some(forkConfig) vc.forkConfig = Opt.some(forkConfig)
@ -1526,11 +1537,32 @@ proc updateRuntimeConfig*(
# Save newly discovered forks. # Save newly discovered forks.
if localForkConfig[fork].epoch == FAR_FUTURE_EPOCH: if localForkConfig[fork].epoch == FAR_FUTURE_EPOCH:
localForkConfig[fork].epoch = item.epoch localForkConfig[fork].epoch = item.epoch
localForkConfig[fork].version = item.version
except KeyError: except KeyError:
raiseAssert "All the forks should be present inside forks configuration" raiseAssert "All the forks should be present inside forks configuration"
vc.forkConfig = Opt.some(localForkConfig) vc.forkConfig = Opt.some(localForkConfig)
ok() ok()
proc updateForkConfig*(vc: ValidatorClientRef) =
if vc.forkConfig.isNone():
return
var config = vc.forkConfig.get()
for fork in ConsensusFork:
let configItem =
try:
config[fork]
except KeyError:
raiseAssert "All the forks should be present inside forks configuration"
for scheduleItem in vc.forks:
if scheduleItem.current_version == configItem.version:
if configItem.epoch == FAR_FUTURE_EPOCH:
# Fork schedule knows about Fork's epoch.
config[fork] = ForkConfigItem(version: scheduleItem.current_version,
epoch: scheduleItem.epoch)
break
vc.forkConfig = Opt.some(config)
proc `+`*(slot: Slot, epochs: Epoch): Slot = proc `+`*(slot: Slot, epochs: Epoch): Slot =
slot + uint64(epochs) * SLOTS_PER_EPOCH slot + uint64(epochs) * SLOTS_PER_EPOCH

View File

@ -69,6 +69,7 @@ proc pollForFork(vc: ValidatorClientRef) {.async: (raises: [CancelledError]).} =
if (len(vc.forks) == 0) or (vc.forks != sortedForks): if (len(vc.forks) == 0) or (vc.forks != sortedForks):
vc.forks = sortedForks vc.forks = sortedForks
vc.updateForkConfig()
notice "Fork schedule updated", fork_schedule = sortedForks notice "Fork schedule updated", fork_schedule = sortedForks
vc.forksAvailable.fire() vc.forksAvailable.fire()