Keymanager API for the validator client (#3976)

* Keymanager API for the validator client
* Properly treat the 'description' field as optional when loading Keystores
* Spec-compliant serialization of the slashing data in Keymanager's DeleteKeys response ()

Fixes #3940
Fixes #3964
Closes #3884 by adding test
This commit is contained in:
zah 2022-08-19 13:30:07 +03:00 committed by GitHub
parent a7192f5d6c
commit fca20e08d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1136 additions and 660 deletions

View File

@ -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 [Preset: mainnet] OK
+ Invalid Authorization Header [Preset: mainnet] OK
+ Invalid Authorization Token [Preset: mainnet] OK
+ Missing Authorization header [Preset: mainnet] OK
+ 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
## ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys [Preset: mainnet]
## ImportKeystores requests [Validator Client] [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
+ 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 [Beacon Node] [Preset: mainnet]
```diff
+ 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
@ -268,17 +312,20 @@ 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
+ [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: 12/12 Fail: 0/12 Skip: 0/12
## Light client [Preset: mainnet]
```diff
+ Init from checkpoint OK
@ -302,20 +349,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 [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]
## ListKeys 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
## ListRemoteKeys 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
## ListRemoteKeys 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
## Message signatures
@ -358,7 +421,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 +655,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: 364/369 Fail: 0/369 Skip: 5/369

View File

@ -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

View File

@ -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,

View File

@ -10,7 +10,6 @@ when (NimMajor, NimMinor) < (1, 4):
else:
{.push raises: [].}
import chronicles
import stew/io2
import spec/keystore

View File

@ -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.} = ""

View File

@ -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(
@ -1568,7 +1514,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"
@ -1588,12 +1534,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()

View File

@ -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)

View File

@ -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"
await vc.shutdownMetrics()
return
info "Initializing slashing protection", path = vc.config.validatorsDir()
vc.attachedValidators.slashingProtection =
SlashingProtectionDB.init(
vc.beaconGenesis.genesis_validators_root,
vc.config.validatorsDir(), "slashing_protection"
)
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()
raise newException(ValidatorClientError,
"Could not initialize local validators")
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)
let
config = makeBannerAndConfig("Nimbus validator client " & fullVersionStr,
ValidatorClientConf)
# Single RNG instance for the application - will be seeded on construction
# and avoid using system resources (such as urandom) after that
rng = crypto.newRng()
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
if len(beaconNodes) == 0:
# This should not happen, thanks to defaults in `conf.nim`
fatal "Not enough beacon nodes in command line"
quit 1
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"
waitFor runValidatorClient(config, rng)

View File

@ -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

View File

@ -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:

View File

@ -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,16 +214,18 @@ 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]()
responseSPDIR: SPDIR
response.slashing_protection.metadata = nodeSPDIR.metadata
responseSPDIR.metadata = nodeSPDIR.metadata
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,29 +248,33 @@ 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:
response.slashing_protection.data.add(validator)
keysAndDeleteStatus.withValue(validator.pubkey.PubKeyBytes,
foundKeystore) do:
responseSPDIR.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])
response.slashing_protection = RestJson.encode(responseSPDIR)
return RestApiResponse.jsonResponsePlain(response)
# 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 +297,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 +319,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 +348,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 +363,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 +374,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 +391,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 +424,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 +444,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)

View File

@ -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:

View File

@ -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(),
)

View File

@ -56,7 +56,7 @@ type
DeleteKeystoresResponse* = object
data*: seq[RequestItemStatus]
slashing_protection*: SPDIR
slashing_protection*: string
RemoteKeystoreStatus* = object
status*: KeystoreStatus

View File

@ -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",

View File

@ -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
@ -782,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]
@ -795,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("<keystore>")
decryptKeystore(keystore, password)
@ -832,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("<keystore>"))
@ -914,7 +930,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 +955,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)

View File

@ -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):

View File

@ -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)

View File

@ -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
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, 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
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 =

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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 ""
@ -89,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,
@ -386,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)
@ -432,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)
@ -552,13 +593,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 +606,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 +1147,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 +1175,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 +1185,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 +1201,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 +1252,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 +1270,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 +1279,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 +1298,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

View File

@ -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"

View File

@ -70,7 +70,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:
@ -88,8 +88,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:
@ -105,7 +108,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,
@ -120,8 +122,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) =
@ -149,7 +151,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",
@ -382,7 +384,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,
@ -1169,7 +1171,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(

View File

@ -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": ""
}}
]
}
}
]

1
ncli/resttest.nim.cfg Normal file
View File

@ -0,0 +1 @@
-d:chronicles_colors:off

View File

@ -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},$!"

View File

@ -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
@ -398,7 +545,7 @@ proc runTests {.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":
@ -464,7 +611,7 @@ proc runTests {.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
)
@ -472,7 +619,7 @@ proc runTests {.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
)
@ -480,7 +627,7 @@ proc runTests {.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
)
@ -488,7 +635,7 @@ proc runTests {.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
)
@ -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]:
for i in 0 ..< 18:
check:
responseJson1["data"][i]["status"].getStr() == "duplicate"
responseJson1["data"][i]["message"].getStr() == ""
for i in 8 ..< 16:
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,31 @@ 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)
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
# await runTests(validatorClientKeymanager)
bnStatus = BeaconNodeStatus.Stopping
proc main() {.async.} =
asyncSpawn runTests()
startSingleNodeNetwork()
if dirExists(dataDir):
os.removeDir dataDir
asyncSpawn delayedTests()
prepareNetwork()
startBeaconNode()
waitFor main()

View File

@ -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,43 @@ const
"path": "m/12381/60/3141592653/589793238",
"uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f",
"version": 4
}"""
}"""
pbkdf2Vector = """{
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": {
"function": "pbkdf2",
@ -89,9 +124,42 @@ const
"path": "m/12381/60/0/0",
"uuid": "64625def-3331-4eea-ab6f-782f3ed16a83",
"version": 4
}"""
}"""
pbkdf2NetVector = """{
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":{
"function":"pbkdf2",
@ -122,9 +190,10 @@ const
"pubkey":"08021221031873e6f4e1bf837b93493d570653cb219743d4fab0ff468d4e005e1679730b0b",
"uuid":"7a053160-1cdf-4faf-a2bb-331e1bc2eb5f",
"version":1
}"""
}"""
scryptNetVector = """{
scryptNetVector = """
{
"crypto":{
"kdf":{
"function":"scrypt",
@ -156,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"
@ -171,9 +275,21 @@ 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
check secret.isEqual(decrypt.get())
test "[PBKDF2] Keystore decryption (requireAllFields, allowUnknownFields)":
let
keystore = parseKeystore(pbkdf2Vector2)
decrypt = decryptKeystore(keystore, KeystorePass.init password)
check decrypt.isOk
@ -181,7 +297,15 @@ 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
check secret.isEqual(decrypt.get())
test "[SCRYPT] Keystore decryption (requireAllFields, allowUnknownFields)":
let
keystore = parseKeystore(pbkdf2Vector2)
decrypt = decryptKeystore(keystore, KeystorePass.init password)
check decrypt.isOk
@ -189,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
@ -197,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

View File

@ -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"