per-validator payload builder configuration (#5062)

This commit is contained in:
tersec 2023-06-25 14:00:17 +02:00 committed by GitHub
parent 614202e30d
commit ba597ef0a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 27 deletions

View File

@ -115,14 +115,34 @@ template rng*(node: BeaconNode): ref HmacDrbgContext =
proc currentSlot*(node: BeaconNode): Slot = proc currentSlot*(node: BeaconNode): Slot =
node.beaconClock.now.slotOrZero node.beaconClock.now.slotOrZero
func getPayloadBuilderAddress*(config: BeaconNodeConf): Opt[string] =
if config.payloadBuilderEnable:
Opt.some config.payloadBuilderUrl
else:
Opt.none(string)
proc getPayloadBuilderClient*( proc getPayloadBuilderClient*(
node: BeaconNode, validator_index: uint64): RestResult[RestClientRef] = node: BeaconNode, validator_index: uint64): RestResult[RestClientRef] =
if node.config.payloadBuilderEnable: if not node.config.payloadBuilderEnable:
# Logging done in caller return err "Payload builder globally disabled"
let res = RestClientRef.new(node.config.payloadBuilderUrl)
if res.isOk and res.get.isNil: let
err "Got nil payload builder REST client reference" defaultPayloadBuilderAddress = node.config.getPayloadBuilderAddress
else: pubkey = withState(node.dag.headState):
res if validator_index >= forkyState.data.validators.lenu64:
return err "Validator index too high"
forkyState.data.validators.item(validator_index).pubkey
payloadBuilderAddress =
if node.keyManagerHost.isNil:
defaultPayloadBuilderAddress
else:
node.keyManagerHost[].getBuilderConfig(pubkey).valueOr:
defaultPayloadBuilderAddress
if payloadBuilderAddress.isNone:
return err "Payload builder disabled"
let res = RestClientRef.new(payloadBuilderAddress.get)
if res.isOk and res.get.isNil:
err "Got nil payload builder REST client reference"
else: else:
err "Payload builder globally disabled" res

View File

@ -689,6 +689,7 @@ proc init*(T: type BeaconNode,
config.secretsDir, config.secretsDir,
config.defaultFeeRecipient, config.defaultFeeRecipient,
config.suggestedGasLimit, config.suggestedGasLimit,
config.getPayloadBuilderAddress,
getValidatorAndIdx, getValidatorAndIdx,
getBeaconTime, getBeaconTime,
getForkForEpoch, getForkForEpoch,

View File

@ -336,6 +336,7 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
vc.config.secretsDir, vc.config.secretsDir,
vc.config.defaultFeeRecipient, vc.config.defaultFeeRecipient,
vc.config.suggestedGasLimit, vc.config.suggestedGasLimit,
Opt.none(string),
nil, nil,
vc.beaconClock.getBeaconTimeFn, vc.beaconClock.getBeaconTimeFn,
getForkForEpoch, getForkForEpoch,

View File

@ -8,8 +8,8 @@
{.push raises: [].} {.push raises: [].}
import import
std/[os, strutils, terminal, wordwrap, unicode], std/[os, unicode],
chronicles, chronos, json_serialization, zxcvbn, chronicles, chronos, json_serialization,
bearssl/rand, bearssl/rand,
serialization, blscurve, eth/common/eth_types, confutils, serialization, blscurve, eth/common/eth_types, confutils,
nimbus_security_resources, nimbus_security_resources,
@ -21,6 +21,12 @@ import
".."/networking/network_metadata, ".."/networking/network_metadata,
./validator_pool ./validator_pool
from std/terminal import
ForegroundColor, Style, readPasswordFromStdin, getch, resetAttributes,
setForegroundColor, setStyle
from std/wordwrap import wrapWords
from zxcvbn import passwordEntropy
export export
keystore, validator_pool, crypto, rand keystore, validator_pool, crypto, rand
@ -32,11 +38,11 @@ when defined(windows):
const const
KeystoreFileName* = "keystore.json" KeystoreFileName* = "keystore.json"
RemoteKeystoreFileName* = "remote_keystore.json" RemoteKeystoreFileName* = "remote_keystore.json"
NetKeystoreFileName* = "network_keystore.json" FeeRecipientFilename = "suggested_fee_recipient.hex"
FeeRecipientFilename* = "suggested_fee_recipient.hex" GasLimitFilename = "suggested_gas_limit.json"
GasLimitFilename* = "suggested_gas_limit.json" BuilderConfigPath = "payload_builder.json"
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters. KeyNameSize = 98 # 0x + hexadecimal key representation 96 characters.
MaxKeystoreFileSize* = 65536 MaxKeystoreFileSize = 65536
type type
WalletPathPair* = object WalletPathPair* = object
@ -47,9 +53,7 @@ type
walletPath*: WalletPathPair walletPath*: WalletPathPair
seed*: KeySeed seed*: KeySeed
KmResult*[T] = Result[T, cstring] KmResult[T] = Result[T, cstring]
AnyKeystore* = RemoteKeystore | Keystore
RemoveValidatorStatus* {.pure.} = enum RemoveValidatorStatus* {.pure.} = enum
deleted = "Deleted" deleted = "Deleted"
@ -59,11 +63,11 @@ type
existingArtifacts = "Keystore artifacts already exists" existingArtifacts = "Keystore artifacts already exists"
failed = "Validator not added" failed = "Validator not added"
AddValidatorFailure* = object AddValidatorFailure = object
status*: AddValidatorStatus status*: AddValidatorStatus
message*: string message*: string
ImportResult*[T] = Result[T, AddValidatorFailure] ImportResult[T] = Result[T, AddValidatorFailure]
ValidatorPubKeyToDataFn* = ValidatorPubKeyToDataFn* =
proc (pubkey: ValidatorPubKey): Opt[ValidatorAndIndex] proc (pubkey: ValidatorPubKey): Opt[ValidatorAndIndex]
@ -82,6 +86,7 @@ type
secretsDir*: string secretsDir*: string
defaultFeeRecipient*: Opt[Eth1Address] defaultFeeRecipient*: Opt[Eth1Address]
defaultGasLimit*: uint64 defaultGasLimit*: uint64
defaultBuilderAddress*: Opt[string]
getValidatorAndIdxFn*: ValidatorPubKeyToDataFn getValidatorAndIdxFn*: ValidatorPubKeyToDataFn
getBeaconTimeFn*: GetBeaconTimeFn getBeaconTimeFn*: GetBeaconTimeFn
getForkFn*: GetForkFn getForkFn*: GetForkFn
@ -110,6 +115,7 @@ func init*(T: type KeymanagerHost,
secretsDir: string, secretsDir: string,
defaultFeeRecipient: Opt[Eth1Address], defaultFeeRecipient: Opt[Eth1Address],
defaultGasLimit: uint64, defaultGasLimit: uint64,
defaultBuilderAddress: Opt[string],
getValidatorAndIdxFn: ValidatorPubKeyToDataFn, getValidatorAndIdxFn: ValidatorPubKeyToDataFn,
getBeaconTimeFn: GetBeaconTimeFn, getBeaconTimeFn: GetBeaconTimeFn,
getForkFn: GetForkFn, getForkFn: GetForkFn,
@ -121,6 +127,7 @@ func init*(T: type KeymanagerHost,
secretsDir: secretsDir, secretsDir: secretsDir,
defaultFeeRecipient: defaultFeeRecipient, defaultFeeRecipient: defaultFeeRecipient,
defaultGasLimit: defaultGasLimit, defaultGasLimit: defaultGasLimit,
defaultBuilderAddress: defaultBuilderAddress,
getValidatorAndIdxFn: getValidatorAndIdxFn, getValidatorAndIdxFn: getValidatorAndIdxFn,
getBeaconTimeFn: getBeaconTimeFn, getBeaconTimeFn: getBeaconTimeFn,
getForkFn: getForkFn, getForkFn: getForkFn,
@ -754,14 +761,17 @@ func gasLimitPath(validatorsDir: string,
pubkey: ValidatorPubKey): string = pubkey: ValidatorPubKey): string =
validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename
func builderConfigPath(validatorsDir: string,
pubkey: ValidatorPubKey): string =
validatorsDir.validatorKeystoreDir(pubkey) / BuilderConfigPath
proc getSuggestedFeeRecipient*( proc getSuggestedFeeRecipient*(
validatorsDir: string, pubkey: ValidatorPubKey, validatorsDir: string, pubkey: ValidatorPubKey,
defaultFeeRecipient: Eth1Address): defaultFeeRecipient: Eth1Address):
Result[Eth1Address, ValidatorConfigFileStatus] = Result[Eth1Address, ValidatorConfigFileStatus] =
# In this particular case, an error might be by design. If the file exists, # In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that's a more urgent matter to fix. Many people # but doesn't load or parse that is more urgent. People might prefer not to
# people might prefer, however, not to override their default suggested fee # override default suggested fee recipients per validator, so don't warn.
# recipients per validator, so don't warn very loudly, if at all.
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)): if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
return err noSuchValidator return err noSuchValidator
@ -788,9 +798,8 @@ proc getSuggestedGasLimit*(
pubkey: ValidatorPubKey, pubkey: ValidatorPubKey,
defaultGasLimit: uint64): Result[uint64, ValidatorConfigFileStatus] = defaultGasLimit: uint64): Result[uint64, ValidatorConfigFileStatus] =
# In this particular case, an error might be by design. If the file exists, # In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that's a more urgent matter to fix. Many people # but doesn't load or parse that is more urgent. People might prefer not to
# people might prefer, however, not to override their default suggested gas # override their default suggested gas limit per validator, so don't warn.
# limit per validator, so don't warn very loudly, if at all.
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)): if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
return err noSuchValidator return err noSuchValidator
@ -802,7 +811,7 @@ proc getSuggestedGasLimit*(
readFile(gasLimitPath), leading = false, trailing = true)) readFile(gasLimitPath), leading = false, trailing = true))
except SerializationError as e: except SerializationError as e:
warn "Invalid local gas limit file", gasLimitPath, warn "Invalid local gas limit file", gasLimitPath,
err= e.formatMsg(gasLimitPath) err = e.formatMsg(gasLimitPath)
err malformedConfigFile err malformedConfigFile
except CatchableError as exc: except CatchableError as exc:
warn "Failed to load gas limit file; falling back to default gas limit", warn "Failed to load gas limit file; falling back to default gas limit",
@ -810,6 +819,46 @@ proc getSuggestedGasLimit*(
err = exc.msg err = exc.msg
err malformedConfigFile err malformedConfigFile
type
BuilderConfig = object
payloadBuilderEnable: bool
payloadBuilderUrl: string
proc getBuilderConfig*(
validatorsDir: string, pubkey: ValidatorPubKey,
defaultBuilderAddress: Opt[string]):
Result[Opt[string], ValidatorConfigFileStatus] =
# In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that is more urgent. People might prefer not to
# override default builder configs per validator, so don't warn.
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
return err noSuchValidator
let builderConfigPath = validatorsDir.builderConfigPath(pubkey)
if not fileExists(builderConfigPath):
return ok defaultBuilderAddress
let builderConfig =
try:
Json.loadFile(builderConfigPath, BuilderConfig,
requireAllFields = true)
except IOError as err:
# Any exception must be in the presence of such a file, and therefore
# an actual error worth logging
error "Failed to read payload builder configuration", err = err.msg,
path = builderConfigPath
return err malformedConfigFile
except SerializationError as err:
error "Invalid payload builder configuration",
err = err.formatMsg(builderConfigPath)
return err malformedConfigFile
ok(
if builderConfig.payloadBuilderEnable:
Opt.some builderConfig.payloadBuilderUrl
else:
Opt.none string)
type type
KeystoreGenerationErrorKind* = enum KeystoreGenerationErrorKind* = enum
FailedToCreateValidatorsDir FailedToCreateValidatorsDir
@ -1445,6 +1494,11 @@ proc getSuggestedGasLimit*(
pubkey: ValidatorPubKey): Result[uint64, ValidatorConfigFileStatus] = pubkey: ValidatorPubKey): Result[uint64, ValidatorConfigFileStatus] =
host.validatorsDir.getSuggestedGasLimit(pubkey, host.defaultGasLimit) host.validatorsDir.getSuggestedGasLimit(pubkey, host.defaultGasLimit)
proc getBuilderConfig*(
host: KeymanagerHost, pubkey: ValidatorPubKey):
Result[Opt[string], ValidatorConfigFileStatus] =
host.validatorsDir.getBuilderConfig(pubkey, host.defaultBuilderAddress)
proc addValidator*( proc addValidator*(
host: KeymanagerHost, keystore: KeystoreData, host: KeymanagerHost, keystore: KeystoreData,
withdrawalAddress: Opt[Eth1Address]) = withdrawalAddress: Opt[Eth1Address]) =