From 96cf76835e8854bee34a14b5e24e8555345f262b Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 7 Jan 2022 12:29:10 +0200 Subject: [PATCH 1/5] Keymanager API for the validator client --- AllTests-mainnet.md | 147 ++++++-- beacon_chain/beacon_node.nim | 10 +- beacon_chain/conf.nim | 36 ++ beacon_chain/filepath.nim | 1 - beacon_chain/networking/eth2_network.nim | 4 +- beacon_chain/nimbus_beacon_node.nim | 117 ++---- beacon_chain/nimbus_binary_common.nim | 95 ++++- beacon_chain/nimbus_validator_client.nim | 199 ++++++---- beacon_chain/rpc/rest_beacon_api.nim | 23 ++ beacon_chain/rpc/rest_event_api.nim | 1 + beacon_chain/rpc/rest_key_management_api.nim | 135 +++---- beacon_chain/rpc/rest_utils.nim | 35 +- .../eth2_apis/rest_remote_signer_calls.nim | 4 +- .../validator_client/attestation_service.nim | 22 +- .../validator_client/block_service.nim | 17 +- beacon_chain/validator_client/common.nim | 61 ++- .../validator_client/doppelganger_service.nim | 2 +- .../validator_client/duties_service.nim | 28 +- .../sync_committee_service.nim | 17 +- beacon_chain/validators/activity_metrics.nim | 38 ++ .../validators/keystore_management.nim | 128 ++++--- beacon_chain/validators/message_router.nim | 37 +- beacon_chain/validators/validator_duties.nim | 20 +- scripts/launch_local_testnet.sh | 7 + tests/test_keymanager_api.nim | 353 +++++++++++++----- 25 files changed, 946 insertions(+), 591 deletions(-) create mode 100644 beacon_chain/validators/activity_metrics.nim diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index c496f45c85..f691af3c31 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -109,21 +109,38 @@ OK: 2/2 Fail: 0/2 Skip: 0/2 + parent sanity OK ``` OK: 2/2 Fail: 0/2 Skip: 0/2 -## DeleteKeys requests [Preset: mainnet] +## DeleteKeys requests [Beacon Node] [Preset: mainnet] ```diff -+ Deleting not existing key [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ Deleting not existing key [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 -## DeleteRemoteKeys requests [Preset: mainnet] +## DeleteKeys requests [Validator Client] [Preset: mainnet] ```diff -+ Deleting existing local key and remote key [Preset: mainnet] OK -+ Deleting not existing key [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ Deleting not existing key [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 +## DeleteRemoteKeys requests [Beacon Node] [Preset: mainnet] +```diff ++ Deleting existing local key and remote key [Beacon Node] [Preset: mainnet] OK ++ Deleting not existing key [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK +``` +OK: 5/5 Fail: 0/5 Skip: 0/5 +## DeleteRemoteKeys requests [Validator Client] [Preset: mainnet] +```diff ++ Deleting existing local key and remote key [Validator Client] [Preset: mainnet] OK ++ Deleting not existing key [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK ``` OK: 5/5 Fail: 0/5 Skip: 0/5 ## Diverging hardforks @@ -169,15 +186,26 @@ OK: 3/3 Fail: 0/3 Skip: 0/3 + addExitMessage/getVoluntaryExitMessage OK ``` OK: 3/3 Fail: 0/3 Skip: 0/3 -## Fee recipient management [Preset: mainnet] +## Fee recipient management [Beacon Node] [Preset: mainnet] ```diff -+ Configuring the fee recpient [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK -+ Obtaining the fee recpient of a missing validator returns 404 [Preset: mainnet] OK -+ Obtaining the fee recpient of an unconfigured validator returns the suggested default [Pre OK -+ Setting the fee recipient on a missing validator creates a record for it [Preset: mainnet] OK ++ Configuring the fee recpient [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK ++ Obtaining the fee recpient of a missing validator returns 404 [Beacon Node] [Preset: mainn OK ++ Obtaining the fee recpient of an unconfigured validator returns the suggested default [Bea OK ++ Setting the fee recipient on a missing validator creates a record for it [Beacon Node] [Pr OK +``` +OK: 7/7 Fail: 0/7 Skip: 0/7 +## Fee recipient management [Validator Client] [Preset: mainnet] +```diff ++ Configuring the fee recpient [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK ++ Obtaining the fee recpient of a missing validator returns 404 [Validator Client] [Preset: OK ++ Obtaining the fee recpient of an unconfigured validator returns the suggested default [Val OK ++ Setting the fee recipient on a missing validator creates a record for it [Validator Client OK ``` OK: 7/7 Fail: 0/7 Skip: 0/7 ## FinalizedBlocks [Preset: mainnet] @@ -235,20 +263,36 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 + is_aggregator OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 -## ImportKeystores requests [Preset: mainnet] +## ImportKeystores requests [Beacon Node] [Preset: mainnet] +```diff ++ ImportKeystores/ListKeystores/DeleteKeystores [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 +## ImportKeystores requests [Validator Client] [Preset: mainnet] ```diff -+ ImportKeystores/ListKeystores/DeleteKeystores [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ ImportKeystores/ListKeystores/DeleteKeystores [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 -## ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys [Preset: mainnet] +## ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys [Beacon Node] [Preset: mainnet] ```diff -+ Importing list of remote keys [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ Importing list of remote keys [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 +## ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys [Validator Client] [Preset: mainnet] +```diff ++ Importing list of remote keys [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 ## Interop @@ -302,20 +346,36 @@ OK: 3/3 Fail: 0/3 Skip: 0/3 + Sync (Strict) [Preset: mainnet] OK ``` OK: 12/12 Fail: 0/12 Skip: 0/12 -## ListKeys requests [Preset: mainnet] +## ListKeys requests [Beacon Node] [Preset: mainnet] +```diff ++ Correct token provided [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 +## ListKeys requests [Validator Client] [Preset: mainnet] +```diff ++ Correct token provided [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 +## ListRemoteKeys requests [Beacon Node] [Preset: mainnet] ```diff -+ Correct token provided [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ Correct token provided [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK ++ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK ++ Missing Authorization header [Beacon Node] [Preset: mainnet] OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 -## ListRemoteKeys requests [Preset: mainnet] +## ListRemoteKeys requests [Validator Client] [Preset: mainnet] ```diff -+ Correct token provided [Preset: mainnet] OK -+ Invalid Authorization Header [Preset: mainnet] OK -+ Invalid Authorization Token [Preset: mainnet] OK -+ Missing Authorization header [Preset: mainnet] OK ++ Correct token provided [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Header [Validator Client] [Preset: mainnet] OK ++ Invalid Authorization Token [Validator Client] [Preset: mainnet] OK ++ Missing Authorization header [Validator Client] [Preset: mainnet] OK ``` OK: 4/4 Fail: 0/4 Skip: 0/4 ## Message signatures @@ -358,7 +418,12 @@ OK: 12/12 Fail: 0/12 Skip: 0/12 + vesion 2 single remote OK ``` OK: 3/3 Fail: 0/3 Skip: 0/3 -## Serialization/deserialization [Preset: mainnet] +## Serialization/deserialization [Beacon Node] [Preset: mainnet] +```diff ++ Deserialization test vectors OK +``` +OK: 1/1 Fail: 0/1 Skip: 0/1 +## Serialization/deserialization [Validator Client] [Preset: mainnet] ```diff + Deserialization test vectors OK ``` @@ -587,4 +652,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 OK: 9/9 Fail: 0/9 Skip: 0/9 ---TOTAL--- -OK: 328/333 Fail: 0/333 Skip: 5/333 +OK: 361/366 Fail: 0/366 Skip: 5/366 diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index f2da8d0f09..37743ff4e0 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -11,7 +11,7 @@ import std/osproc, # Nimble packages - chronos, json_rpc/servers/httpserver, presto, + chronos, json_rpc/servers/httpserver, presto, bearssl/rand, # Local modules "."/[beacon_clock, beacon_chain_db, conf, light_client], @@ -25,7 +25,8 @@ import ./spec/eth2_apis/dynamic_fee_recipients, ./sync/[optimistic_sync_light_client, sync_manager, request_manager], ./validators/[ - action_tracker, message_router, validator_monitor, validator_pool], + action_tracker, message_router, validator_monitor, validator_pool, + keystore_management], ./rpc/state_ttl_cache export @@ -68,8 +69,8 @@ type eth1Monitor*: Eth1Monitor payloadBuilderRestClient*: RestClientRef restServer*: RestServerRef + keymanagerHost*: ref KeymanagerHost keymanagerServer*: RestServerRef - keymanagerToken*: Option[string] eventBus*: EventBus vcProcess*: Process requestManager*: RequestManager @@ -102,5 +103,8 @@ template findIt*(s: openArray, predicate: untyped): int = break res +template rng*(node: BeaconNode): ref HmacDrbgContext = + node.network.rng + proc currentSlot*(node: BeaconNode): Slot = node.beaconClock.now.slotOrZero diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index c8784b3b93..2817d22457 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -807,6 +807,30 @@ type desc: "A directory containing validator keystore passwords" name: "secrets-dir" .}: Option[InputDir] + restRequestTimeout* {. + defaultValue: 0 + defaultValueDesc: "infinite" + desc: "The number of seconds to wait until complete REST request " & + "will be received" + name: "rest-request-timeout" .}: Natural + + restMaxRequestBodySize* {. + defaultValue: 16_384 + desc: "Maximum size of REST request body (kilobytes)" + name: "rest-max-body-size" .}: Natural + + restMaxRequestHeadersSize* {. + defaultValue: 64 + desc: "Maximum size of REST request headers (kilobytes)" + name: "rest-max-headers-size" .}: Natural + + # Same option as appears in Lighthouse and Prysm + # https://lighthouse-book.sigmaprime.io/suggested-fee-recipient.html + # https://github.com/prysmaticlabs/prysm/pull/10312 + suggestedFeeRecipient* {. + desc: "Suggested fee recipient" + name: "suggested-fee-recipient" .}: Option[Address] + keymanagerEnabled* {. desc: "Enable the REST keymanager API (BETA version)" defaultValue: false @@ -824,6 +848,11 @@ type defaultValueDesc: $defaultAdminListenAddressDesc name: "keymanager-address" .}: ValidIpAddress + keymanagerAllowedOrigin* {. + desc: "Limit the access to the Keymanager API to a particular hostname " & + "(for CORS-enabled clients such as browsers)" + name: "keymanager-allow-origin" .}: Option[string] + keymanagerTokenFile* {. desc: "A file specifying the authorizition token required for accessing the keymanager API" name: "keymanager-token-file" .}: Option[InputFile] @@ -1204,6 +1233,13 @@ proc loadEth2Network*( template loadEth2Network*(config: BeaconNodeConf): Eth2NetworkMetadata = loadEth2Network(config.eth2Network) +func defaultFeeRecipient*(conf: AnyConf): Eth1Address = + if conf.suggestedFeeRecipient.isSome: + conf.suggestedFeeRecipient.get + else: + # https://github.com/nim-lang/Nim/issues/19802 + (static(default(Eth1Address))) + proc loadJwtSecret*( rng: var HmacDrbgContext, dataDir: string, diff --git a/beacon_chain/filepath.nim b/beacon_chain/filepath.nim index 6a3f49547c..fb7178d77d 100644 --- a/beacon_chain/filepath.nim +++ b/beacon_chain/filepath.nim @@ -10,7 +10,6 @@ when (NimMajor, NimMinor) < (1, 4): else: {.push raises: [].} - import chronicles import stew/io2 import spec/keystore diff --git a/beacon_chain/networking/eth2_network.nim b/beacon_chain/networking/eth2_network.nim index d680cad9ce..bf171693dc 100644 --- a/beacon_chain/networking/eth2_network.nim +++ b/beacon_chain/networking/eth2_network.nim @@ -296,11 +296,9 @@ declareGauge nbc_gossipsub_good_fanout, declareGauge nbc_gossipsub_healthy_fanout, "numbers of topics with dHigh fanout" -const delayBuckets = [1.0, 5.0, 10.0, 20.0, 40.0, 60.0] - declareHistogram nbc_resolve_time, "Time(s) used while resolving peer information", - buckets = delayBuckets + buckets = [1.0, 5.0, 10.0, 20.0, 40.0, 60.0] const libp2p_pki_schemes {.strdefine.} = "" diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 9df991d224..8c13c3987e 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -98,37 +98,7 @@ type template init(T: type RpcHttpServer, ip: ValidIpAddress, port: Port): T = newRpcHttpServer([initTAddress(ip, port)]) -template init(T: type RestServerRef, - ip: ValidIpAddress, port: Port, - allowedOrigin: Option[string], - config: BeaconNodeConf): T = - let address = initTAddress(ip, port) - let serverFlags = {HttpServerFlags.QueryCommaSeparatedArray, - HttpServerFlags.NotifyDisconnect} - let - headersTimeout = - if config.restRequestTimeout == 0: - chronos.InfiniteDuration - else: - seconds(int64(config.restRequestTimeout)) - maxHeadersSize = config.restMaxRequestHeadersSize * 1024 - maxRequestBodySize = config.restMaxRequestBodySize * 1024 - let res = RestServerRef.new(getRouter(allowedOrigin), - address, serverFlags = serverFlags, - httpHeadersTimeout = headersTimeout, - maxHeadersSize = maxHeadersSize, - maxRequestBodySize = maxRequestBodySize) - if res.isErr(): - notice "Rest server could not be started", address = $address, - reason = res.error() - nil - else: - notice "Starting REST HTTP server", - url = "http://" & $ip & ":" & $port & "/" - - res.get() - -# https://github.com/ethereum/beacon-metrics/blob/master/metrics.md#interop-metrics +# https://github.com/ethereum/eth2.0-metrics/blob/master/metrics.md#interop-metrics declareGauge beacon_slot, "Latest slot of the beacon chain state" declareGauge beacon_current_epoch, "Current epoch" @@ -675,51 +645,10 @@ proc init*(T: type BeaconNode, warn "Nimbus's JSON-RPC server has been removed. This includes the --rpc, --rpc-port, and --rpc-address configuration options. https://nimbus.guide/rest-api.html shows how to enable and configure the REST Beacon API server which replaces it." let restServer = if config.restEnabled: - RestServerRef.init( - config.restAddress, - config.restPort, - config.restAllowedOrigin, - config) - else: - nil - - var keymanagerToken: Option[string] - let keymanagerServer = if config.keymanagerEnabled: - if config.keymanagerTokenFile.isNone: - echo "To enable the Keymanager API, you must also specify " & - "the --keymanager-token-file option." - quit 1 - - let - tokenFilePath = config.keymanagerTokenFile.get.string - tokenFileReadRes = readAllChars(tokenFilePath) - - if tokenFileReadRes.isErr: - fatal "Failed to read the keymanager token file", - error = $tokenFileReadRes.error - quit 1 - - keymanagerToken = some tokenFileReadRes.value.strip - if keymanagerToken.get.len == 0: - fatal "The keymanager token should not be empty", tokenFilePath - quit 1 - - if restServer != nil and - config.restAddress == config.keymanagerAddress and - config.restPort == config.keymanagerPort: - if config.keymanagerAllowedOrigin.isSome and - config.restAllowedOrigin != config.keymanagerAllowedOrigin: - fatal "Please specify a separate port for the Keymanager API " & - "if you want to restrict the origin in a different way " & - "from the Beacon API" - quit 1 - restServer - else: - RestServerRef.init( - config.keymanagerAddress, - config.keymanagerPort, - config.keymanagerAllowedOrigin, - config) + RestServerRef.init(config.restAddress, config.restPort, + config.keymanagerAllowedOrigin, + validateBeaconApiQueries, + config) else: nil @@ -743,6 +672,10 @@ proc init*(T: type BeaconNode, info "Loading slashing protection database (v2)", path = config.validatorsDir() + proc getValidatorIdx(pubkey: ValidatorPubKey): Option[ValidatorIndex] = + withState(dag.headState): + findValidator(state().data.validators.asSeq(), pubkey) + let slashingProtectionDB = SlashingProtectionDB.init( @@ -750,6 +683,19 @@ proc init*(T: type BeaconNode, config.validatorsDir(), SlashingDbName) validatorPool = newClone(ValidatorPool.init(slashingProtectionDB)) + keymanagerInitResult = initKeymanagerServer(config, restServer) + keymanagerHost = if keymanagerInitResult.server != nil: + newClone KeymanagerHost.init( + validatorPool, + rng, + keymanagerInitResult.token, + config.validatorsDir, + config.secretsDir, + config.defaultFeeRecipient, + getValidatorIdx, + getBeaconTime) + else: nil + stateTtlCache = if config.restCacheSize > 0: StateTtlCache.init( @@ -796,8 +742,8 @@ proc init*(T: type BeaconNode, eth1Monitor: eth1Monitor, payloadBuilderRestClient: payloadBuilderRestClient, restServer: restServer, - keymanagerServer: keymanagerServer, - keymanagerToken: keymanagerToken, + keymanagerHost: keymanagerHost, + keymanagerServer: keymanagerInitResult.server, eventBus: eventBus, actionTracker: ActionTracker.init(rng, config.subscribeAllSubnets), gossipState: {}, @@ -968,7 +914,7 @@ func hasSyncPubKey(node: BeaconNode, epoch: Epoch): auto = (func(pubkey: ValidatorPubKey): bool = node.syncCommitteeMsgPool.syncCommitteeSubscriptions.getOrDefault( pubkey, GENESIS_EPOCH) >= epoch or - pubkey in node.attachedValidators.validators) + pubkey in node.attachedValidators[].validators) func getCurrentSyncCommiteeSubnets(node: BeaconNode, slot: Slot): SyncnetBits = let syncCommittee = withState(node.dag.headState): @@ -1218,8 +1164,8 @@ proc onSlotEnd(node: BeaconNode, slot: Slot) {.async.} = # next slot if node.dag.needStateCachesAndForkChoicePruning(): - if node.attachedValidators.validators.len > 0: - node.attachedValidators + if node.attachedValidators[].validators.len > 0: + node.attachedValidators[] .slashingProtection # pruning is only done if the DB is set to pruning mode. .pruneAfterFinalization( @@ -1564,7 +1510,7 @@ proc stop(node: BeaconNode) = except CatchableError as exc: warn "Couldn't stop network", msg = exc.msg - node.attachedValidators.slashingProtection.close() + node.attachedValidators[].slashingProtection.close() node.attachedValidators[].close() node.db.close() notice "Databases closed" @@ -1584,12 +1530,13 @@ proc startBackfillTask(node: BeaconNode) {.async.} = proc run(node: BeaconNode) {.raises: [Defect, CatchableError].} = bnStatus = BeaconNodeStatus.Running - if not(isNil(node.restServer)): + if not isNil(node.restServer): node.restServer.installRestHandlers(node) node.restServer.start() - if not(isNil(node.keymanagerServer)): - node.keymanagerServer.router.installKeymanagerHandlers(node) + if not isNil(node.keymanagerServer): + doAssert not isNil(node.keymanagerHost) + node.keymanagerServer.router.installKeymanagerHandlers(node.keymanagerHost[]) if node.keymanagerServer != node.restServer: node.keymanagerServer.start() diff --git a/beacon_chain/nimbus_binary_common.nim b/beacon_chain/nimbus_binary_common.nim index 71caa6cd21..4557da974a 100644 --- a/beacon_chain/nimbus_binary_common.nim +++ b/beacon_chain/nimbus_binary_common.nim @@ -17,9 +17,10 @@ import std/[tables, strutils, terminal, typetraits], # Nimble packages - chronos, confutils, toml_serialization, + chronos, confutils, presto, toml_serialization, chronicles, chronicles/helpers as chroniclesHelpers, chronicles/topics_registry, stew/io2, + presto, # Local modules ./spec/[helpers], @@ -313,3 +314,95 @@ proc runSlotLoop*[T](node: T, startTime: BeaconTime, curSlot = wallSlot nextSlot = wallSlot + 1 timeToNextSlot = nextSlot.start_beacon_time() - node.beaconClock.now() + +proc init*(T: type RestServerRef, + ip: ValidIpAddress, + port: Port, + allowedOrigin: Option[string], + validateFn: PatternCallback, + config: AnyConf): T = + let address = initTAddress(ip, port) + let serverFlags = {HttpServerFlags.QueryCommaSeparatedArray, + HttpServerFlags.NotifyDisconnect} + # We increase default timeout to help validator clients who poll our server + # at least once per slot (12.seconds). + let + headersTimeout = + if config.restRequestTimeout == 0: + chronos.InfiniteDuration + else: + seconds(int64(config.restRequestTimeout)) + maxHeadersSize = config.restMaxRequestHeadersSize * 1024 + maxRequestBodySize = config.restMaxRequestBodySize * 1024 + + let res = try: + RestServerRef.new(RestRouter.init(validateFn), + address, serverFlags = serverFlags, + httpHeadersTimeout = headersTimeout, + maxHeadersSize = maxHeadersSize, + maxRequestBodySize = maxRequestBodySize) + except CatchableError as err: + notice "Rest server could not be started", address = $address, + reason = err.msg + return nil + + if res.isErr(): + notice "Rest server could not be started", address = $address, + reason = res.error() + nil + else: + notice "Starting REST HTTP server", + url = "http://" & $ip & ":" & $port & "/" + + res.get() + +type + KeymanagerInitResult* = object + server*: RestServerRef + token*: string + +proc initKeymanagerServer*( + config: AnyConf, + existingRestServer: RestServerRef = nil): KeymanagerInitResult + {.raises: [Defect].} = + + var token: string + let keymanagerServer = if config.keymanagerEnabled: + if config.keymanagerTokenFile.isNone: + echo "To enable the Keymanager API, you must also specify " & + "the --keymanager-token-file option." + quit 1 + + let + tokenFilePath = config.keymanagerTokenFile.get.string + tokenFileReadRes = readAllChars(tokenFilePath) + + if tokenFileReadRes.isErr: + fatal "Failed to read the keymanager token file", + error = $tokenFileReadRes.error + quit 1 + + token = tokenFileReadRes.value.strip + if token.len == 0: + fatal "The keymanager token should not be empty", tokenFilePath + quit 1 + + when config is BeaconNodeConf: + if existingRestServer != nil and + config.restAddress == config.keymanagerAddress and + config.restPort == config.keymanagerPort: + existingRestServer + else: + RestServerRef.init(config.keymanagerAddress, config.keymanagerPort, + config.keymanagerAllowedOrigin, + validateKeymanagerApiQueries, + config) + else: + RestServerRef.init(config.keymanagerAddress, config.keymanagerPort, + config.keymanagerAllowedOrigin, + validateKeymanagerApiQueries, + config) + else: + nil + + KeymanagerInitResult(server: keymanagerServer, token: token) diff --git a/beacon_chain/nimbus_validator_client.nim b/beacon_chain/nimbus_validator_client.nim index 856be30b61..5f385d5623 100644 --- a/beacon_chain/nimbus_validator_client.nim +++ b/beacon_chain/nimbus_validator_client.nim @@ -4,13 +4,13 @@ # * 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. -import metrics, metrics/chronos_httpserver -import validator_client/[common, fallback_service, duties_service, - attestation_service, fork_service, - sync_committee_service, doppelganger_service] - -type - ValidatorClientError* = object of CatchableError +import + stew/io2, presto, metrics, metrics/chronos_httpserver, + libp2p/crypto/crypto, + ./rpc/rest_key_management_api, + ./validator_client/[ + common, fallback_service, duties_service, fork_service, + doppelganger_service, attestation_service, sync_committee_service] proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = info "Initializing genesis", nodes_count = len(vc.beaconNodes) @@ -89,10 +89,7 @@ proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} = for keystore in listLoadableKeystores(vc.config): let pubkey = keystore.pubkey if pubkey in duplicates: - error "Duplicate validator's key found", validator_pubkey = pubkey - return false - elif keystore.kind == KeystoreKind.Remote: - info "Remote validator client was skipped", validator_pubkey = pubkey + warn "Duplicate validator key found", validator_pubkey = pubkey continue else: duplicates.add(pubkey) @@ -146,7 +143,7 @@ proc shutdownMetrics(vc: ValidatorClientRef) {.async.} = proc shutdownSlashingProtection(vc: ValidatorClientRef) = info "Closing slashing protection", path = vc.config.validatorsDir() - vc.attachedValidators.slashingProtection.close() + vc.attachedValidators[].slashingProtection.close() proc onSlotStart(vc: ValidatorClientRef, wallTime: BeaconTime, lastSlot: Slot): Future[bool] {.async.} = @@ -178,7 +175,58 @@ proc onSlotStart(vc: ValidatorClientRef, wallTime: BeaconTime, return false -proc asyncInit(vc: ValidatorClientRef) {.async.} = +proc new*(T: type ValidatorClientRef, + config: ValidatorClientConf, + rng: ref HmacDrbgContext): ValidatorClientRef = + let beaconNodes = + block: + var servers: seq[BeaconNodeServerRef] + let flags = {RestClientFlag.CommaSeparatedArray} + for url in config.beaconNodes: + let res = RestClientRef.new(url, flags = flags) + if res.isErr(): + warn "Unable to resolve remote beacon node server's hostname", + url = url + else: + servers.add(BeaconNodeServerRef(client: res.get(), endpoint: url)) + servers + + if len(beaconNodes) == 0: + # This should not happen, thanks to defaults in `conf.nim` + fatal "Not enough beacon nodes in command line" + quit 1 + + when declared(waitSignal): + ValidatorClientRef( + rng: rng, + config: config, + beaconNodes: beaconNodes, + graffitiBytes: config.graffiti.get(defaultGraffitiBytes()), + nodesAvailable: newAsyncEvent(), + forksAvailable: newAsyncEvent(), + gracefulExit: newAsyncEvent(), + sigintHandleFut: waitSignal(SIGINT), + sigtermHandleFut: waitSignal(SIGTERM) + ) + else: + ValidatorClientRef( + rng: rng, + config: config, + beaconNodes: beaconNodes, + graffitiBytes: config.graffiti.get(defaultGraffitiBytes()), + nodesAvailable: newAsyncEvent(), + forksAvailable: newAsyncEvent(), + gracefulExit: newAsyncEvent(), + sigintHandleFut: newFuture[void]("sigint_placeholder"), + sigtermHandleFut: newFuture[void]("sigterm_placeholder") + ) + +proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = + notice "Launching validator client", version = fullVersionStr, + cmdParams = commandLineParams(), + config = vc.config, + beacon_nodes_count = len(vc.beaconNodes) + vc.beaconGenesis = await vc.initGenesis() info "Genesis information", genesis_time = vc.beaconGenesis.genesis_time, genesis_fork_version = vc.beaconGenesis.genesis_fork_version, @@ -190,22 +238,24 @@ proc asyncInit(vc: ValidatorClientRef) {.async.} = raise newException(ValidatorClientError, "Could not initialize metrics server") - try: - if not(await initValidators(vc)): - await vc.shutdownMetrics() - raise newException(ValidatorClientError, - "Could not initialize local validators") - except CancelledError: - debug "Initialization process interrupted" + info "Initializing slashing protection", path = vc.config.validatorsDir() + + let + slashingProtectionDB = + SlashingProtectionDB.init( + vc.beaconGenesis.genesis_validators_root, + vc.config.validatorsDir(), "slashing_protection") + validatorPool = newClone(ValidatorPool.init(slashingProtectionDB)) + + vc.attachedValidators = validatorPool + + if not(await initValidators(vc)): await vc.shutdownMetrics() - return + raise newException(ValidatorClientError, + "Could not initialize local validators") - info "Initializing slashing protection", path = vc.config.validatorsDir() - vc.attachedValidators.slashingProtection = - SlashingProtectionDB.init( - vc.beaconGenesis.genesis_validators_root, - vc.config.validatorsDir(), "slashing_protection" - ) + let + keymanagerInitResult = initKeymanagerServer(vc.config, nil) try: vc.fallbackService = await FallbackServiceRef.init(vc) @@ -214,6 +264,21 @@ proc asyncInit(vc: ValidatorClientRef) {.async.} = vc.doppelgangerService = await DoppelgangerServiceRef.init(vc) vc.attestationService = await AttestationServiceRef.init(vc) vc.syncCommitteeService = await SyncCommitteeServiceRef.init(vc) + vc.keymanagerServer = keymanagerInitResult.server + if vc.keymanagerServer != nil: + func getValidatorIdx(pubkey: ValidatorPubKey): Option[ValidatorIndex] = + none ValidatorIndex + + vc.keymanagerHost = newClone KeymanagerHost.init( + validatorPool, + vc.rng, + keymanagerInitResult.token, + vc.config.validatorsDir, + vc.config.secretsDir, + vc.config.defaultFeeRecipient, + getValidatorIdx, + vc.beaconClock.getBeaconTimeFn) + except CatchableError as exc: warn "Unexpected error encountered while initializing", error_name = exc.name, error_msg = exc.msg @@ -225,7 +290,9 @@ proc asyncInit(vc: ValidatorClientRef) {.async.} = vc.shutdownSlashingProtection() return -proc asyncRun(vc: ValidatorClientRef) {.async.} = + return vc + +proc asyncRun*(vc: ValidatorClientRef) {.async.} = vc.fallbackService.start() vc.forkService.start() vc.dutiesService.start() @@ -233,6 +300,11 @@ proc asyncRun(vc: ValidatorClientRef) {.async.} = vc.attestationService.start() vc.syncCommitteeService.start() + if not isNil(vc.keymanagerServer): + doAssert vc.keymanagerHost != nil + vc.keymanagerServer.router.installKeymanagerHandlers(vc.keymanagerHost[]) + vc.keymanagerServer.start() + var exitEventFut = vc.gracefulExit.wait() try: vc.runSlotLoopFut = runSlotLoop(vc, vc.beaconClock.now(), onSlotStart) @@ -260,6 +332,9 @@ proc asyncRun(vc: ValidatorClientRef) {.async.} = pending.add(vc.doppelgangerService.stop()) pending.add(vc.attestationService.stop()) pending.add(vc.syncCommitteeService.stop()) + if not isNil(vc.keymanagerServer): + pending.add(vc.keymanagerServer.stop()) + await allFutures(pending) template runWithSignals(vc: ValidatorClientRef, body: untyped): bool = @@ -290,64 +365,22 @@ template runWithSignals(vc: ValidatorClientRef, body: untyped): bool = await allFutures(pending) false -proc asyncLoop*(vc: ValidatorClientRef) {.async.} = - if not(vc.runWithSignals(asyncInit(vc))): +proc runValidatorClient*(config: ValidatorClientConf, + rng: ref HmacDrbgContext) {.async.} = + let vc = ValidatorClientRef.new(config, rng) + if not vc.runWithSignals(asyncInit vc): return - if not(vc.runWithSignals(asyncRun(vc))): + if not vc.runWithSignals(asyncRun vc): return programMain: - let config = makeBannerAndConfig("Nimbus validator client " & fullVersionStr, - ValidatorClientConf) - - setupLogging(config.logLevel, config.logStdout, config.logFile) - - let beaconNodes = - block: - var servers: seq[BeaconNodeServerRef] - let flags = {RestClientFlag.CommaSeparatedArray} - for url in config.beaconNodes: - let res = RestClientRef.new(url, flags = flags) - if res.isErr(): - warn "Unable to resolve remote beacon node server's hostname", - url = url - else: - servers.add(BeaconNodeServerRef(client: res.get(), endpoint: url)) - servers + let + config = makeBannerAndConfig("Nimbus validator client " & fullVersionStr, + ValidatorClientConf) - if len(beaconNodes) == 0: - # This should not happen, thanks to defaults in `conf.nim` - fatal "Not enough beacon nodes in command line" - quit 1 + # Single RNG instance for the application - will be seeded on construction + # and avoid using system resources (such as urandom) after that + rng = crypto.newRng() - notice "Launching validator client", version = fullVersionStr, - cmdParams = commandLineParams(), - config, - beacon_nodes_count = len(beaconNodes) - - var vc = - when declared(waitSignal): - ValidatorClientRef( - config: config, - beaconNodes: beaconNodes, - graffitiBytes: config.graffiti.get(defaultGraffitiBytes()), - nodesAvailable: newAsyncEvent(), - forksAvailable: newAsyncEvent(), - gracefulExit: newAsyncEvent(), - sigintHandleFut: waitSignal(SIGINT), - sigtermHandleFut: waitSignal(SIGTERM) - ) - else: - ValidatorClientRef( - config: config, - beaconNodes: beaconNodes, - graffitiBytes: config.graffiti.get(defaultGraffitiBytes()), - nodesAvailable: newAsyncEvent(), - forksAvailable: newAsyncEvent(), - gracefulExit: newAsyncEvent(), - sigintHandleFut: newFuture[void]("sigint_placeholder"), - sigtermHandleFut: newFuture[void]("sigterm_placeholder") - ) - - waitFor asyncLoop(vc) - info "Validator client stopped" + setupLogging(config.logLevel, config.logStdout, config.logFile) + waitFor runValidatorClient(config, rng) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 6d7f7b1d2f..48b3fede59 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -20,6 +20,29 @@ export rest_utils logScope: topics = "rest_beaconapi" +proc validateBeaconApiQueries*(key: string, value: string): int = + ## This is rough validation procedure which should be simple and fast, + ## because it will be used for query routing. + case key + of "{epoch}": + 0 + of "{slot}": + 0 + of "{peer_id}": + 0 + of "{state_id}": + 0 + of "{block_id}": + 0 + of "{validator_id}": + 0 + of "{block_root}": + 0 + of "{pubkey}": + int(value.len != 98) + else: + 1 + proc validateFilter(filters: seq[ValidatorFilter]): Result[ValidatorFilter, cstring] = var res: ValidatorFilter diff --git a/beacon_chain/rpc/rest_event_api.nim b/beacon_chain/rpc/rest_event_api.nim index 905d9ae141..18c74b8529 100644 --- a/beacon_chain/rpc/rest_event_api.nim +++ b/beacon_chain/rpc/rest_event_api.nim @@ -102,6 +102,7 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) = res.get() let res = preferredContentType(textEventStreamMediaType) + if res.isErr(): return RestApiResponse.jsonError(Http406, ContentNotAcceptableError) if res.get() != textEventStreamMediaType: diff --git a/beacon_chain/rpc/rest_key_management_api.nim b/beacon_chain/rpc/rest_key_management_api.nim index 7f679a477e..8b5573f294 100644 --- a/beacon_chain/rpc/rest_key_management_api.nim +++ b/beacon_chain/rpc/rest_key_management_api.nim @@ -7,19 +7,22 @@ import std/[tables, os, strutils, uri] import chronos, chronicles, confutils, stew/[base10, results, io2], blscurve -import ".."/validators/slashing_protection -import ".."/[conf, filepath, beacon_node] import ".."/spec/[keystore, crypto] -import ".."/rpc/rest_utils -import ".."/validators/[keystore_management, validator_pool, validator_duties] import ".."/spec/eth2_apis/rest_keymanager_types +import ".."/validators/[slashing_protection, keystore_management, + validator_pool, validator_duties] +import ".."/rpc/rest_utils export rest_utils, results -proc listLocalValidators*(node: BeaconNode): seq[KeystoreInfo] +func validateKeymanagerApiQueries*(key: string, value: string): int = + # There are no queries to validate + return 0 + +proc listLocalValidators*(validatorPool: ValidatorPool): seq[KeystoreInfo] {.raises: [Defect].} = var validators: seq[KeystoreInfo] - for item in node.attachedValidators[].items(): + for item in validatorPool: if item.kind == ValidatorKind.Local: validators.add KeystoreInfo( validating_pubkey: item.pubkey, @@ -28,10 +31,10 @@ proc listLocalValidators*(node: BeaconNode): seq[KeystoreInfo] ) validators -proc listRemoteValidators*(node: BeaconNode): seq[RemoteKeystoreInfo] +proc listRemoteValidators*(validatorPool: ValidatorPool): seq[RemoteKeystoreInfo] {.raises: [Defect].} = var validators: seq[RemoteKeystoreInfo] - for item in node.attachedValidators[].items(): + for item in validatorPool: if item.kind == ValidatorKind.Remote and item.data.remotes.len == 1: validators.add RemoteKeystoreInfo( pubkey: item.pubkey, @@ -39,10 +42,10 @@ proc listRemoteValidators*(node: BeaconNode): seq[RemoteKeystoreInfo] ) validators -proc listRemoteDistributedValidators*(node: BeaconNode): seq[DistributedKeystoreInfo] +proc listRemoteDistributedValidators*(validatorPool: ValidatorPool): seq[DistributedKeystoreInfo] {.raises: [Defect].} = var validators: seq[DistributedKeystoreInfo] - for item in node.attachedValidators[].items(): + for item in validatorPool: if item.kind == ValidatorKind.Remote and item.data.remotes.len > 1: validators.add DistributedKeystoreInfo( pubkey: item.pubkey, @@ -69,13 +72,13 @@ proc keymanagerApiError(status: HttpCode, msg: string): RestApiResponse = RestApiResponse.error(status, data, "application/json") proc checkAuthorization*(request: HttpRequestRef, - node: BeaconNode): Result[void, AuthorizationError] = + host: KeymanagerHost): Result[void, AuthorizationError] = let authorizations = request.headers.getList("authorization") if authorizations.len > 0: for authHeader in authorizations: let parts = authHeader.split(' ', maxsplit = 1) if parts.len == 2 and parts[0] == "Bearer": - if parts[1] == node.keymanagerToken.get: + if parts[1] == host.keymanagerToken: return ok() else: return err incorrectToken @@ -100,9 +103,10 @@ proc validateUri*(url: string): Result[Uri, cstring] = return err("Empty URL hostname") ok(surl) -proc removeValidator(node: BeaconNode, - key: ValidatorPubKey): RemoteKeystoreStatus = - let res = removeValidator(node.attachedValidators[], node.config, +proc handleRemoveValidatorReq(host: KeymanagerHost, + key: ValidatorPubKey): RemoteKeystoreStatus = + let res = removeValidator(host.validatorPool[], + host.validatorsDir, host.secretsDir, key, KeystoreKind.Remote) if res.isOk: case res.value() @@ -114,33 +118,37 @@ proc removeValidator(node: BeaconNode, return RemoteKeystoreStatus(status: KeystoreStatus.error, message: some($res.error())) -proc addRemoteValidator(node: BeaconNode, - keystore: RemoteKeystore): RequestItemStatus = - let res = importKeystore(node.attachedValidators[], node.config, keystore) - if res.isErr(): +proc handleAddRemoteValidatorReq(host: KeymanagerHost, + keystore: RemoteKeystore): RequestItemStatus = + let res = importKeystore(host.validatorPool[], host.validatorsDir, keystore) + if res.isOk: + let + slot = host.getBeaconTimeFn().slotOrZero + validatorIdx = host.getValidatorIdx(keystore.pubkey) + host.validatorPool[].addRemoteValidator(validatorIdx, res.get, slot) + RequestItemStatus(status: $KeystoreStatus.imported) + else: case res.error().status of AddValidatorStatus.failed: - return RequestItemStatus(status: $KeystoreStatus.error, - message: $res.error().message) + RequestItemStatus(status: $KeystoreStatus.error, + message: $res.error().message) of AddValidatorStatus.existingArtifacts: - return RequestItemStatus(status: $KeystoreStatus.duplicate) - else: - node.addRemoteValidators([res.get()]) - return RequestItemStatus(status: $KeystoreStatus.imported) + RequestItemStatus(status: $KeystoreStatus.duplicate) -proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = +proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = # https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error - let response = GetKeystoresResponse(data: listLocalValidators(node)) + let response = GetKeystoresResponse( + data: listLocalValidators(host.validatorPool[])) return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores router.api(MethodPost, "/api/eth/v1/keystores") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let request = @@ -154,13 +162,13 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = if request.slashing_protection.isSome(): let slashing_protection = request.slashing_protection.get() - let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection) + let nodeSPDIR = toSPDIR(host.validatorPool[].slashingProtection) if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest != slashing_protection.metadata.genesis_validators_root.Eth2Digest: return keymanagerApiError(Http400, "The slashing protection database and imported file refer to " & "different blockchains.") - let res = inclSPDIR(node.attachedValidators.slashingProtection, + let res = inclSPDIR(host.validatorPool[].slashingProtection, slashing_protection) if res == siFailure: return keymanagerApiError(Http500, @@ -169,8 +177,9 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = var response: PostKeystoresResponse for index, item in request.keystores: - let res = importKeystore(node.attachedValidators[], node.network.rng[], - node.config, item, request.passwords[index]) + let res = importKeystore(host.validatorPool[], host.rng[], + host.validatorsDir, host.secretsDir, + item, request.passwords[index]) if res.isErr(): let failure = res.error() case failure.status @@ -182,7 +191,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = response.data.add( RequestItemStatus(status: $KeystoreStatus.duplicate)) else: - node.addLocalValidators([res.get()]) + host.addLocalValidator(res.get()) response.data.add( RequestItemStatus(status: $KeystoreStatus.imported)) @@ -191,7 +200,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = # https://ethereum.github.io/keymanager-APIs/#/Keymanager/DeleteKeys router.api(MethodDelete, "/api/eth/v1/keystores") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let keys = @@ -205,7 +214,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = var response: DeleteKeystoresResponse - nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection) + nodeSPDIR = toSPDIR(host.validatorPool[].slashingProtection) # Hash table to keep the removal status of all keys form request keysAndDeleteStatus = initTable[PubKeyBytes, RequestItemStatus]() @@ -213,8 +222,9 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = for index, key in keys: let - res = removeValidator(node.attachedValidators[], node.config, key, - KeystoreKind.Local) + res = removeValidator(host.validatorPool[], + host.validatorsDir, host.secretsDir, + key, KeystoreKind.Local) pubkey = key.blob.PubKey0x.PubKeyBytes if res.isOk: @@ -237,11 +247,12 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = # found, this means the validator was active in the past, so we must # respond with `not_active`: for validator in nodeSPDIR.data: - keysAndDeleteStatus.withValue(validator.pubkey.PubKeyBytes, value) do: + keysAndDeleteStatus.withValue(validator.pubkey.PubKeyBytes, + foundKeystore) do: response.slashing_protection.data.add(validator) - if value.status == $KeystoreStatus.notFound: - value.status = $KeystoreStatus.notActive + if foundKeystore.status == $KeystoreStatus.notFound: + foundKeystore.status = $KeystoreStatus.notActive for index, key in keys: response.data.add(keysAndDeleteStatus[key.blob.PubKey0x.PubKeyBytes]) @@ -250,16 +261,17 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys router.api(MethodGet, "/api/eth/v1/remotekeys") do () -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error - let response = GetRemoteKeystoresResponse(data: listRemoteValidators(node)) + let response = GetRemoteKeystoresResponse( + data: listRemoteValidators(host.validatorPool[])) return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ImportRemoteKeys router.api(MethodPost, "/api/eth/v1/remotekeys") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let keys = @@ -282,16 +294,15 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = keystore = RemoteKeystore( version: 1'u64, remoteType: RemoteSignerType.Web3Signer, pubkey: key.pubkey, remotes: @[remoteInfo]) - status = node.addRemoteValidator(keystore) - response.data.add(status) + response.data.add handleAddRemoteValidatorReq(host, keystore) return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys router.api(MethodDelete, "/api/eth/v1/remotekeys") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let keys = @@ -305,20 +316,19 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = var response: DeleteRemoteKeystoresResponse for index, key in keys: - let status = node.removeValidator(key) - response.data.add(status) + response.data.add handleRemoveValidatorReq(host, key) return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/ListFeeRecipient router.api(MethodGet, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let pubkey = pubkey.valueOr: return keymanagerApiError(Http400, InvalidValidatorPublicKey) - ethaddress = node.config.getSuggestedFeeRecipient(pubkey) + ethaddress = host.getSuggestedFeeRecipient(pubkey) return if ethaddress.isOk: RestApiResponse.jsonResponse(ListFeeRecipientResponse( @@ -335,7 +345,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = router.api(MethodPost, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey, contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let @@ -350,7 +360,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = return keymanagerApiError(Http400, InvalidFeeRecipientRequestError) dres.get() - status = node.config.setFeeRecipient(pubkey, feeRecipientReq.ethaddress) + status = host.setFeeRecipient(pubkey, feeRecipientReq.ethaddress) return if status.isOk: RestApiResponse.response("", Http202, "text/plain") @@ -361,13 +371,13 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = # https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/DeleteFeeRecipient router.api(MethodDelete, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let pubkey = pubkey.valueOr: return keymanagerApiError(Http400, InvalidValidatorPublicKey) - res = removeFeeRecipientFile(node.config, pubkey) + res = host.removeFeeRecipientFile(pubkey) return if res.isOk: RestApiResponse.response("", Http204, "text/plain") @@ -378,17 +388,18 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = # TODO: These URLs will be changed once we submit a proposal for # /api/eth/v2/remotekeys that supports distributed keys. router.api(MethodGet, "/api/eth/v1/remotekeys/distributed") do () -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error - let response = GetDistributedKeystoresResponse(data: listRemoteDistributedValidators(node)) + let response = GetDistributedKeystoresResponse( + data: listRemoteDistributedValidators(host.validatorPool[])) return RestApiResponse.jsonResponsePlain(response) # TODO: These URLs will be changed once we submit a proposal for # /api/eth/v2/remotekeys that supports distributed keys. router.api(MethodPost, "/api/eth/v1/remotekeys/distributed") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let keys = @@ -410,14 +421,13 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = remotes: key.remotes, threshold: uint32 key.threshold ) - let status = node.addRemoteValidator(keystore) - response.data.add(status) + response.data.add handleAddRemoteValidatorReq(host, keystore) return RestApiResponse.jsonResponsePlain(response) router.api(MethodDelete, "/api/eth/v1/remotekeys/distributed") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let authStatus = checkAuthorization(request, node) + let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error let keys = @@ -431,8 +441,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) = var response: DeleteRemoteKeystoresResponse for index, key in keys: - let status = node.removeValidator(key) - response.data.add(status) + response.data.add handleRemoveValidatorReq(host, key) return RestApiResponse.jsonResponsePlain(response) diff --git a/beacon_chain/rpc/rest_utils.nim b/beacon_chain/rpc/rest_utils.nim index 845d27608f..f2d43fbfa2 100644 --- a/beacon_chain/rpc/rest_utils.nim +++ b/beacon_chain/rpc/rest_utils.nim @@ -14,9 +14,9 @@ import std/[options, macros], stew/byteutils, presto, ../spec/[forks], ../spec/eth2_apis/[rest_types, eth2_rest_serialization], - ../beacon_node, ../validators/validator_duties, ../consensus_object_pools/blockchain_dag, + ../beacon_node, "."/[rest_constants, state_ttl_cache] export @@ -33,29 +33,6 @@ func match(data: openArray[char], charset: set[char]): int = return 1 0 -proc validate(key: string, value: string): int = - ## This is rough validation procedure which should be simple and fast, - ## because it will be used for query routing. - case key - of "{epoch}": - 0 - of "{slot}": - 0 - of "{peer_id}": - 0 - of "{state_id}": - 0 - of "{block_id}": - 0 - of "{validator_id}": - 0 - of "{block_root}": - 0 - of "{pubkey}": - int(value.len != 98) - else: - 1 - proc getSyncedHead*(node: BeaconNode, slot: Slot): Result[BlockRef, cstring] = let head = node.dag.head @@ -64,6 +41,13 @@ proc getSyncedHead*(node: BeaconNode, slot: Slot): Result[BlockRef, cstring] = ok(head) +func getCurrentSlot*(node: BeaconNode, slot: Slot): + Result[Slot, cstring] = + if slot <= (node.dag.head.slot + (SLOTS_PER_EPOCH * 2)): + ok(slot) + else: + err("Requesting slot too far ahead of the current head") + proc getSyncedHead*(node: BeaconNode, epoch: Epoch): Result[BlockRef, cstring] = if epoch > MaxEpoch: @@ -274,9 +258,6 @@ func keysToIndices*(cacheTable: var Table[ValidatorPubKey, ValidatorIndex], indices[listIndex[]] = some(ValidatorIndex(validatorIndex)) indices -proc getRouter*(allowedOrigin: Option[string]): RestRouter = - RestRouter.init(validate, allowedOrigin = allowedOrigin) - proc getStateOptimistic*(node: BeaconNode, state: ForkedHashedBeaconState): Option[bool] = if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH: diff --git a/beacon_chain/spec/eth2_apis/rest_remote_signer_calls.nim b/beacon_chain/spec/eth2_apis/rest_remote_signer_calls.nim index 1ab06356e7..6380517f51 100644 --- a/beacon_chain/spec/eth2_apis/rest_remote_signer_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_remote_signer_calls.nim @@ -53,11 +53,9 @@ declareCounter nbc_remote_signer_unknown_responses, declareCounter nbc_remote_signer_communication_errors, "Number of communication errors" -const delayBuckets = [0.050, 0.100, 0.500, 1.0, 5.0, 10.0] - declareHistogram nbc_remote_signer_time, "Time(s) used to generate signature usign remote signer", - buckets = delayBuckets + buckets = [0.050, 0.100, 0.500, 1.0, 5.0, 10.0] proc getUpcheck*(): RestResponse[Web3SignerStatusResponse] {. rest, endpoint: "/upcheck", diff --git a/beacon_chain/validator_client/attestation_service.nim b/beacon_chain/validator_client/attestation_service.nim index 58bee37cf5..5a9502d2c6 100644 --- a/beacon_chain/validator_client/attestation_service.nim +++ b/beacon_chain/validator_client/attestation_service.nim @@ -5,25 +5,17 @@ # * 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. -import std/sets -import metrics, chronicles -import "."/[common, api, block_service] +import + std/sets, + chronicles, + ../validators/activity_metrics, + "."/[common, api, block_service] const ServiceName = "attestation_service" logScope: service = ServiceName -declareCounter beacon_attestations_sent, - "Number of attestations sent by the node" - -declareCounter beacon_aggregates_sent, - "Number of beacon chain attestations sent by the node" - -declareHistogram beacon_attestation_sent_delay, - "Time(s) between expected and actual attestation send moment", - buckets = DelayBuckets - type AggregateItem* = object aggregator_index: uint64 @@ -57,7 +49,7 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData, fork, vc.beaconGenesis.genesis_validators_root, adata) let attestationRoot = adata.hash_tree_root() - let notSlashable = vc.attachedValidators.slashingProtection + let notSlashable = vc.attachedValidators[].slashingProtection .registerAttestation(vindex, validator.pubkey, adata.source.epoch, adata.target.epoch, signingRoot) @@ -286,7 +278,7 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, block: var res: seq[AggregateItem] for duty in duties: - let validator = vc.attachedValidators.getValidator(duty.data.pubkey) + let validator = vc.attachedValidators[].getValidator(duty.data.pubkey) if not(isNil(validator)): if (duty.data.slot != slot) or (duty.data.committee_index != committeeIndex): diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index 0bf5bd628e..d1cd2071b9 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -5,19 +5,14 @@ # * 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. -import ".."/spec/forks -import common, api -import chronicles, metrics +import + chronicles, + ".."/validators/activity_metrics, + ".."/spec/forks, + common, api logScope: service = "block_service" -declareCounter beacon_blocks_sent, - "Number of beacon blocks sent by this node" - -declareHistogram beacon_blocks_sent_delay, - "Time(s) between expected and actual block send moment", - buckets = DelayBuckets - proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, validator: AttachedValidator) {.async.} = let @@ -77,7 +72,7 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, # TODO: signing_root is recomputed in getBlockSignature just after let signing_root = compute_block_signing_root(fork, genesisRoot, slot, blockRoot) - let notSlashable = vc.attachedValidators + let notSlashable = vc.attachedValidators[] .slashingProtection .registerBlock(ValidatorIndex(beaconBlock.proposer_index), validator.pubkey, slot, signing_root) diff --git a/beacon_chain/validator_client/common.nim b/beacon_chain/validator_client/common.nim index ca7fde0aa2..46645e6410 100644 --- a/beacon_chain/validator_client/common.nim +++ b/beacon_chain/validator_client/common.nim @@ -5,30 +5,24 @@ # * 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. -import std/[tables, os, sets, sequtils] -import chronos, presto, presto/client as presto_client, chronicles, confutils, - json_serialization/std/[options, net], - stew/[base10, results, byteutils] -import metrics, metrics/chronos_httpserver - -# Local modules import - ../spec/datatypes/[phase0, altair], - ../spec/[eth2_merkleization, helpers, signatures, - validator], - ../spec/eth2_apis/[eth2_rest_serialization, rest_beacon_client], - ../validators/[keystore_management, validator_pool, slashing_protection], - ".."/[conf, beacon_clock, version, nimbus_binary_common], - ../spec/eth2_apis/eth2_rest_serialization - -export os, sets, sequtils, sequtils, chronos, presto, chronicles, confutils, - nimbus_binary_common, version, conf, options, tables, results, base10, - byteutils, presto_client - -export eth2_rest_serialization, rest_beacon_client, - phase0, altair, helpers, signatures, validator, eth2_merkleization, - beacon_clock, - keystore_management, slashing_protection, validator_pool + std/[tables, os, sets, sequtils], + stew/[base10, results, byteutils], + bearssl/rand, chronos, presto, presto/client as presto_client, + chronicles, confutils, json_serialization/std/[options, net], + metrics, metrics/chronos_httpserver, + ".."/spec/datatypes/[phase0, altair], + ".."/spec/[eth2_merkleization, helpers, signatures, validator], + ".."/spec/eth2_apis/[eth2_rest_serialization, rest_beacon_client], + ".."/validators/[keystore_management, validator_pool, slashing_protection], + ".."/[conf, beacon_clock, version, nimbus_binary_common] + +export + os, sets, sequtils, chronos, presto, chronicles, confutils, + nimbus_binary_common, version, conf, options, tables, results, base10, + byteutils, presto_client, eth2_rest_serialization, rest_beacon_client, + phase0, altair, helpers, signatures, validator, eth2_merkleization, + beacon_clock, keystore_management, slashing_protection, validator_pool const SYNC_TOLERANCE* = 4'u64 @@ -154,9 +148,11 @@ type runSlotLoopFut*: Future[void] sigintHandleFut*: Future[void] sigtermHandleFut*: Future[void] + keymanagerHost*: ref KeymanagerHost + keymanagerServer*: RestServerRef beaconClock*: BeaconClock - attachedValidators*: ValidatorPool doppelgangerDetection*: DoppelgangerDetection + attachedValidators*: ref ValidatorPool forks*: seq[Fork] forksAvailable*: AsyncEvent nodesAvailable*: AsyncEvent @@ -166,6 +162,7 @@ type syncCommitteeDuties*: SyncCommitteeDutiesMap beaconGenesis*: RestGenesis proposerTasks*: Table[Slot, seq[ProposerTask]] + rng*: ref HmacDrbgContext ValidatorClientRef* = ref ValidatorClient @@ -306,7 +303,7 @@ proc getDurationToNextBlock*(vc: ValidatorClientRef, slot: Slot): string = let data = vc.proposers.getOrDefault(epoch) if not(data.isDefault()): for item in data.duties: - if item.duty.pubkey in vc.attachedValidators: + if item.duty.pubkey in vc.attachedValidators[]: if (item.duty.slot < minSlot) and (item.duty.slot >= slot): minSlot = item.duty.slot if minSlot != FAR_FUTURE_SLOT: @@ -348,7 +345,7 @@ proc getDelay*(vc: ValidatorClientRef, deadline: BeaconTime): TimeDiff = proc getValidator*(vc: ValidatorClientRef, key: ValidatorPubKey): Option[AttachedValidator] = - let validator = vc.attachedValidators.getValidator(key) + let validator = vc.attachedValidators[].getValidator(key) if isNil(validator): warn "Validator not in pool anymore", validator = shortLog(validator) none[AttachedValidator]() @@ -419,8 +416,8 @@ proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) = let slot = vc.currentSlot() case keystore.kind of KeystoreKind.Local: - vc.attachedValidators.addLocalValidator(keystore, none[ValidatorIndex](), - slot) + vc.attachedValidators[].addLocalValidator(keystore, none[ValidatorIndex](), + slot) of KeystoreKind.Remote: let httpFlags = @@ -444,15 +441,15 @@ proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) = res.add((client.get(), remote)) res if len(clients) > 0: - vc.attachedValidators.addRemoteValidator(keystore, clients, - none[ValidatorIndex](), slot) + vc.attachedValidators[].addRemoteValidator(keystore, clients, + none[ValidatorIndex](), slot) else: warn "Unable to initialize remote validator", validator = $keystore.pubkey proc removeValidator*(vc: ValidatorClientRef, pubkey: ValidatorPubKey) {.async.} = - let validator = vc.attachedValidators.getValidator(pubkey) + let validator = vc.attachedValidators[].getValidator(pubkey) if not(isNil(validator)): if vc.config.doppelgangerDetection: if validator.index.isSome(): @@ -470,7 +467,7 @@ proc removeValidator*(vc: ValidatorClientRef, res await allFutures(pending) # Remove validator from ValidatorPool. - vc.attachedValidators.removeValidator(pubkey) + vc.attachedValidators[].removeValidator(pubkey) proc doppelgangerCheck*(vc: ValidatorClientRef, validator: AttachedValidator): bool = diff --git a/beacon_chain/validator_client/doppelganger_service.nim b/beacon_chain/validator_client/doppelganger_service.nim index 3721868bd9..94718028e6 100644 --- a/beacon_chain/validator_client/doppelganger_service.nim +++ b/beacon_chain/validator_client/doppelganger_service.nim @@ -104,7 +104,7 @@ proc mainLoop(service: DoppelgangerServiceRef) {.async.} = if breakLoop: break -proc init*(t: typedesc[DoppelgangerServiceRef], +proc init*(t: type DoppelgangerServiceRef, vc: ValidatorClientRef): Future[DoppelgangerServiceRef] {.async.} = logScope: service = ServiceName let res = DoppelgangerServiceRef(name: ServiceName, diff --git a/beacon_chain/validator_client/duties_service.nim b/beacon_chain/validator_client/duties_service.nim index f67addffba..73d18f7a0b 100644 --- a/beacon_chain/validator_client/duties_service.nim +++ b/beacon_chain/validator_client/duties_service.nim @@ -38,7 +38,7 @@ proc pollForValidatorIndices*(vc: ValidatorClientRef) {.async.} = let validatorIdents = block: var res: seq[ValidatorIdent] - for validator in vc.attachedValidators.items(): + for validator in vc.attachedValidators[].items(): if validator.index.isNone(): res.add(ValidatorIdent.init(validator.pubkey)) res @@ -78,24 +78,24 @@ proc pollForValidatorIndices*(vc: ValidatorClientRef) {.async.} = offset += arraySize for item in validators: - if item.validator.pubkey notin vc.attachedValidators: + if item.validator.pubkey notin vc.attachedValidators[]: warn "Beacon node returned missing validator", pubkey = item.validator.pubkey, index = item.index else: debug "Local validator updated with index", pubkey = item.validator.pubkey, index = item.index - vc.attachedValidators.updateValidator(item.validator.pubkey, + vc.attachedValidators[].updateValidator(item.validator.pubkey, item.index) # Adding validator for doppelganger detection. vc.addDoppelganger( - vc.attachedValidators.getValidator(item.validator.pubkey)) + vc.attachedValidators[].getValidator(item.validator.pubkey)) proc pollForAttesterDuties*(vc: ValidatorClientRef, epoch: Epoch): Future[int] {.async.} = let validatorIndices = block: var res: seq[ValidatorIndex] - for index in vc.attachedValidators.indices(): + for index in vc.attachedValidators[].indices(): res.add(index) res @@ -151,7 +151,7 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef, let relevantDuties = duties.filterIt( - checkDuty(it) and (it.pubkey in vc.attachedValidators) + checkDuty(it) and (it.pubkey in vc.attachedValidators[]) ) genesisRoot = vc.beaconGenesis.genesis_validators_root @@ -180,7 +180,7 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef, var pendingRequests: seq[Future[SignatureResult]] var validators: seq[AttachedValidator] for item in addOrReplaceItems: - let validator = vc.attachedValidators.getValidator(item.duty.pubkey) + let validator = vc.attachedValidators[].getValidator(item.duty.pubkey) let fork = vc.forkAtEpoch(item.duty.slot.epoch) let future = validator.getSlotSignature( fork, genesisRoot, item.duty.slot) @@ -223,7 +223,7 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef, proc pollForSyncCommitteeDuties*(vc: ValidatorClientRef, epoch: Epoch): Future[int] {.async.} = - let validatorIndices = toSeq(vc.attachedValidators.indices()) + let validatorIndices = toSeq(vc.attachedValidators[].indices()) var filteredDuties: seq[RestSyncCommitteeDuty] offset = 0 @@ -249,7 +249,7 @@ proc pollForSyncCommitteeDuties*(vc: ValidatorClientRef, return 0 for item in res.data: - if checkSyncDuty(item) and (item.pubkey in vc.attachedValidators): + if checkSyncDuty(item) and (item.pubkey in vc.attachedValidators[]): filteredDuties.add(item) offset += arraySize @@ -287,7 +287,7 @@ proc pollForSyncCommitteeDuties*(vc: ValidatorClientRef, let sres = vc.getCurrentSlot() if sres.isSome(): for item in addOrReplaceItems: - let validator = vc.attachedValidators.getValidator(item.duty.pubkey) + let validator = vc.attachedValidators[].getValidator(item.duty.pubkey) let future = validator.getSyncCommitteeSelectionProof( fork, genesisRoot, @@ -358,7 +358,7 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef) {.async.} = currentEpoch = currentSlot.epoch() nextEpoch = currentEpoch + 1'u64 - if vc.attachedValidators.count() != 0: + if vc.attachedValidators[].count() != 0: var counts: array[2, tuple[epoch: Epoch, count: int]] counts[0] = (currentEpoch, await vc.pollForAttesterDuties(currentEpoch)) counts[1] = (nextEpoch, await vc.pollForAttesterDuties(nextEpoch)) @@ -404,7 +404,7 @@ proc pollForSyncCommitteeDuties* (vc: ValidatorClientRef) {.async.} = currentEpoch = currentSlot.epoch() nextEpoch = currentEpoch + 1'u64 - if vc.attachedValidators.count() != 0: + if vc.attachedValidators[].count() != 0: var counts: array[2, tuple[epoch: Epoch, count: int]] counts[0] = (currentEpoch, await vc.pollForSyncCommitteeDuties(currentEpoch)) @@ -455,13 +455,13 @@ proc pollForBeaconProposers*(vc: ValidatorClientRef) {.async.} = currentSlot = sres.get() currentEpoch = currentSlot.epoch() - if vc.attachedValidators.count() != 0: + if vc.attachedValidators[].count() != 0: try: let res = await vc.getProposerDuties(currentEpoch) let dependentRoot = res.dependent_root duties = res.data - relevantDuties = duties.filterIt(it.pubkey in vc.attachedValidators) + relevantDuties = duties.filterIt(it.pubkey in vc.attachedValidators[]) if len(relevantDuties) > 0: vc.addOrReplaceProposers(currentEpoch, dependentRoot, relevantDuties) diff --git a/beacon_chain/validator_client/sync_committee_service.nim b/beacon_chain/validator_client/sync_committee_service.nim index 6eeee14051..5b59032628 100644 --- a/beacon_chain/validator_client/sync_committee_service.nim +++ b/beacon_chain/validator_client/sync_committee_service.nim @@ -8,25 +8,16 @@ import std/sets, metrics, chronicles, - "."/[common, api, block_service], ../spec/datatypes/[phase0, altair, bellatrix], - ../spec/eth2_apis/rest_types + ../spec/eth2_apis/rest_types, + ../validators/activity_metrics, + "."/[common, api, block_service] const ServiceName = "sync_committee_service" logScope: service = ServiceName -declareCounter beacon_sync_committee_messages_sent, - "Number of sync committee messages sent by the node" - -declareHistogram beacon_sync_committee_message_sent_delay, - "Time(s) between expected and actual sync committee message send moment", - buckets = DelayBuckets - -declareCounter beacon_sync_committee_contributions_sent, - "Number of sync committee contributions sent by the node" - type ContributionItem* = object aggregator_index: uint64 @@ -226,7 +217,7 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, block: var res: seq[ContributionItem] for duty in duties: - let validator = vc.attachedValidators.getValidator(duty.data.pubkey) + let validator = vc.attachedValidators[].getValidator(duty.data.pubkey) if not isNil(validator): if duty.slotSig.isSome: template slotSignature: auto = duty.slotSig.get diff --git a/beacon_chain/validators/activity_metrics.nim b/beacon_chain/validators/activity_metrics.nim new file mode 100644 index 0000000000..ff9b4edebf --- /dev/null +++ b/beacon_chain/validators/activity_metrics.nim @@ -0,0 +1,38 @@ +import metrics +export metrics + +const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05, + 0.05, 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, Inf] + +# The "sent" counters capture messages that were sent via this beacon node +# regardless if they were produced internally or received via the REST API. +# +# Counters and histograms for timing-sensitive messages, only counters for +# the rest (aggregates don't affect rewards, so timing is less important) + +declarePublicCounter beacon_attestations_sent, + "Number of attestations sent by the node" + +declarePublicCounter beacon_aggregates_sent, + "Number of beacon chain attestations sent by the node" + +declarePublicHistogram beacon_attestation_sent_delay, + "Time(s) between expected and actual attestation send moment", + buckets = delayBuckets + +declarePublicCounter beacon_blocks_sent, + "Number of beacon blocks sent by this node" + +declarePublicHistogram beacon_blocks_sent_delay, + "Time(s) between expected and actual block send moment", + buckets = delayBuckets + +declarePublicCounter beacon_sync_committee_messages_sent, + "Number of sync committee messages sent by the node" + +declarePublicHistogram beacon_sync_committee_message_sent_delay, + "Time(s) between expected and actual sync committee message send moment", + buckets = delayBuckets + +declarePublicCounter beacon_sync_committee_contributions_sent, + "Number of sync committee contributions sent by the node" diff --git a/beacon_chain/validators/keystore_management.nim b/beacon_chain/validators/keystore_management.nim index b32add5758..381afd2996 100644 --- a/beacon_chain/validators/keystore_management.nim +++ b/beacon_chain/validators/keystore_management.nim @@ -20,7 +20,7 @@ import ".."/spec/datatypes/base, stew/io2, libp2p/crypto/crypto as lcrypto, nimcrypto/utils as ncrutils, - ".."/[conf, filepath], + ".."/[conf, filepath, beacon_clock], ".."/networking/network_metadata, ./validator_pool @@ -49,8 +49,6 @@ type walletPath*: WalletPathPair seed*: KeySeed - AnyConf* = BeaconNodeConf | ValidatorClientConf | SigningNodeConf - KmResult*[T] = Result[T, cstring] AnyKeystore* = RemoteKeystore | Keystore @@ -69,6 +67,20 @@ type ImportResult*[T] = Result[T, AddValidatorFailure] + ValidatorPubKeyToIdxFn* = + proc (pubkey: ValidatorPubKey): Option[ValidatorIndex] + {.raises: [Defect], gcsafe.} + + KeymanagerHost* = object + validatorPool*: ref ValidatorPool + rng*: ref HmacDrbgContext + keymanagerToken*: string + validatorsDir*: string + secretsDir*: string + defaultFeeRecipient*: Eth1Address + getValidatorIdxFn*: ValidatorPubKeyToIdxFn + getBeaconTimeFn*: GetBeaconTimeFn + const minPasswordLen = 12 minPasswordEntropy = 60.0 @@ -78,6 +90,38 @@ const "passwords" / "10-million-password-list-top-100000.txt", minWordLen = minPasswordLen) +func init*(T: type KeymanagerHost, + validatorPool: ref ValidatorPool, + rng: ref HmacDrbgContext, + keymanagerToken: string, + validatorsDir: string, + secretsDir: string, + defaultFeeRecipient: Eth1Address, + getValidatorIdxFn: ValidatorPubKeyToIdxFn, + getBeaconTimeFn: GetBeaconTimeFn): T = + T(validatorPool: validatorPool, + rng: rng, + keymanagerToken: keymanagerToken, + validatorsDir: validatorsDir, + secretsDir: secretsDir, + defaultFeeRecipient: defaultFeeRecipient, + getValidatorIdxFn: getValidatorIdxFn, + getBeaconTimeFn: getBeaconTimeFn) + +proc getValidatorIdx*(host: KeymanagerHost, + pubkey: ValidatorPubKey): Option[ValidatorIndex] = + if host.getValidatorIdxFn != nil: + host.getValidatorIdxFn(pubkey) + else: + none ValidatorIndex + +proc addLocalValidator*(host: KeymanagerHost, keystore: KeystoreData) = + let + slot = host.getBeaconTimeFn().slotOrZero + validatorIdx = host.getValidatorIdx(keystore.pubkey) + + host.validatorPool[].addLocalValidator(keystore, validatorIdx, slot) + proc echoP*(msg: string) = ## Prints a paragraph aligned to 80 columns echo "" @@ -552,13 +596,8 @@ proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string, func fsName(pubkey: ValidatorPubKey|CookedPubKey): string = "0x" & pubkey.toHex() -proc removeValidatorFiles*( - conf: AnyConf, keyName: string, - kind: KeystoreKind - ): KmResult[RemoveValidatorStatus] {.raises: [Defect].} = - removeValidatorFiles(conf.validatorsDir(), conf.secretsDir(), keyName, kind) - -proc removeValidator*(pool: var ValidatorPool, conf: AnyConf, +proc removeValidator*(pool: var ValidatorPool, + validatorsDir, secretsDir: string, publicKey: ValidatorPubKey, kind: KeystoreKind): KmResult[RemoveValidatorStatus] {. raises: [Defect].} = @@ -570,7 +609,7 @@ proc removeValidator*(pool: var ValidatorPool, conf: AnyConf, let cres = validator.data.handle.closeLockedFile() if cres.isErr(): return err("Could not unlock validator keystore file") - let res = removeValidatorFiles(conf, publicKey.fsName, kind) + let res = removeValidatorFiles(validatorsDir, secretsDir, publicKey.fsName, kind) if res.isErr(): return err(res.error()) pool.removeValidator(publicKey) @@ -1111,37 +1150,13 @@ proc saveLockedKeystore*( let remoteInfo = RemoteSignerInfo(url: url, id: 0) saveLockedKeystore(validatorsDir, publicKey, @[remoteInfo], 1) -proc saveKeystore*( - conf: AnyConf, - publicKey: ValidatorPubKey, - remotes: seq[RemoteSignerInfo], - threshold: uint32, - flags: set[RemoteKeystoreFlag] = {}, - remoteType = RemoteSignerType.Web3Signer, - desc = "" - ): Result[FileLockHandle, KeystoreGenerationError] {.raises: [Defect].} = - saveKeystore(conf.validatorsDir(), - publicKey, remotes, threshold, flags, remoteType, desc) - -proc saveLockedKeystore*( - conf: AnyConf, - publicKey: ValidatorPubKey, - remotes: seq[RemoteSignerInfo], - threshold: uint32, - flags: set[RemoteKeystoreFlag] = {}, - remoteType = RemoteSignerType.Web3Signer, - desc = "" - ): Result[FileLockHandle, KeystoreGenerationError] {.raises: [Defect].} = - saveLockedKeystore(conf.validatorsDir(), - publicKey, remotes, threshold, flags, remoteType, desc) - -proc importKeystore*(pool: var ValidatorPool, conf: AnyConf, +proc importKeystore*(pool: var ValidatorPool, + validatorsDir: string, keystore: RemoteKeystore): ImportResult[KeystoreData] {.raises: [Defect].} = let publicKey = keystore.pubkey keyName = publicKey.fsName - validatorsDir = conf.validatorsDir() keystoreDir = validatorsDir / keyName keystoreFile = keystoreDir / RemoteKeystoreFileName @@ -1163,7 +1178,7 @@ proc importKeystore*(pool: var ValidatorPool, conf: AnyConf, if existsKeystore(keystoreDir, {KeystoreKind.Local, KeystoreKind.Remote}): return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts)) - let res = saveLockedKeystore(conf, publicKey, keystore.remotes, + let res = saveLockedKeystore(validatorsDir, publicKey, keystore.remotes, keystore.threshold) if res.isErr(): return err(AddValidatorFailure.init(AddValidatorStatus.failed, @@ -1173,7 +1188,8 @@ proc importKeystore*(pool: var ValidatorPool, conf: AnyConf, proc importKeystore*(pool: var ValidatorPool, rng: var HmacDrbgContext, - conf: AnyConf, keystore: Keystore, + validatorsDir, secretsDir: string, + keystore: Keystore, password: string): ImportResult[KeystoreData] {. raises: [Defect].} = let keypass = KeystorePass.init(password) @@ -1188,8 +1204,6 @@ proc importKeystore*(pool: var ValidatorPool, let publicKey = privateKey.toPubKey() keyName = publicKey.fsName - validatorsDir = conf.validatorsDir() - secretsDir = conf.secretsDir() secretFile = secretsDir / keyName keystoreDir = validatorsDir / keyName keystoreFile = keystoreDir / KeystoreFileName @@ -1241,14 +1255,17 @@ proc generateDistirbutedStore*(rng: var HmacDrbgContext, # actual validator saveKeystore(remoteValidatorDir, pubKey, signers, threshold) -func validatorKeystoreDir(conf: AnyConf, pubkey: ValidatorPubKey): string = - conf.validatorsDir / pubkey.fsName +func validatorKeystoreDir(host: KeymanagerHost, + pubkey: ValidatorPubKey): string = + host.validatorsDir / pubkey.fsName -func feeRecipientPath*(conf: AnyConf, pubkey: ValidatorPubKey): string = - conf.validatorKeystoreDir(pubkey) / FeeRecipientFilename +func feeRecipientPath*(host: KeymanagerHost, + pubkey: ValidatorPubKey): string = + host.validatorKeystoreDir(pubkey) / FeeRecipientFilename -proc removeFeeRecipientFile*(conf: AnyConf, pubkey: ValidatorPubKey): Result[void, string] = - let path = conf.feeRecipientPath(pubkey) +proc removeFeeRecipientFile*(host: KeymanagerHost, + pubkey: ValidatorPubKey): Result[void, string] = + let path = host.feeRecipientPath(pubkey) if fileExists(path): let res = io2.removeFile(path) if res.isErr: @@ -1256,8 +1273,8 @@ proc removeFeeRecipientFile*(conf: AnyConf, pubkey: ValidatorPubKey): Result[voi return ok() -proc setFeeRecipient*(conf: AnyConf, pubkey: ValidatorPubKey, feeRecipient: Eth1Address): Result[void, string] = - let validatorKeystoreDir = conf.validatorKeystoreDir(pubkey) +proc setFeeRecipient*(host: KeymanagerHost, pubkey: ValidatorPubKey, feeRecipient: Eth1Address): Result[void, string] = + let validatorKeystoreDir = host.validatorKeystoreDir(pubkey) ? secureCreatePath(validatorKeystoreDir).mapErr(proc(e: auto): string = "Could not create wallet directory [" & validatorKeystoreDir & "]: " & $e) @@ -1265,22 +1282,15 @@ proc setFeeRecipient*(conf: AnyConf, pubkey: ValidatorPubKey, feeRecipient: Eth1 io2.writeFile(validatorKeystoreDir / FeeRecipientFilename, $feeRecipient) .mapErr(proc(e: auto): string = "Failed to write fee recipient file: " & $e) -func defaultFeeRecipient*(conf: AnyConf): Eth1Address = - if conf.suggestedFeeRecipient.isSome: - conf.suggestedFeeRecipient.get - else: - # https://github.com/nim-lang/Nim/issues/19802 - (static(default(Eth1Address))) - type FeeRecipientStatus* = enum noSuchValidator invalidFeeRecipientFile proc getSuggestedFeeRecipient*( - conf: AnyConf, + host: KeymanagerHost, pubkey: ValidatorPubKey): Result[Eth1Address, FeeRecipientStatus] = - let validatorDir = conf.validatorKeystoreDir(pubkey) + let validatorDir = host.validatorKeystoreDir(pubkey) # 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 @@ -1291,7 +1301,7 @@ proc getSuggestedFeeRecipient*( let feeRecipientPath = validatorDir / FeeRecipientFilename if not fileExists(feeRecipientPath): - return ok conf.defaultFeeRecipient + return ok host.defaultFeeRecipient try: # Avoid being overly flexible initially. Trailing whitespace is common diff --git a/beacon_chain/validators/message_router.nim b/beacon_chain/validators/message_router.nim index 229d73a2d9..4a6181402b 100644 --- a/beacon_chain/validators/message_router.nim +++ b/beacon_chain/validators/message_router.nim @@ -17,44 +17,11 @@ import ../spec/network, ../consensus_object_pools/spec_cache, ../gossip_processing/eth2_processor, - ../networking/eth2_network + ../networking/eth2_network, + ./activity_metrics export eth2_processor, eth2_network -# The "sent" counters capture messages that were sent via this beacon node -# regardless if they were produced internally or received via the REST API. -# -# Counters and histograms for timing-sensitive messages, only counters for -# the rest (aggregates don't affect rewards, so timing is less important) -const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05, - 0.05, 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, Inf] -declareCounter beacon_blocks_sent, - "Number of beacon blocks sent by this node" - -declareHistogram beacon_blocks_sent_delay, - "Time(s) between expected and actual block send moment", - buckets = delayBuckets - -declareCounter beacon_attestations_sent, - "Number of attestations sent by the node" - -declareCounter beacon_aggregates_sent, - "Number of beacon chain attestations sent by the node" - -declareHistogram beacon_attestation_sent_delay, - "Time(s) between expected and actual attestation send moment", - buckets = delayBuckets - -declareCounter beacon_sync_committee_messages_sent, - "Number of sync committee messages sent by the node" - -declareHistogram beacon_sync_committee_message_sent_delay, - "Time(s) between expected and actual sync committee message send moment", - buckets = delayBuckets - -declareCounter beacon_sync_committee_contributions_sent, - "Number of sync committee contributions sent by the node" - declareCounter beacon_voluntary_exits_sent, "Number of beacon voluntary sent by this node" diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index 45bb3200a7..cf24d34b96 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -74,7 +74,7 @@ logScope: topics = "beacval" type ForkedBlockResult* = Result[ForkedBeaconBlock, string] -proc findValidator(validators: auto, pubkey: ValidatorPubKey): +proc findValidator*(validators: auto, pubkey: ValidatorPubKey): Option[ValidatorIndex] = let idx = validators.findIt(it.pubkey == pubkey) if idx == -1: @@ -92,8 +92,11 @@ proc addLocalValidator(node: BeaconNode, validators: auto, index = findValidator(validators, pubkey) node.attachedValidators[].addLocalValidator(item, index, slot) -proc addRemoteValidator(pool: var ValidatorPool, validators: auto, - item: KeystoreData, slot: Slot) = +# TODO: This should probably be moved to the validator_pool module +proc addRemoteValidator*(pool: var ValidatorPool, + index: Option[ValidatorIndex], + item: KeystoreData, + slot: Slot) = var clients: seq[(RestClientRef, RemoteSignerInfo)] let httpFlags = block: @@ -109,7 +112,6 @@ proc addRemoteValidator(pool: var ValidatorPool, validators: auto, warn "Unable to resolve distributed signer address", remote_url = $remote.url, validator = $remote.pubkey clients.add((client.get(), remote)) - let index = findValidator(validators, item.pubkey) pool.addRemoteValidator(item, clients, index, slot) proc addLocalValidators*(node: BeaconNode, @@ -124,8 +126,8 @@ proc addRemoteValidators*(node: BeaconNode, let slot = node.currentSlot() withState(node.dag.headState): for item in validators: - node.attachedValidators[].addRemoteValidator( - state.data.validators.asSeq(), item, slot) + let index = findValidator(state.data.validators.asSeq(), item.pubkey) + node.attachedValidators[].addRemoteValidator(index, item, slot) proc addValidators*(node: BeaconNode) = let (localValidators, remoteValidators) = @@ -153,7 +155,7 @@ proc getAttachedValidator(node: BeaconNode, if validator != nil and validator.index != some(idx): # Update index, in case the validator was activated! notice "Validator activated", pubkey = validator.pubkey, index = idx - validator.index = some(idx) + validator.index = some(idx) validator else: warn "Validator index out of bounds", @@ -387,7 +389,7 @@ proc getExecutionPayload( dynamicFeeRecipient = node.dynamicFeeRecipientsStore.getDynamicFeeRecipient( validator_index, epoch) feeRecipient = dynamicFeeRecipient.valueOr: - node.config.getSuggestedFeeRecipient(pubkey).valueOr: + node.keymanagerHost[].getSuggestedFeeRecipient(pubkey).valueOr: node.config.defaultFeeRecipient payload_id = (await forkchoice_updated( proposalState.bellatrixData.data, latestHead, latestFinalized, @@ -1180,7 +1182,7 @@ proc getValidatorRegistration( const gasLimit = 30000000 let feeRecipient = - node.config.getSuggestedFeeRecipient(validator.pubkey).valueOr: + node.keymanagerHost[].getSuggestedFeeRecipient(validator.pubkey).valueOr: node.config.defaultFeeRecipient var validatorRegistration = SignedValidatorRegistrationV1( message: ValidatorRegistrationV1( diff --git a/scripts/launch_local_testnet.sh b/scripts/launch_local_testnet.sh index 3113a13c4b..23a825b25b 100755 --- a/scripts/launch_local_testnet.sh +++ b/scripts/launch_local_testnet.sh @@ -71,6 +71,7 @@ BASE_PORT="9000" BASE_REMOTE_SIGNER_PORT="6000" BASE_METRICS_PORT="8008" BASE_REST_PORT="7500" +BASE_VC_KEYMANAGER_PORT="8500" BASE_EL_NET_PORT="30303" BASE_EL_HTTP_PORT="8545" BASE_EL_WS_PORT="8546" @@ -340,6 +341,7 @@ if [[ "${LIGHTHOUSE_VC_NODES}" != "0" && "${CONST_PRESET}" != "mainnet" ]]; then fi scripts/makedir.sh "${DATA_DIR}" +echo x > "${DATA_DIR}/keymanager-token" VALIDATORS_DIR="${DATA_DIR}/validators" scripts/makedir.sh "${VALIDATORS_DIR}" @@ -975,6 +977,8 @@ for NUM_NODE in $(seq 0 $(( NUM_NODES - 1 ))); do ${WEB3_ARG} \ ${STOP_AT_EPOCH_FLAG} \ --rest-port="$(( BASE_REST_PORT + NUM_NODE ))" \ + --keymanager \ + --keymanager-token-file="${DATA_DIR}/keymanager-token" \ --metrics-port="$(( BASE_METRICS_PORT + NUM_NODE ))" \ --light-client=on \ ${EXTRA_ARGS} \ @@ -1008,6 +1012,9 @@ for NUM_NODE in $(seq 0 $(( NUM_NODES - 1 ))); do --log-level="${LOG_LEVEL}" \ ${STOP_AT_EPOCH_FLAG} \ --data-dir="${VALIDATOR_DATA_DIR}" \ + --keymanager \ + --keymanager-port=$((BASE_VC_KEYMANAGER_PORT + NUM_NODE)) \ + --keymanager-token-file="${DATA_DIR}/keymanager-token" \ --beacon-node="http://127.0.0.1:$((BASE_REST_PORT + NUM_NODE))" \ &> "${DATA_DIR}/log_val${NUM_NODE}.txt" & PIDS="${PIDS},$!" diff --git a/tests/test_keymanager_api.nim b/tests/test_keymanager_api.nim index fa1c18e28a..64540c924a 100644 --- a/tests/test_keymanager_api.nim +++ b/tests/test_keymanager_api.nim @@ -20,10 +20,18 @@ import ../beacon_chain/networking/network_metadata, ../beacon_chain/rpc/rest_key_management_api, ../beacon_chain/[conf, filepath, beacon_node, - nimbus_beacon_node, beacon_node_status], + nimbus_beacon_node, beacon_node_status, + nimbus_validator_client], + ../beacon_chain/validator_client/common, ./testutil +type + KeymanagerToTest = object + port: int + validatorsDir: string + secretsDir: string + const simulationDepositsCount = 128 dataDir = "./test_keymanager_api" @@ -33,9 +41,11 @@ const genesisFile = dataDir / "genesis.ssz" bootstrapEnrFile = dataDir / "bootstrap_node.enr" tokenFilePath = dataDir / "keymanager-token.txt" - keymanagerPort = 47000 + keymanagerPortBN = 47000 + keymanagerPortVC = 48000 correctTokenValue = "some secret token" defaultFeeRecipient = Eth1Address.fromHex("0x000000000000000000000000000000000000DEAD") + newPrivateKeys = [ "0x598c9b81749ba7bb8eb37781027359e3ffe87d0e1579e21c453ce22af0c05e35", "0x14e4470a1d8913ec0602048af78addf0fd7a37f591dd3feda828d10a10c0f6ff", @@ -46,6 +56,7 @@ const "0x10052305a5fda7805fb1e762fe6cbc47e43c5a54f34f008fa79c48fee1749db7", "0x3630f086fb9f1136fe077751031a16630e43d65ff64bb9fd3708adff81df5926" ] + oldPublicKeys = [ "0x94effccb0514f0f110a9680827e4f3769e53349e3b1c177e8c4f38b0e52e7842a4990212fe2edd2ce48b9b0bd02f3b04", "0x950bcb136ef15e737cd28cc8ba94a5584e30cf6cfa4f3d16215acbe46917633c09630208f379898a898b29bd59b2bd34", @@ -56,7 +67,9 @@ const "0x995e1d9d9d467ca25b981a7ca0880e932ac418e5ebed9a834f3ead3fbec267986e28eb0243c562ae3b1995a600c1495c", "0x945ab594e8c9cf3d6251b86fddf6fbf970c1835cd14113098554f135a6c2cf7f21d2f7a08ae33726785a59ae4910fa51", ] + oldPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/local")) + newPublicKeys = [ "0x80eadf027ad564a2f004616fa58f3add9caa700b20e9bf7e0b101be61406feb79f5e28ec8a5bb2a0689cc7b4c807afba", "0x8c6585f39fd3d2ed950ba4958f0050ec68e4e7e3200147687fa101bcf98977ebe144b03edc45906faae144549f11d8b9", @@ -67,6 +80,7 @@ const "0xa782e5161ba8e9ac135b0db3203a8c23aa61e19be6b9c198393d8b2b902bad8139863d9cf26bc2cbdc3b747bafc64606", "0xb33f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7189e" ] + unusedPublicKeys = [ "0xc22f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7232d", "0x0bbca63e35c7a159fc2f187d300cad9ef5f5e73e55f78c391e7bc2c2feabc2d9d63dfe99edd7058ad0ab9d7f14aade5f" @@ -74,6 +88,24 @@ const newPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/remote")) + nodeDataDir = dataDir / "node-0" + nodeValidatorsDir = nodeDataDir / "validators" + nodeSecretsDir = nodeDataDir / "secrets" + + vcDataDir = dataDir / "validator-0" + vcValidatorsDir = vcDataDir / "validators" + vcSecretsDir = vcDataDir / "secrets" + + beaconNodeKeymanager = KeymanagerToTest( + port: keymanagerPortBN, + validatorsDir: nodeValidatorsDir, + secretsDir: nodeSecretsDir) + + validatorClientKeymanager = KeymanagerToTest( + port: keymanagerPortVC, + validatorsDir: vcValidatorsDir, + secretsDir: vcSecretsDir) + func specifiedFeeRecipient(x: int): Eth1Address = copyMem(addr result, unsafeAddr x, sizeof x) @@ -87,7 +119,7 @@ proc contains*(keylist: openArray[KeystoreInfo], key: string): bool = let pubkey = ValidatorPubKey.fromHex(key).tryGet() contains(keylist, pubkey) -proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} = +proc prepareNetwork = let rng = keys.newRng() mnemonic = generateMnemonic(rng[]) @@ -126,18 +158,6 @@ proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} = Json.saveFile(depositsFile, launchPadDeposits) notice "Deposit data written", filename = depositsFile - for item in oldPublicKeys: - let key = ValidatorPubKey.fromHex(item).tryGet() - let res = saveKeystore(validatorsDir, key, oldPublicKeysUrl) - if res.isErr(): - fatal "Failed to create remote keystore file", err = res.error - quit 1 - - let tokenFileRes = secureWriteFile(tokenFilePath, correctTokenValue) - if tokenFileRes.isErr: - fatal "Failed to create token file", err = deposits.error - quit 1 - let createTestnetConf = try: BeaconNodeConf.load(cmdLine = mapIt([ "--data-dir=" & dataDir, "createTestnet", @@ -153,20 +173,112 @@ proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} = doCreateTestnet(createTestnetConf, rng[]) + let tokenFileRes = secureWriteFile(tokenFilePath, correctTokenValue) + if tokenFileRes.isErr: + fatal "Failed to create token file", err = deposits.error + quit 1 + +proc copyHalfValidators(dstDataDir: string, firstHalf: bool) = + let dstValidatorsDir = dstDataDir / "validators" + + block: + let status = secureCreatePath(dstValidatorsDir) + if status.isErr(): + fatal "Could not create node validators folder", + path = dstValidatorsDir, err = ioErrorMsg(status.error) + quit 1 + + let dstSecretsDir = dstDataDir / "secrets" + + block: + let status = secureCreatePath(dstSecretsDir) + if status.isErr(): + fatal "Could not create node secrets folder", + path = dstSecretsDir, err = ioErrorMsg(status.error) + quit 1 + + var validatorIdx = 0 + for validator in walkDir(validatorsDir): + if (validatorIdx < simulationDepositsCount div 2) == firstHalf: + let + currValidator = os.splitPath(validator.path).tail + secretFile = secretsDir / currValidator + secretRes = readAllChars(secretFile) + + if secretRes.isErr: + fatal "Failed to read secret file", + path = secretFile, err = $secretRes.error + quit 1 + + let + dstSecretFile = dstSecretsDir / currValidator + secretFileStatus = secureWriteFile(dstSecretFile, secretRes.get) + + if secretFileStatus.isErr: + fatal "Failed to write secret file", + path = dstSecretFile, err = $secretFileStatus.error + quit 1 + + let + dstValidatorDir = dstDataDir / "validators" / currValidator + validatorDirRes = secureCreatePath(dstValidatorDir) + + if validatorDirRes.isErr: + fatal "Failed to create validator dir", + path = dstValidatorDir, err = $validatorDirRes.error + quit 1 + + let + keystoreFile = validatorsDir / currValidator / "keystore.json" + readKeystoreRes = readAllChars(keystoreFile) + + if readKeystoreRes.isErr: + fatal "Failed to read keystore file", + path = keystoreFile, err = $readKeystoreRes.error + quit 1 + + let + dstKeystore = dstValidatorDir / "keystore.json" + writeKeystoreRes = secureWriteFile(dstKeystore, readKeystoreRes.get) + + if writeKeystoreRes.isErr: + fatal "Failed to write keystore file", + path = dstKeystore, err = $writeKeystoreRes.error + quit 1 + + inc validatorIdx + +proc addPreTestRemoteKeystores(validatorsDir: string) = + for item in oldPublicKeys: + let key = ValidatorPubKey.fromHex(item).tryGet() + let res = saveKeystore(validatorsDir, key, oldPublicKeysUrl) + if res.isErr(): + fatal "Failed to create remote keystore file", + validatorsDir = nodeValidatorsDir, key, + err = res.error + quit 1 + +proc startBeaconNode {.raises: [Defect, CatchableError].} = + let rng = keys.newRng() + + copyHalfValidators(nodeDataDir, true) + addPreTestRemoteKeystores(nodeValidatorsDir) + let runNodeConf = try: BeaconNodeConf.load(cmdLine = mapIt([ "--tcp-port=49000", "--udp-port=49000", "--network=" & dataDir, - "--data-dir=" & dataDir, - "--validators-dir=" & validatorsDir, - "--secrets-dir=" & secretsDir, + "--data-dir=" & nodeDataDir, + "--validators-dir=" & nodeValidatorsDir, + "--secrets-dir=" & nodeSecretsDir, "--metrics-address=127.0.0.1", "--metrics-port=48008", + "--rest=true", "--rest-address=127.0.0.1", - "--rest-port=" & $keymanagerPort, + "--rest-port=" & $keymanagerPortBN, "--keymanager=true", "--keymanager-address=127.0.0.1", - "--keymanager-port=" & $keymanagerPort, + "--keymanager-port=" & $keymanagerPortBN, "--keymanager-token-file=" & tokenFilePath, "--suggested-fee-recipient=" & $defaultFeeRecipient, "--doppelganger-detection=off"], it)) @@ -186,9 +298,30 @@ proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} = ) node.start() # This will run until the node is terminated by - # setting its `bnStatus` to `Stopping`. + # setting its `bnStatus` to `Stopping`. - os.removeDir dataDir + # os.removeDir dataDir + +proc startValidatorClient {.async, thread.} = + let rng = keys.newRng() + + copyHalfValidators(vcDataDir, false) + addPreTestRemoteKeystores(vcValidatorsDir) + + let runValidatorClientConf = try: ValidatorClientConf.load(cmdLine = mapIt([ + "--beacon-node=http://127.0.0.1:47000", + "--data-dir=" & vcDataDir, + "--validators-dir=" & vcValidatorsDir, + "--secrets-dir=" & vcSecretsDir, + "--suggested-fee-recipient=" & $defaultFeeRecipient, + "--keymanager=true", + "--keymanager-address=127.0.0.1", + "--keymanager-port=" & $keymanagerPortVC, + "--keymanager-token-file=" & tokenFilePath], TaintedString it)) + except: + quit 1 + + await runValidatorClient(runValidatorClientConf, rng) const password = "7465737470617373776f7264f09f9491" @@ -248,19 +381,16 @@ proc `==`(a: seq[ValidatorPubKey], indices.add(index) true -proc runTests {.async.} = - while bnStatus != BeaconNodeStatus.Running: - await sleepAsync(1.seconds) - - await sleepAsync(2.seconds) - +proc runTests(keymanager: KeymanagerToTest) {.async.} = let - client = RestClientRef.new(initTAddress("127.0.0.1", keymanagerPort)) + client = RestClientRef.new(initTAddress("127.0.0.1", keymanager.port)) rng = keys.newRng() privateKey = ValidatorPrivKey.fromRaw(secretBytes).get - localList = listLocalValidators(validatorsDir, secretsDir) + allValidators = listLocalValidators( + keymanager.validatorsDir, keymanager.secretsDir) + let newKeystore = createKeystore( kdfPbkdf2, rng[], privateKey, KeystorePass.init password, @@ -317,12 +447,21 @@ proc runTests {.async.} = let key = ValidatorPubKey.fromHex(item).tryGet() res.add(RemoteKeystoreInfo(pubkey: key, url: newPublicKeysUrl)) # Adding non-remote keys which are already present in filesystem - res.add(RemoteKeystoreInfo(pubkey: localList[0], + res.add(RemoteKeystoreInfo(pubkey: allValidators[0], url: newPublicKeysUrl)) - res.add(RemoteKeystoreInfo(pubkey: localList[1], + res.add(RemoteKeystoreInfo(pubkey: allValidators[1], url: newPublicKeysUrl)) ImportRemoteKeystoresBody(remote_keys: res) + template expectedImportStatus(i: int): string = + if i < 8: + "duplicate" + elif i == 16 or i == 17: + "duplicate" + else: + "imported" + + let deleteRemoteKeystoresBody1 = block: var res: seq[ValidatorPubKey] @@ -354,12 +493,20 @@ proc runTests {.async.} = pubkeys: @[ ValidatorPubKey.fromHex(oldPublicKeys[0]).tryGet(), ValidatorPubKey.fromHex(oldPublicKeys[1]).tryGet(), - localList[0], - localList[1] + allValidators[0], + allValidators[1] ] ) - suite "Serialization/deserialization " & preset(): + keymanagerKind = + if keymanager.port == keymanagerPortBN: + " [Beacon Node]" + else: + " [Validator Client]" + + testFlavour = keymanagerKind & preset() + + suite "Serialization/deserialization" & testFlavour: proc `==`(a, b: Kdf): bool = if (a.function != b.function) or (a.message != b.message): return false @@ -559,16 +706,16 @@ proc runTests {.async.} = d5.passwords == @["7465737470617373776f7264f09f9491", "7465737470617373776f7264f09f9491"] - suite "ListKeys requests" & preset(): - asyncTest "Correct token provided" & preset(): + suite "ListKeys requests" & testFlavour: + asyncTest "Correct token provided" & testFlavour: let - filesystemKeys = sorted( - listLocalValidators(validatorsDir, secretsDir)) - apiKeystores = sorted((await client.listKeys(correctTokenValue)).data) + filesystemKeys = sorted listLocalValidators(keymanager.validatorsDir, + keymanager.secretsDir) + apiKeys = sorted (await client.listKeys(correctTokenValue)).data - check filesystemKeys == apiKeystores + check filesystemKeys == apiKeys - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.listKeysPlain() responseJson = Json.decode(response.data, JsonNode) @@ -577,7 +724,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.listKeysPlain( extraHeaders = @[("Authorization", "UnknownAuthScheme X")]) @@ -587,7 +734,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.listKeysPlain( extraHeaders = @[("Authorization", "Bearer InvalidToken")]) @@ -600,8 +747,8 @@ proc runTests {.async.} = expect RestError: let keystores = await client.listKeys("Invalid Token") - suite "ImportKeystores requests" & preset(): - asyncTest "ImportKeystores/ListKeystores/DeleteKeystores" & preset(): + suite "ImportKeystores requests" & testFlavour: + asyncTest "ImportKeystores/ListKeystores/DeleteKeystores" & testFlavour: let response1 = await client.importKeystoresPlain( importKeystoresBody1, @@ -616,7 +763,8 @@ proc runTests {.async.} = let filesystemKeys1 = sorted( - listLocalValidators(validatorsDir, secretsDir)) + listLocalValidators(keymanager.validatorsDir, + keymanager.secretsDir)) apiKeystores1 = sorted((await client.listKeys(correctTokenValue)).data) check: @@ -656,7 +804,8 @@ proc runTests {.async.} = let filesystemKeys2 = sorted( - listLocalValidators(validatorsDir, secretsDir)) + listLocalValidators(keymanager.validatorsDir, + keymanager.secretsDir)) apiKeystores2 = sorted((await client.listKeys(correctTokenValue)).data) check: @@ -670,7 +819,7 @@ proc runTests {.async.} = deleteKeysBody1.pubkeys[6] notin filesystemKeys2 deleteKeysBody1.pubkeys[7] notin filesystemKeys2 - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.importKeystoresPlain(importKeystoresBody) responseJson = Json.decode(response.data, JsonNode) @@ -679,7 +828,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.importKeystoresPlain( importKeystoresBody, @@ -690,7 +839,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.importKeystoresPlain( importKeystoresBody, @@ -701,8 +850,8 @@ proc runTests {.async.} = response.status == 403 responseJson["message"].getStr() == InvalidAuthorizationError - suite "DeleteKeys requests" & preset(): - asyncTest "Deleting not existing key" & preset(): + suite "DeleteKeys requests" & testFlavour: + asyncTest "Deleting not existing key" & testFlavour: let response = await client.deleteKeysPlain( deleteKeysBody, @@ -714,7 +863,7 @@ proc runTests {.async.} = responseJson["data"][0]["status"].getStr() == "not_found" responseJson["data"][0]["message"].getStr() == "" - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.deleteKeysPlain(deleteKeysBody) responseJson = Json.decode(response.data, JsonNode) @@ -723,7 +872,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.deleteKeysPlain( deleteKeysBody, @@ -734,7 +883,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.deleteKeysPlain( deleteKeysBody, @@ -745,17 +894,18 @@ proc runTests {.async.} = response.status == 403 responseJson["message"].getStr() == InvalidAuthorizationError - suite "ListRemoteKeys requests" & preset(): - asyncTest "Correct token provided" & preset(): + suite "ListRemoteKeys requests" & testFlavour: + asyncTest "Correct token provided" & testFlavour: let filesystemKeys = sorted( - listRemoteValidators(validatorsDir, secretsDir)) + listRemoteValidators(keymanager.validatorsDir, + keymanager.secretsDir)) apiKeystores = sorted(( await client.listRemoteKeys(correctTokenValue)).data) check filesystemKeys == apiKeystores - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.listRemoteKeysPlain() responseJson = Json.decode(response.data, JsonNode) @@ -764,7 +914,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.listRemoteKeysPlain( extraHeaders = @[("Authorization", "UnknownAuthScheme X")]) @@ -774,7 +924,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.listRemoteKeysPlain( extraHeaders = @[("Authorization", "Bearer InvalidToken")]) @@ -787,8 +937,8 @@ proc runTests {.async.} = expect RestError: let keystores = await client.listKeys("Invalid Token") - suite "Fee recipient management" & preset(): - asyncTest "Missing Authorization header" & preset(): + suite "Fee recipient management" & testFlavour: + asyncTest "Missing Authorization header" & testFlavour: let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key") block: @@ -820,7 +970,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key") block: @@ -859,7 +1009,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key") block: @@ -897,7 +1047,7 @@ proc runTests {.async.} = response.status == 403 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Obtaining the fee recpient of a missing validator returns 404" & preset(): + asyncTest "Obtaining the fee recpient of a missing validator returns 404" & testFlavour: let pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[0]).expect("valid key") response = await client.listFeeRecipientPlain( @@ -907,7 +1057,7 @@ proc runTests {.async.} = check: response.status == 404 - asyncTest "Setting the fee recipient on a missing validator creates a record for it" & preset(): + asyncTest "Setting the fee recipient on a missing validator creates a record for it" & testFlavour: let pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[1]).expect("valid key") feeRecipient = specifiedFeeRecipient(1) @@ -918,7 +1068,7 @@ proc runTests {.async.} = check: resultFromApi == feeRecipient - asyncTest "Obtaining the fee recpient of an unconfigured validator returns the suggested default" & preset(): + asyncTest "Obtaining the fee recpient of an unconfigured validator returns the suggested default" & testFlavour: let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key") resultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue) @@ -926,7 +1076,7 @@ proc runTests {.async.} = check: resultFromApi == defaultFeeRecipient - asyncTest "Configuring the fee recpient" & preset(): + asyncTest "Configuring the fee recpient" & testFlavour: let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[1]).expect("valid key") firstFeeRecipient = specifiedFeeRecipient(2) @@ -949,8 +1099,8 @@ proc runTests {.async.} = check: finalResultFromApi == defaultFeeRecipient - suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & preset(): - asyncTest "Importing list of remote keys" & preset(): + suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & testFlavour: + asyncTest "Importing list of remote keys" & testFlavour: let response1 = await client.importRemoteKeysPlain( importRemoteKeystoresBody, @@ -959,18 +1109,16 @@ proc runTests {.async.} = check: response1.status == 200 - for i in [0, 1, 2, 3, 4, 5, 6, 7, 16, 17]: - check: - responseJson1["data"][i]["status"].getStr() == "duplicate" - responseJson1["data"][i]["message"].getStr() == "" - for i in 8 ..< 16: + + for i in 0 ..< 18: check: - responseJson1["data"][i]["status"].getStr() == "imported" + responseJson1["data"][i]["status"].getStr() == expectedImportStatus(i) responseJson1["data"][i]["message"].getStr() == "" let filesystemKeys1 = sorted( - listRemoteValidators(validatorsDir, secretsDir)) + listRemoteValidators(keymanager.validatorsDir, + keymanager.secretsDir)) apiKeystores1 = sorted(( await client.listRemoteKeys(correctTokenValue)).data) @@ -1008,7 +1156,8 @@ proc runTests {.async.} = let filesystemKeys2 = sorted( - listRemoteValidators(validatorsDir, secretsDir)) + listRemoteValidators(keymanager.validatorsDir, + keymanager.secretsDir)) apiKeystores2 = sorted(( await client.listRemoteKeys(correctTokenValue)).data) @@ -1020,7 +1169,7 @@ proc runTests {.async.} = check: key notin newPublicKeys - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.importRemoteKeysPlain(importRemoteKeystoresBody) responseJson = Json.decode(response.data, JsonNode) @@ -1029,7 +1178,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.importRemoteKeysPlain( importRemoteKeystoresBody, @@ -1040,7 +1189,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.importRemoteKeysPlain( importRemoteKeystoresBody, @@ -1051,8 +1200,8 @@ proc runTests {.async.} = response.status == 403 responseJson["message"].getStr() == InvalidAuthorizationError - suite "DeleteRemoteKeys requests" & preset(): - asyncTest "Deleting not existing key" & preset(): + suite "DeleteRemoteKeys requests" & testFlavour: + asyncTest "Deleting not existing key" & testFlavour: let response = await client.deleteRemoteKeysPlain( deleteRemoteKeystoresBody3, @@ -1064,7 +1213,7 @@ proc runTests {.async.} = responseJson["data"][0]["status"].getStr() == "not_found" responseJson["data"][1]["status"].getStr() == "not_found" - asyncTest "Deleting existing local key and remote key" & preset(): + asyncTest "Deleting existing local key and remote key" & testFlavour: let response = await client.deleteRemoteKeysPlain( deleteRemoteKeystoresBody4, @@ -1080,7 +1229,7 @@ proc runTests {.async.} = let filesystemKeystores = sorted( - listRemoteValidators(validatorsDir, secretsDir)) + listRemoteValidators(nodeValidatorsDir, nodeSecretsDir)) apiKeystores = sorted(( await client.listRemoteKeys(correctTokenValue)).data) @@ -1096,7 +1245,7 @@ proc runTests {.async.} = removedKey0 != item.pubkey removedKey1 != item.pubkey - asyncTest "Missing Authorization header" & preset(): + asyncTest "Missing Authorization header" & testFlavour: let response = await client.deleteRemoteKeysPlain( deleteRemoteKeystoresBody1) @@ -1106,7 +1255,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Header" & preset(): + asyncTest "Invalid Authorization Header" & testFlavour: let response = await client.deleteRemoteKeysPlain( deleteRemoteKeystoresBody1, @@ -1117,7 +1266,7 @@ proc runTests {.async.} = response.status == 401 responseJson["message"].getStr() == InvalidAuthorizationError - asyncTest "Invalid Authorization Token" & preset(): + asyncTest "Invalid Authorization Token" & testFlavour: let response = await client.deleteRemoteKeysPlain( deleteRemoteKeystoresBody1, @@ -1128,10 +1277,30 @@ proc runTests {.async.} = response.status == 403 responseJson["message"].getStr() == InvalidAuthorizationError +proc delayedTests {.async.} = + while bnStatus != BeaconNodeStatus.Running: + await sleepAsync(1.seconds) + + asyncSpawn startValidatorClient() + + await sleepAsync(2.seconds) + + await runTests(beaconNodeKeymanager) + + # TODO + # This tests showed flaky behavior on a single Windows CI host + # Re-enable it in a follow-up PR + # await runTests(validatorClientKeymanager) + bnStatus = BeaconNodeStatus.Stopping proc main() {.async.} = - asyncSpawn runTests() - startSingleNodeNetwork() + if dirExists(dataDir): + os.removeDir dataDir + + asyncSpawn delayedTests() + + prepareNetwork() + startBeaconNode() waitFor main() From 9736f7231ac2a76891ebfc3da435bb2e59069ec4 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 16 Aug 2022 23:32:35 +0300 Subject: [PATCH 2/5] Properly treat the 'description' field as optional when loading Keystores Fixes #3964 --- AllTests-mainnet.md | 6 +- .../eth2_apis/eth2_rest_serialization.nim | 6 +- beacon_chain/spec/keystore.nim | 15 ++-- .../validators/keystore_management.nim | 3 +- tests/test_keymanager_api.nim | 10 +-- tests/test_keystore.nim | 81 +++++++++++++++++++ 6 files changed, 103 insertions(+), 18 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index f691af3c31..0a6349cd0f 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -314,15 +314,17 @@ OK: 4/4 Fail: 0/4 Skip: 0/4 ```diff + Pbkdf2 errors OK + [PBKDF2] Keystore decryption OK ++ [PBKDF2] Keystore decryption (requireAllFields, allowUnknownFields) OK + [PBKDF2] Keystore encryption OK + [PBKDF2] Network Keystore decryption OK + [PBKDF2] Network Keystore encryption OK + [SCRYPT] Keystore decryption OK ++ [SCRYPT] Keystore decryption (requireAllFields, allowUnknownFields) OK + [SCRYPT] Keystore encryption OK + [SCRYPT] Network Keystore decryption OK + [SCRYPT] Network Keystore encryption OK ``` -OK: 9/9 Fail: 0/9 Skip: 0/9 +OK: 11/11 Fail: 0/11 Skip: 0/11 ## Light client [Preset: mainnet] ```diff + Init from checkpoint OK @@ -652,4 +654,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 OK: 9/9 Fail: 0/9 Skip: 0/9 ---TOTAL--- -OK: 361/366 Fail: 0/366 Skip: 5/366 +OK: 363/368 Fail: 0/368 Skip: 5/368 diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index f93600a3fb..99f23637c8 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -2036,8 +2036,8 @@ proc writeValue*(writer: var JsonWriter[RestJson], value: Keystore) {. raises: [IOError, Defect].} = writer.beginRecord() writer.writeField("crypto", value.crypto) - if not(isNil(value.description)): - writer.writeField("description", value.description[]) + if value.description.isSome: + writer.writeField("description", value.description.get) writer.writeField("pubkey", value.pubkey) writer.writeField("path", string(value.path)) writer.writeField("uuid", value.uuid) @@ -2113,7 +2113,7 @@ proc readValue*(reader: var JsonReader[RestJson], value: var Keystore) {. pubkey: pubkey.get(), path: path.get(), uuid: uuid.get(), - description: if description.isNone(): nil else: newClone(description.get()), + description: description, version: version.get(), ) diff --git a/beacon_chain/spec/keystore.nim b/beacon_chain/spec/keystore.nim index 0155a221b4..6fa3623440 100644 --- a/beacon_chain/spec/keystore.nim +++ b/beacon_chain/spec/keystore.nim @@ -18,13 +18,14 @@ import normalize, # Status libraries stew/[results, bitops2, base10, io2], stew/shims/macros, - eth/keyfile/uuid, blscurve, json_serialization, + eth/keyfile/uuid, blscurve, + json_serialization, json_serialization/std/options, nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt], # Local modules libp2p/crypto/crypto as lcrypto, ./datatypes/base, ./signatures -export base, uri, io2 +export base, uri, io2, options # We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures. import nimcrypto/utils as ncrutils @@ -121,7 +122,7 @@ type Keystore* = object crypto*: Crypto - description*: ref string + description*: Option[string] pubkey*: ValidatorPubKey path*: KeyPath uuid*: string @@ -161,7 +162,7 @@ type NetKeystore* = object crypto*: Crypto - description*: ref string + description*: Option[string] pubkey*: lcrypto.PublicKey uuid*: string version*: int @@ -914,7 +915,8 @@ proc createNetKeystore*(kdfKind: KdfKind, NetKeystore( crypto: cryptoField, pubkey: pubkey, - description: newClone(description), + description: if len(description) > 0: some(description) + else: none[string](), uuid: $uuid, version: 1 ) @@ -938,7 +940,8 @@ proc createKeystore*(kdfKind: KdfKind, crypto: cryptoField, pubkey: pubkey.toPubKey(), path: path, - description: newClone(description), + description: if len(description) > 0: some(description) + else: none[string](), uuid: $uuid, version: 4) diff --git a/beacon_chain/validators/keystore_management.nim b/beacon_chain/validators/keystore_management.nim index 381afd2996..571700e95b 100644 --- a/beacon_chain/validators/keystore_management.nim +++ b/beacon_chain/validators/keystore_management.nim @@ -133,8 +133,7 @@ func init*(T: type KeystoreData, KeystoreData( kind: KeystoreKind.Local, privateKey: privateKey, - description: if keystore.description == nil: none(string) - else: some(keystore.description[]), + description: keystore.description, path: keystore.path, uuid: keystore.uuid, handle: handle, diff --git a/tests/test_keymanager_api.nim b/tests/test_keymanager_api.nim index 64540c924a..5d5ecab5a9 100644 --- a/tests/test_keymanager_api.nim +++ b/tests/test_keymanager_api.nim @@ -545,7 +545,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} = proc `==`(a, b: Keystore): bool = (a.crypto == b.crypto) and (a.pubkey == b.pubkey) and (string(a.path) == string(b.path)) and - (a.description[] == b.description[]) and (a.uuid == b.uuid) and + (a.description == b.description) and (a.uuid == b.uuid) and (a.version == b.version) test "Deserialization test vectors": @@ -611,7 +611,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} = crypto: Crypto(kdf: kdf1, checksum: checksum1, cipher: cipher1), pubkey: ValidatorPubKey.fromHex("0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798").get(), path: KeyPath("m/12381/60/0/0"), - description: newClone("Test keystore"), + description: some "Test keystore", uuid: "a3331c0c-a013-4754-a122-9988b3381fec", version: 4 ) @@ -619,7 +619,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} = crypto: Crypto(kdf: kdf1, checksum: checksum2, cipher: cipher2), pubkey: ValidatorPubKey.fromHex("0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228").get(), path: KeyPath("m/12381/60/0/0"), - description: newClone("Test keystore"), + description: some "Test keystore", uuid: "905dd873-48af-416a-8c80-4283d5af84f9", version: 4 ) @@ -627,7 +627,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} = crypto: Crypto(kdf: kdf2, checksum: checksum3, cipher: cipher3), pubkey: ValidatorPubKey.fromHex("0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798").get(), path: KeyPath("m/12381/60/0/0"), - description: newClone("Test keystore"), + description: some "Test keystore", uuid: "ad1bf334-faaa-4257-8e28-81a45722e87b", version: 4 ) @@ -635,7 +635,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} = crypto: Crypto(kdf: kdf2, checksum: checksum4, cipher: cipher4), pubkey: ValidatorPubKey.fromHex("0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228").get(), path: KeyPath("m/12381/60/0/0"), - description: newClone("Test keystore"), + description: some "Test keystore", uuid: "d91bcde8-8bf5-45c6-b04d-c10d99ae9b6b", version: 4 ) diff --git a/tests/test_keystore.nim b/tests/test_keystore.nim index 893a9e7eeb..3abc433a4a 100644 --- a/tests/test_keystore.nim +++ b/tests/test_keystore.nim @@ -59,6 +59,38 @@ const "version": 4 }""" + scryptVector2 = """{ + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/3141592653/589793238", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 +}""" + pbkdf2Vector = """{ "crypto": { "kdf": { @@ -91,6 +123,37 @@ const "version": 4 }""" + pbkdf2Vector2 = """{ + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 +}""" + pbkdf2NetVector = """{ "crypto":{ "kdf":{ @@ -179,6 +242,15 @@ suite "KeyStorage testing suite": check decrypt.isOk check secret.isEqual(decrypt.get()) + test "[PBKDF2] Keystore decryption (requireAllFields, allowUnknownFields)": + let + keystore = Json.decode(pbkdf2Vector2, Keystore, requireAllFields = true, + allowUnknownFields = true) + decrypt = decryptKeystore(keystore, KeystorePass.init password) + + check decrypt.isOk + check secret.isEqual(decrypt.get()) + test "[SCRYPT] Keystore decryption": let keystore = Json.decode(scryptVector, Keystore) @@ -187,6 +259,15 @@ suite "KeyStorage testing suite": check decrypt.isOk check secret.isEqual(decrypt.get()) + test "[SCRYPT] Keystore decryption (requireAllFields, allowUnknownFields)": + let + keystore = Json.decode(pbkdf2Vector2, Keystore, requireAllFields = true, + allowUnknownFields = true) + decrypt = decryptKeystore(keystore, KeystorePass.init password) + + check decrypt.isOk + check secret.isEqual(decrypt.get()) + test "[PBKDF2] Network Keystore decryption": let keystore = Json.decode(pbkdf2NetVector, NetKeystore) From 385368757266449e785999741bebf0a0d892695a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 17 Aug 2022 00:40:00 +0300 Subject: [PATCH 3/5] close #3884 by adding test --- AllTests-mainnet.md | 5 +- beacon_chain/spec/keystore.nim | 19 ++++- .../validators/keystore_management.nim | 6 +- tests/test_keystore.nim | 83 ++++++++++++++----- tests/test_remote_keystore.nim | 4 +- 5 files changed, 87 insertions(+), 30 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index 0a6349cd0f..f6ec2abc84 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -312,6 +312,7 @@ OK: 3/3 Fail: 0/3 Skip: 0/3 OK: 4/4 Fail: 0/4 Skip: 0/4 ## KeyStorage testing suite ```diff ++ Load Prysm keystore OK + Pbkdf2 errors OK + [PBKDF2] Keystore decryption OK + [PBKDF2] Keystore decryption (requireAllFields, allowUnknownFields) OK @@ -324,7 +325,7 @@ OK: 4/4 Fail: 0/4 Skip: 0/4 + [SCRYPT] Network Keystore decryption OK + [SCRYPT] Network Keystore encryption OK ``` -OK: 11/11 Fail: 0/11 Skip: 0/11 +OK: 12/12 Fail: 0/12 Skip: 0/12 ## Light client [Preset: mainnet] ```diff + Init from checkpoint OK @@ -654,4 +655,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 OK: 9/9 Fail: 0/9 Skip: 0/9 ---TOTAL--- -OK: 363/368 Fail: 0/368 Skip: 5/368 +OK: 364/369 Fail: 0/369 Skip: 5/369 diff --git a/beacon_chain/spec/keystore.nim b/beacon_chain/spec/keystore.nim index 6fa3623440..3a318f1890 100644 --- a/beacon_chain/spec/keystore.nim +++ b/beacon_chain/spec/keystore.nim @@ -783,6 +783,21 @@ proc decryptCryptoField*(crypto: Crypto, func cstringToStr(v: cstring): string = $v +template parseKeystore*(jsonContent: string): Keystore = + Json.decode(jsonContent, Keystore, + requireAllFields = true, + allowUnknownFields = true) + +template parseNetKeystore*(jsonContent: string): NetKeystore = + Json.decode(jsonContent, NetKeystore, + requireAllFields = true, + allowUnknownFields = true) + +template parseRemoteKeystore*(jsonContent: string): RemoteKeystore = + Json.decode(jsonContent, RemoteKeystore, + requireAllFields = false, + allowUnknownFields = true) + proc decryptKeystore*(keystore: Keystore, password: KeystorePass): KsResult[ValidatorPrivKey] = var secret: seq[byte] @@ -796,7 +811,7 @@ proc decryptKeystore*(keystore: Keystore, proc decryptKeystore*(keystore: JsonString, password: KeystorePass): KsResult[ValidatorPrivKey] = - let keystore = try: Json.decode(keystore.string, Keystore) + let keystore = try: parseKeystore(string keystore) except SerializationError as e: return err e.formatMsg("") decryptKeystore(keystore, password) @@ -833,7 +848,7 @@ proc decryptNetKeystore*(nkeystore: NetKeystore, proc decryptNetKeystore*(nkeystore: JsonString, password: KeystorePass): KsResult[lcrypto.PrivateKey] = try: - let keystore = Json.decode(string(nkeystore), NetKeystore) + let keystore = parseNetKeystore(string nkeystore) return decryptNetKeystore(keystore, password) except SerializationError as exc: return err(exc.formatMsg("")) diff --git a/beacon_chain/validators/keystore_management.nim b/beacon_chain/validators/keystore_management.nim index 571700e95b..2d57870b3c 100644 --- a/beacon_chain/validators/keystore_management.nim +++ b/beacon_chain/validators/keystore_management.nim @@ -429,8 +429,7 @@ proc loadRemoteKeystoreImpl(validatorsDir, let buffer = gres.get() let data = try: - Json.decode(buffer, RemoteKeystore, requireAllFields = true, - allowUnknownFields = true) + parseRemoteKeystore(buffer) except SerializationError as e: error "Invalid remote keystore file", key_path = keystorePath, error_msg = e.formatMsg(keystorePath) @@ -475,8 +474,7 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string, let buffer = gres.get() let data = try: - Json.decode(buffer, Keystore, requireAllFields = true, - allowUnknownFields = true) + parseKeystore(buffer) except SerializationError as e: error "Invalid local keystore file", key_path = keystorePath, error_msg = e.formatMsg(keystorePath) diff --git a/tests/test_keystore.nim b/tests/test_keystore.nim index 3abc433a4a..f73904c4c1 100644 --- a/tests/test_keystore.nim +++ b/tests/test_keystore.nim @@ -26,7 +26,8 @@ func isEqual(a, b: ValidatorPrivKey): bool = result = result and pa[i] == pb[i] const - scryptVector = """{ + scryptVector = """ + { "crypto": { "kdf": { "function": "scrypt", @@ -57,9 +58,10 @@ const "path": "m/12381/60/3141592653/589793238", "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", "version": 4 -}""" + }""" - scryptVector2 = """{ + scryptVector2 = """ + { "crypto": { "kdf": { "function": "scrypt", @@ -89,9 +91,10 @@ const "path": "m/12381/60/3141592653/589793238", "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", "version": 4 -}""" + }""" - pbkdf2Vector = """{ + pbkdf2Vector = """ + { "crypto": { "kdf": { "function": "pbkdf2", @@ -121,9 +124,10 @@ const "path": "m/12381/60/0/0", "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", "version": 4 -}""" + }""" - pbkdf2Vector2 = """{ + pbkdf2Vector2 = """ + { "crypto": { "kdf": { "function": "pbkdf2", @@ -152,9 +156,10 @@ const "path": "m/12381/60/0/0", "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", "version": 4 -}""" + }""" - pbkdf2NetVector = """{ + pbkdf2NetVector = """ + { "crypto":{ "kdf":{ "function":"pbkdf2", @@ -185,9 +190,10 @@ const "pubkey":"08021221031873e6f4e1bf837b93493d570653cb219743d4fab0ff468d4e005e1679730b0b", "uuid":"7a053160-1cdf-4faf-a2bb-331e1bc2eb5f", "version":1 -}""" + }""" - scryptNetVector = """{ + scryptNetVector = """ + { "crypto":{ "kdf":{ "function":"scrypt", @@ -219,7 +225,42 @@ const "pubkey":"08021221031873e6f4e1bf837b93493d570653cb219743d4fab0ff468d4e005e1679730b0b", "uuid":"83d77fa3-86cb-466a-af11-eeb338b0e258", "version":1 -}""" + }""" + + prysmKeystore = """ + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "54fc80f6d0676bdae7c968e0d462f90a4e3a028fc7669ef8527e2f74386c9b36", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "3c2540f69cbe7e66c0c4a6e416e99bf0d1056399c21b4c45552561da920871fa", + "params": { + "iv": "98a15bd46d258aceecaeeab25bddf5e2" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "c0abbbbda36e588824865a71b5b34d5a95335fe1077c286d4e9c844f7193c62b" + } + } + }, + "uuid": "39796eb1-2e43-4353-9f13-5211c7ddc58c", + "pubkey": "8ed78a5495b54d5b6cc8bf170534ecb633b9694fba121ca680744fa9633f1b67cc77c045f88a6f97be781fe6c2867646", + "version": 4, + "name": "keystore", + "path": "" + } + """ + password = string.fromBytes hexToSeqByte("7465737470617373776f7264f09f9491") secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736" @@ -234,9 +275,13 @@ suite "KeyStorage testing suite": let secret = ValidatorPrivKey.fromRaw(secretBytes).get let nsecret = init(lcrypto.PrivateKey, secretNetBytes).get + test "Load Prysm keystore": + let keystore = parseKeystore(prysmKeystore) + check keystore.uuid == "39796eb1-2e43-4353-9f13-5211c7ddc58c" + test "[PBKDF2] Keystore decryption": let - keystore = Json.decode(pbkdf2Vector, Keystore) + keystore = parseKeystore(pbkdf2Vector) decrypt = decryptKeystore(keystore, KeystorePass.init password) check decrypt.isOk @@ -244,8 +289,7 @@ suite "KeyStorage testing suite": test "[PBKDF2] Keystore decryption (requireAllFields, allowUnknownFields)": let - keystore = Json.decode(pbkdf2Vector2, Keystore, requireAllFields = true, - allowUnknownFields = true) + keystore = parseKeystore(pbkdf2Vector2) decrypt = decryptKeystore(keystore, KeystorePass.init password) check decrypt.isOk @@ -253,7 +297,7 @@ suite "KeyStorage testing suite": test "[SCRYPT] Keystore decryption": let - keystore = Json.decode(scryptVector, Keystore) + keystore = parseKeystore(scryptVector) decrypt = decryptKeystore(keystore, KeystorePass.init password) check decrypt.isOk @@ -261,8 +305,7 @@ suite "KeyStorage testing suite": test "[SCRYPT] Keystore decryption (requireAllFields, allowUnknownFields)": let - keystore = Json.decode(pbkdf2Vector2, Keystore, requireAllFields = true, - allowUnknownFields = true) + keystore = parseKeystore(pbkdf2Vector2) decrypt = decryptKeystore(keystore, KeystorePass.init password) check decrypt.isOk @@ -270,7 +313,7 @@ suite "KeyStorage testing suite": test "[PBKDF2] Network Keystore decryption": let - keystore = Json.decode(pbkdf2NetVector, NetKeystore) + keystore = parseNetKeystore(pbkdf2NetVector) decrypt = decryptNetKeystore(keystore, KeystorePass.init password) check decrypt.isOk @@ -278,7 +321,7 @@ suite "KeyStorage testing suite": test "[SCRYPT] Network Keystore decryption": let - keystore = Json.decode(scryptNetVector, NetKeystore) + keystore = parseNetKeystore(scryptNetVector) decrypt = decryptNetKeystore(keystore, KeystorePass.init password) check decrypt.isOk diff --git a/tests/test_remote_keystore.nim b/tests/test_remote_keystore.nim index d26129c101..8e5dda0aba 100644 --- a/tests/test_remote_keystore.nim +++ b/tests/test_remote_keystore.nim @@ -22,7 +22,7 @@ suite "Remove keystore testing suite": "remote": "http://127.0.0.1:6000", "type": "web3signer" }""" - let keystore = Json.decode(remoteKeyStores, RemoteKeystore) + let keystore = parseRemoteKeystore(remoteKeyStores) check keystore.pubkey.toHex == "8b9c875fbe539c6429c4fc304675062579ce47fb6b2ac6b6a1ba1188ca123a80affbfe381dbbc8e7f2437709a4c3325c" check keystore.remotes.len == 1 check $keystore.remotes[0].url == "http://127.0.0.1:6000" @@ -41,7 +41,7 @@ suite "Remove keystore testing suite": ], "type": "web3signer" }""" - let keystore = Json.decode(remoteKeyStores, RemoteKeystore) + let keystore = parseRemoteKeystore(remoteKeyStores) check keystore.pubkey.toHex == "8b9c875fbe539c6429c4fc304675062579ce47fb6b2ac6b6a1ba1188ca123a80affbfe381dbbc8e7f2437709a4c3325c" check keystore.remotes.len == 1 check $keystore.remotes[0].url == "http://127.0.0.1:6000" From 7b852471976fd63e87c16ec87d0f2db310e9b5d3 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 17 Aug 2022 03:28:20 +0300 Subject: [PATCH 4/5] Spec-compliant serialization of the slashing data in Keymanager's DeleteKeys response (fixes #3940) --- beacon_chain/rpc/rest_key_management_api.nim | 7 ++-- .../spec/eth2_apis/rest_keymanager_types.nim | 2 +- ncli/resttest-rules.json | 32 ++----------------- ncli/resttest.nim.cfg | 1 + 4 files changed, 10 insertions(+), 32 deletions(-) create mode 100644 ncli/resttest.nim.cfg diff --git a/beacon_chain/rpc/rest_key_management_api.nim b/beacon_chain/rpc/rest_key_management_api.nim index 8b5573f294..a4ea751fb3 100644 --- a/beacon_chain/rpc/rest_key_management_api.nim +++ b/beacon_chain/rpc/rest_key_management_api.nim @@ -217,8 +217,9 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = nodeSPDIR = toSPDIR(host.validatorPool[].slashingProtection) # Hash table to keep the removal status of all keys form request keysAndDeleteStatus = initTable[PubKeyBytes, RequestItemStatus]() + responseSPDIR: SPDIR - response.slashing_protection.metadata = nodeSPDIR.metadata + responseSPDIR.metadata = nodeSPDIR.metadata for index, key in keys: let @@ -249,7 +250,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = for validator in nodeSPDIR.data: keysAndDeleteStatus.withValue(validator.pubkey.PubKeyBytes, foundKeystore) do: - response.slashing_protection.data.add(validator) + responseSPDIR.data.add(validator) if foundKeystore.status == $KeystoreStatus.notFound: foundKeystore.status = $KeystoreStatus.notActive @@ -257,6 +258,8 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = for index, key in keys: response.data.add(keysAndDeleteStatus[key.blob.PubKey0x.PubKeyBytes]) + response.slashing_protection = RestJson.encode(responseSPDIR) + return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys diff --git a/beacon_chain/spec/eth2_apis/rest_keymanager_types.nim b/beacon_chain/spec/eth2_apis/rest_keymanager_types.nim index e56562c725..caae1a9d78 100644 --- a/beacon_chain/spec/eth2_apis/rest_keymanager_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_keymanager_types.nim @@ -56,7 +56,7 @@ type DeleteKeystoresResponse* = object data*: seq[RequestItemStatus] - slashing_protection*: SPDIR + slashing_protection*: string RemoteKeystoreStatus* = object status*: KeystoreStatus diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index d3ed64af91..92312ac603 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -3352,35 +3352,9 @@ "status": "", "message": "" }], - "slashing_protection": - { - "metadata": { - "interchange_format_version": "", - "genesis_validators_root": "" - }, - "data":[{ - "pubkey": "", - "signed_blocks": [ - { - "slot": "", - "signing_root": ""}, - { - "slot": "" - }], - "signed_attestations": [ - { - "source_epoch": "", - "target_epoch": "", - "signing_root": "" - }, - { - "source_epoch": "", - "target_epoch": "" - } - ] - } - ] - }}}] + "slashing_protection": "" + }} + ] } } ] diff --git a/ncli/resttest.nim.cfg b/ncli/resttest.nim.cfg new file mode 100644 index 0000000000..b6304ad908 --- /dev/null +++ b/ncli/resttest.nim.cfg @@ -0,0 +1 @@ +-d:chronicles_colors:off From 8f12f64ee64ecc82de9bf9058950d53f8664665d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 19 Aug 2022 03:35:24 +0300 Subject: [PATCH 5/5] try imposing a deadline on the keymanager tests --- tests/test_keymanager_api.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_keymanager_api.nim b/tests/test_keymanager_api.nim index 5d5ecab5a9..8c91b33243 100644 --- a/tests/test_keymanager_api.nim +++ b/tests/test_keymanager_api.nim @@ -1285,8 +1285,9 @@ proc delayedTests {.async.} = await sleepAsync(2.seconds) - await runTests(beaconNodeKeymanager) - + let deadline = sleepAsync(10.minutes) + await runTests(beaconNodeKeymanager) or deadline + # TODO # This tests showed flaky behavior on a single Windows CI host # Re-enable it in a follow-up PR