From ba597ef0a2d37c6baa8a5e66c249edebc9d2192e Mon Sep 17 00:00:00 2001 From: tersec Date: Sun, 25 Jun 2023 14:00:17 +0200 Subject: [PATCH] per-validator payload builder configuration (#5062) --- beacon_chain/beacon_node.nim | 36 ++++++-- beacon_chain/nimbus_beacon_node.nim | 1 + beacon_chain/nimbus_validator_client.nim | 1 + .../validators/keystore_management.nim | 92 +++++++++++++++---- 4 files changed, 103 insertions(+), 27 deletions(-) diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index eb7fcf20b..c6ed25a76 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -115,14 +115,34 @@ template rng*(node: BeaconNode): ref HmacDrbgContext = proc currentSlot*(node: BeaconNode): Slot = node.beaconClock.now.slotOrZero +func getPayloadBuilderAddress*(config: BeaconNodeConf): Opt[string] = + if config.payloadBuilderEnable: + Opt.some config.payloadBuilderUrl + else: + Opt.none(string) + proc getPayloadBuilderClient*( node: BeaconNode, validator_index: uint64): RestResult[RestClientRef] = - if node.config.payloadBuilderEnable: - # Logging done in caller - let res = RestClientRef.new(node.config.payloadBuilderUrl) - if res.isOk and res.get.isNil: - err "Got nil payload builder REST client reference" - else: - res + if not node.config.payloadBuilderEnable: + return err "Payload builder globally disabled" + + let + defaultPayloadBuilderAddress = node.config.getPayloadBuilderAddress + pubkey = withState(node.dag.headState): + 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: - err "Payload builder globally disabled" + res diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 1c463b565..01073ba46 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -689,6 +689,7 @@ proc init*(T: type BeaconNode, config.secretsDir, config.defaultFeeRecipient, config.suggestedGasLimit, + config.getPayloadBuilderAddress, getValidatorAndIdx, getBeaconTime, getForkForEpoch, diff --git a/beacon_chain/nimbus_validator_client.nim b/beacon_chain/nimbus_validator_client.nim index 27d08dbdc..6591a23df 100644 --- a/beacon_chain/nimbus_validator_client.nim +++ b/beacon_chain/nimbus_validator_client.nim @@ -336,6 +336,7 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = vc.config.secretsDir, vc.config.defaultFeeRecipient, vc.config.suggestedGasLimit, + Opt.none(string), nil, vc.beaconClock.getBeaconTimeFn, getForkForEpoch, diff --git a/beacon_chain/validators/keystore_management.nim b/beacon_chain/validators/keystore_management.nim index acbf46493..7cc20326d 100644 --- a/beacon_chain/validators/keystore_management.nim +++ b/beacon_chain/validators/keystore_management.nim @@ -8,8 +8,8 @@ {.push raises: [].} import - std/[os, strutils, terminal, wordwrap, unicode], - chronicles, chronos, json_serialization, zxcvbn, + std/[os, unicode], + chronicles, chronos, json_serialization, bearssl/rand, serialization, blscurve, eth/common/eth_types, confutils, nimbus_security_resources, @@ -21,6 +21,12 @@ import ".."/networking/network_metadata, ./validator_pool +from std/terminal import + ForegroundColor, Style, readPasswordFromStdin, getch, resetAttributes, + setForegroundColor, setStyle +from std/wordwrap import wrapWords +from zxcvbn import passwordEntropy + export keystore, validator_pool, crypto, rand @@ -32,11 +38,11 @@ when defined(windows): const KeystoreFileName* = "keystore.json" RemoteKeystoreFileName* = "remote_keystore.json" - NetKeystoreFileName* = "network_keystore.json" - FeeRecipientFilename* = "suggested_fee_recipient.hex" - GasLimitFilename* = "suggested_gas_limit.json" - KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters. - MaxKeystoreFileSize* = 65536 + FeeRecipientFilename = "suggested_fee_recipient.hex" + GasLimitFilename = "suggested_gas_limit.json" + BuilderConfigPath = "payload_builder.json" + KeyNameSize = 98 # 0x + hexadecimal key representation 96 characters. + MaxKeystoreFileSize = 65536 type WalletPathPair* = object @@ -47,9 +53,7 @@ type walletPath*: WalletPathPair seed*: KeySeed - KmResult*[T] = Result[T, cstring] - - AnyKeystore* = RemoteKeystore | Keystore + KmResult[T] = Result[T, cstring] RemoveValidatorStatus* {.pure.} = enum deleted = "Deleted" @@ -59,11 +63,11 @@ type existingArtifacts = "Keystore artifacts already exists" failed = "Validator not added" - AddValidatorFailure* = object + AddValidatorFailure = object status*: AddValidatorStatus message*: string - ImportResult*[T] = Result[T, AddValidatorFailure] + ImportResult[T] = Result[T, AddValidatorFailure] ValidatorPubKeyToDataFn* = proc (pubkey: ValidatorPubKey): Opt[ValidatorAndIndex] @@ -82,6 +86,7 @@ type secretsDir*: string defaultFeeRecipient*: Opt[Eth1Address] defaultGasLimit*: uint64 + defaultBuilderAddress*: Opt[string] getValidatorAndIdxFn*: ValidatorPubKeyToDataFn getBeaconTimeFn*: GetBeaconTimeFn getForkFn*: GetForkFn @@ -110,6 +115,7 @@ func init*(T: type KeymanagerHost, secretsDir: string, defaultFeeRecipient: Opt[Eth1Address], defaultGasLimit: uint64, + defaultBuilderAddress: Opt[string], getValidatorAndIdxFn: ValidatorPubKeyToDataFn, getBeaconTimeFn: GetBeaconTimeFn, getForkFn: GetForkFn, @@ -121,6 +127,7 @@ func init*(T: type KeymanagerHost, secretsDir: secretsDir, defaultFeeRecipient: defaultFeeRecipient, defaultGasLimit: defaultGasLimit, + defaultBuilderAddress: defaultBuilderAddress, getValidatorAndIdxFn: getValidatorAndIdxFn, getBeaconTimeFn: getBeaconTimeFn, getForkFn: getForkFn, @@ -754,14 +761,17 @@ func gasLimitPath(validatorsDir: string, pubkey: ValidatorPubKey): string = validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename +func builderConfigPath(validatorsDir: string, + pubkey: ValidatorPubKey): string = + validatorsDir.validatorKeystoreDir(pubkey) / BuilderConfigPath + proc getSuggestedFeeRecipient*( validatorsDir: string, pubkey: ValidatorPubKey, defaultFeeRecipient: Eth1Address): Result[Eth1Address, ValidatorConfigFileStatus] = # 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 - # people might prefer, however, not to override their default suggested fee - # recipients per validator, so don't warn very loudly, if at all. + # but doesn't load or parse that is more urgent. People might prefer not to + # override default suggested fee recipients per validator, so don't warn. if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)): return err noSuchValidator @@ -788,9 +798,8 @@ proc getSuggestedGasLimit*( pubkey: ValidatorPubKey, defaultGasLimit: uint64): Result[uint64, ValidatorConfigFileStatus] = # 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 - # people might prefer, however, not to override their default suggested gas - # limit per validator, so don't warn very loudly, if at all. + # but doesn't load or parse that is more urgent. People might prefer not to + # override their default suggested gas limit per validator, so don't warn. if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)): return err noSuchValidator @@ -802,7 +811,7 @@ proc getSuggestedGasLimit*( readFile(gasLimitPath), leading = false, trailing = true)) except SerializationError as e: warn "Invalid local gas limit file", gasLimitPath, - err= e.formatMsg(gasLimitPath) + err = e.formatMsg(gasLimitPath) err malformedConfigFile except CatchableError as exc: warn "Failed to load gas limit file; falling back to default gas limit", @@ -810,6 +819,46 @@ proc getSuggestedGasLimit*( err = exc.msg 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 KeystoreGenerationErrorKind* = enum FailedToCreateValidatorsDir @@ -1445,6 +1494,11 @@ proc getSuggestedGasLimit*( pubkey: ValidatorPubKey): Result[uint64, ValidatorConfigFileStatus] = host.validatorsDir.getSuggestedGasLimit(pubkey, host.defaultGasLimit) +proc getBuilderConfig*( + host: KeymanagerHost, pubkey: ValidatorPubKey): + Result[Opt[string], ValidatorConfigFileStatus] = + host.validatorsDir.getBuilderConfig(pubkey, host.defaultBuilderAddress) + proc addValidator*( host: KeymanagerHost, keystore: KeystoreData, withdrawalAddress: Opt[Eth1Address]) =