Remote KeyManager API and number of fixes/tests for KeyManager API (#3360)

* Initial commit.

* Fix current test suite.

* Fix keymanager api test.

* Fix wss_sim.

* Add more keystore_management tests.

* Recover deleted isEmptyDir().

* Add `HttpHostUri` distinct type.
Move keymanager calls away from rest_beacon_calls to rest_keymanager_calls.
Add REST serialization of RemoteKeystore and Keystore object.
Add tests for Remote Keystore management API.
Add tests for Keystore management API (Add keystore).
Fix serialzation issues.

* Fix test to use HttpHostUri instead of Uri.

* Add links to specification in comments.

* Remove debugging echoes.
This commit is contained in:
Eugene Kabanov 2022-02-07 22:36:09 +02:00 committed by GitHub
parent c7abc97545
commit 40c77e5928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2223 additions and 304 deletions

View File

@ -43,7 +43,7 @@ import
blockchain_dag, block_quarantine, block_clearance, attestation_pool,
sync_committee_msg_pool, exit_pool, spec_cache],
./eth1/eth1_monitor,
./spec/eth2_apis/rest_beacon_calls
./spec/eth2_apis/[rest_beacon_calls, rest_common]
from eth/common/eth_types import BlockHashOrNumber

View File

@ -4,34 +4,39 @@
# * 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, strutils]
import std/[tables, os, strutils, uri]
import chronos, chronicles, confutils,
stew/[base10, results, io2], bearssl, blscurve
import ".."/validators/slashing_protection
import ".."/[conf, version, filepath, beacon_node]
import ".."/spec/[keystore, crypto]
import ".."/rpc/rest_utils
import ".."/validators/[keystore_management, validator_pool]
import ".."/validators/[keystore_management, validator_pool, validator_duties]
import ".."/spec/eth2_apis/rest_keymanager_types
export
rest_utils,
results
export rest_utils, results
proc listValidators*(validatorsDir,
secretsDir: string): seq[KeystoreInfo]
{.raises: [Defect].} =
proc listLocalValidators*(node: BeaconNode): seq[KeystoreInfo] {.
raises: [Defect].} =
var validators: seq[KeystoreInfo]
for item in node.attachedValidators[].items():
if item.kind == ValidatorKind.Local:
validators.add KeystoreInfo(
validating_pubkey: item.pubkey,
derivation_path: string(item.data.path),
readonly: false
)
validators
try:
for el in listLoadableKeystores(validatorsDir, secretsDir, true):
validators.add KeystoreInfo(validating_pubkey: el.pubkey,
derivation_path: el.path.string,
readonly: false)
except OSError as err:
error "Failure to list the validator directories",
validatorsDir, secretsDir, err = err.msg
proc listRemoteValidators*(node: BeaconNode): seq[RemoteKeystoreInfo] {.
raises: [Defect].} =
var validators: seq[RemoteKeystoreInfo]
for item in node.attachedValidators[].items():
if item.kind == ValidatorKind.Remote:
validators.add RemoteKeystoreInfo(
pubkey: item.pubkey,
url: HttpHostUri(item.data.remoteUrl)
)
validators
proc checkAuthorization*(request: HttpRequestRef,
@ -49,6 +54,14 @@ proc checkAuthorization*(request: HttpRequestRef,
else:
return err noAuthorizationHeader
proc validateUri*(url: string): Result[Uri, cstring] =
let surl = parseUri(url)
if surl.scheme notin ["http", "https"]:
return err("Incorrect URL scheme")
if len(surl.hostname) == 0:
return err("Empty URL hostname")
ok(surl)
proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys
router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse:
@ -56,9 +69,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
let response = GetKeystoresResponse(
data: listValidators(node.config.validatorsDir(),
node.config.secretsDir()))
let response = GetKeystoresResponse(data: listLocalValidators(node))
return RestApiResponse.jsonResponsePlain(response)
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores
@ -78,34 +89,39 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
$dres.error())
dres.get()
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
request.slashing_protection.metadata.genesis_validators_root.Eth2Digest:
return RestApiResponse.jsonError(
Http400,
"The slashing protection database and imported file refer to different blockchains.")
if request.slashing_protection.isSome():
let slashing_protection = request.slashing_protection.get()
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
slashing_protection.metadata.genesis_validators_root.Eth2Digest:
return RestApiResponse.jsonError(Http400,
"The slashing protection database and imported file refer to " &
"different blockchains.")
let res = inclSPDIR(node.attachedValidators.slashingProtection,
slashing_protection)
if res == siFailure:
return RestApiResponse.jsonError(Http500,
"Internal server error; Failed to import slashing protection data")
var
response: PostKeystoresResponse
inclRes = inclSPDIR(node.attachedValidators.slashingProtection,
request.slashing_protection)
if inclRes == siFailure:
return RestApiResponse.jsonError(
Http500,
"Internal server error; Failed to import slashing protection data")
var response: PostKeystoresResponse
for index, item in request.keystores.pairs():
let res = importKeystore(node.attachedValidators[], node.network.rng[],
node.config, item, request.passwords[index])
if res.isErr():
response.data.add(RequestItemStatus(status: $KeystoreStatus.error,
message: $res.error()))
elif res.value() == AddValidatorStatus.existingArtifacts:
response.data.add(RequestItemStatus(status: $KeystoreStatus.duplicate))
let failure = res.error()
case failure.status
of AddValidatorStatus.failed:
response.data.add(
RequestItemStatus(status: $KeystoreStatus.error,
message: failure.message))
of AddValidatorStatus.existingArtifacts:
response.data.add(
RequestItemStatus(status: $KeystoreStatus.duplicate))
else:
response.data.add(RequestItemStatus(status: $KeystoreStatus.imported))
node.addLocalValidators([res.get()])
response.data.add(
RequestItemStatus(status: $KeystoreStatus.imported))
return RestApiResponse.jsonResponsePlain(response)
@ -136,19 +152,21 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
for index, key in keys.pairs():
let
res = removeValidator(node.attachedValidators[], node.config, key)
res = removeValidator(node.attachedValidators[], node.config, key,
KeystoreKind.Local)
pubkey = key.blob.PubKey0x.PubKeyBytes
if res.isOk:
case res.value()
of RemoveValidatorStatus.deleted:
keysAndDeleteStatus.add(pubkey,
RequestItemStatus(status: $KeystoreStatus.deleted))
keysAndDeleteStatus.add(
pubkey, RequestItemStatus(status: $KeystoreStatus.deleted))
# At first all keys with status missing directory after removal receive status 'not_found'
of RemoveValidatorStatus.missingDir:
keysAndDeleteStatus.add(pubkey,
RequestItemStatus(status: $KeystoreStatus.notFound))
# At first all keys with status missing directory after removal receive
# status 'not_found'
of RemoveValidatorStatus.notFound:
keysAndDeleteStatus.add(
pubkey, RequestItemStatus(status: $KeystoreStatus.notFound))
else:
keysAndDeleteStatus.add(pubkey,
RequestItemStatus(status: $KeystoreStatus.error,
@ -169,6 +187,96 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonResponsePlain(response)
# https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys
router.api(MethodGet, "/api/eth/v1/remotekey") do () -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
let response = GetRemoteKeystoresResponse(data: listRemoteValidators(node))
return RestApiResponse.jsonResponsePlain(response)
# https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ImportRemoteKeys
router.api(MethodPost, "/api/eth/v1/remotekey") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
let dres = decodeBody(ImportRemoteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
$dres.error())
dres.get().remote_keys
var response: PostKeystoresResponse
for index, key in keys.pairs():
let keystore = RemoteKeystore(
version: 1'u64, remoteType: RemoteSignerType.Web3Signer,
pubkey: key.pubkey, remote: key.url
)
let res = importKeystore(node.attachedValidators[], node.config,
keystore)
if res.isErr():
case res.error().status
of AddValidatorStatus.failed:
response.data.add(
RequestItemStatus(status: $KeystoreStatus.error,
message: $res.error().message))
of AddValidatorStatus.existingArtifacts:
response.data.add(
RequestItemStatus(status: $KeystoreStatus.duplicate))
else:
node.addRemoteValidators([res.get()])
response.data.add(
RequestItemStatus(status: $KeystoreStatus.imported))
return RestApiResponse.jsonResponsePlain(response)
# https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys
router.api(MethodDelete, "/api/eth/v1/remotekey") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
$dres.error())
dres.get().pubkeys
let response =
block:
var resp: DeleteRemoteKeystoresResponse
for index, key in keys.pairs():
let res = removeValidator(node.attachedValidators[], node.config, key,
KeystoreKind.Remote)
if res.isOk:
case res.value()
of RemoveValidatorStatus.deleted:
resp.data.add(
RemoteKeystoreStatus(status: KeystoreStatus.deleted))
of RemoveValidatorStatus.notFound:
resp.data.add(
RemoteKeystoreStatus(status: KeystoreStatus.notFound))
else:
resp.data.add(
RemoteKeystoreStatus(status: KeystoreStatus.error,
message: some($res.error())))
resp
return RestApiResponse.jsonResponsePlain(response)
router.redirect(
MethodGet,
"/eth/v1/keystores",
@ -183,3 +291,18 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
MethodPost,
"/eth/v1/keystores/delete",
"/api/eth/v1/keystores/delete")
router.redirect(
MethodGet,
"/eth/v1/remotekey",
"/api/eth/v1/remotekey")
router.redirect(
MethodPost,
"/eth/v1/remotekey",
"/api/eth/v1/remotekey")
router.redirect(
MethodDelete,
"/eth/v1/remotekey",
"/api/eth/v1/remotekey")

View File

@ -6,15 +6,17 @@
import std/typetraits
import stew/[assign2, results, base10, byteutils], presto/common,
libp2p/peerid,
serialization, json_serialization, json_serialization/std/[options, net, sets]
import ".."/[eth2_ssz_serialization, forks],
libp2p/peerid, serialization, json_serialization,
json_serialization/std/[options, net, sets]
import ".."/[eth2_ssz_serialization, forks, keystore],
".."/datatypes/[phase0, altair, bellatrix],
".."/../validators/slashing_protection_common,
"."/[rest_types, rest_keymanager_types]
import nimcrypto/utils as ncrutils
export
eth2_ssz_serialization, results, peerid, common, serialization,
json_serialization, options, net, sets, rest_types
json_serialization, options, net, sets, rest_types, slashing_protection_common
from web3/ethtypes import BlockHash
export ethtypes.BlockHash
@ -54,7 +56,8 @@ type
SignedVoluntaryExit |
Web3SignerRequest |
KeystoresAndSlashingProtection |
DeleteKeystoresBody
DeleteKeystoresBody |
ImportRemoteKeystoresBody
EncodeArrays* =
seq[ValidatorIndex] |
@ -63,7 +66,8 @@ type
seq[RestCommitteeSubscription] |
seq[RestSyncCommitteeSubscription] |
seq[RestSyncCommitteeMessage] |
seq[RestSignedContributionAndProof]
seq[RestSignedContributionAndProof] |
seq[RemoteKeystoreInfo]
DecodeTypes* =
DataEnclosedObject |
@ -71,6 +75,7 @@ type
DataRootEnclosedObject |
GetBlockV2Response |
GetKeystoresResponse |
GetRemoteKeystoresResponse |
GetStateV2Response |
GetStateForkResponse |
ProduceBlockResponseV2 |
@ -1265,6 +1270,331 @@ proc readValue*(reader: var JsonReader[RestJson],
syncCommitteeContributionAndProof: data
)
## RemoteKeystoreStatus
proc writeValue*(writer: var JsonWriter[RestJson],
value: RemoteKeystoreStatus) {.raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("status", $value.status)
if value.message.isSome():
writer.writeField("message", value.message.get())
writer.endRecord()
proc readValue*(reader: var JsonReader[RestJson],
value: var RemoteKeystoreStatus) {.
raises: [IOError, SerializationError, Defect].} =
var message: Option[string]
var status: Option[KeystoreStatus]
for fieldName in readObjectFields(reader):
case fieldName
of "message":
if message.isSome():
reader.raiseUnexpectedField("Multiple `message` fields found",
"RemoteKeystoreStatus")
message = some(reader.readValue(string))
of "status":
if status.isSome():
reader.raiseUnexpectedField("Multiple `status` fields found",
"RemoteKeystoreStatus")
let res = reader.readValue(string)
status = some(
case res
of "error":
KeystoreStatus.error
of "not_active":
KeystoreStatus.notActive
of "not_found":
KeystoreStatus.notFound
of "deleted":
KeystoreStatus.deleted
of "duplicate":
KeystoreStatus.duplicate
of "imported":
KeystoreStatus.imported
else:
reader.raiseUnexpectedValue("Invalid `status` value")
)
else:
# We ignore all unknown fields.
discard
if status.isNone():
reader.raiseUnexpectedValue("Field `status` is missing")
value = RemoteKeystoreStatus(status: status.get(), message: message)
## ScryptSalt
proc readValue*(reader: var JsonReader[RestJson], value: var ScryptSalt) {.
raises: [SerializationError, IOError, Defect].} =
let res = ncrutils.fromHex(reader.readValue(string))
if len(res) == 0:
reader.raiseUnexpectedValue("Invalid scrypt salt value")
value = ScryptSalt(res)
## Pbkdf2Params
proc writeValue*(writer: var JsonWriter[RestJson], value: Pbkdf2Params) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("dklen", JsonString(Base10.toString(value.dklen)))
writer.writeField("c", JsonString(Base10.toString(value.c)))
writer.writeField("prf", value.prf)
writer.writeField("salt", value.salt)
writer.endRecord()
proc readValue*(reader: var JsonReader[RestJson], value: var Pbkdf2Params) {.
raises: [SerializationError, IOError, Defect].} =
var
dklen: Option[uint64]
c: Option[uint64]
prf: Option[PrfKind]
salt: Option[Pbkdf2Salt]
for fieldName in readObjectFields(reader):
case fieldName
of "dklen":
if dklen.isSome():
reader.raiseUnexpectedField("Multiple `dklen` fields found",
"Pbkdf2Params")
dklen = some(reader.readValue(uint64))
of "c":
if c.isSome():
reader.raiseUnexpectedField("Multiple `c` fields found",
"Pbkdf2Params")
c = some(reader.readValue(uint64))
of "prf":
if prf.isSome():
reader.raiseUnexpectedField("Multiple `prf` fields found",
"Pbkdf2Params")
prf = some(reader.readValue(PrfKind))
of "salt":
if salt.isSome():
reader.raiseUnexpectedField("Multiple `salt` fields found",
"Pbkdf2Params")
salt = some(reader.readValue(Pbkdf2Salt))
else:
# Ignore unknown field names.
discard
if dklen.isNone():
reader.raiseUnexpectedValue("Field `dklen` is missing")
if c.isNone():
reader.raiseUnexpectedValue("Field `c` is missing")
if prf.isNone():
reader.raiseUnexpectedValue("Field `prf` is missing")
if salt.isNone():
reader.raiseUnexpectedValue("Field `salt` is missing")
value = Pbkdf2Params(
dklen: dklen.get(),
c: c.get(),
prf: prf.get(),
salt: salt.get()
)
## ScryptParams
proc writeValue*(writer: var JsonWriter[RestJson], value: ScryptParams) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("dklen", JsonString(Base10.toString(value.dklen)))
writer.writeField("n", JsonString(Base10.toString(uint64(value.n))))
writer.writeField("p", JsonString(Base10.toString(uint64(value.p))))
writer.writeField("r", JsonString(Base10.toString(uint64(value.r))))
writer.writeField("salt", value.salt)
writer.endRecord()
proc readValue*(reader: var JsonReader[RestJson], value: var ScryptParams) {.
raises: [SerializationError, IOError, Defect].} =
var
dklen: Option[uint64]
n, p, r: Option[int]
salt: Option[ScryptSalt]
for fieldName in readObjectFields(reader):
case fieldName
of "dklen":
if dklen.isSome():
reader.raiseUnexpectedField("Multiple `dklen` fields found",
"ScryptParams")
dklen = some(reader.readValue(uint64))
of "n":
if n.isSome():
reader.raiseUnexpectedField("Multiple `n` fields found",
"ScryptParams")
let res = reader.readValue(int)
if res < 0:
reader.raiseUnexpectedValue("Unexpected negative `n` value")
n = some(res)
of "p":
if p.isSome():
reader.raiseUnexpectedField("Multiple `p` fields found",
"ScryptParams")
let res = reader.readValue(int)
if res < 0:
reader.raiseUnexpectedValue("Unexpected negative `p` value")
p = some(res)
of "r":
if r.isSome():
reader.raiseUnexpectedField("Multiple `r` fields found",
"ScryptParams")
let res = reader.readValue(int)
if res < 0:
reader.raiseUnexpectedValue("Unexpected negative `r` value")
r = some(res)
of "salt":
if salt.isSome():
reader.raiseUnexpectedField("Multiple `salt` fields found",
"ScryptParams")
salt = some(reader.readValue(ScryptSalt))
else:
# Ignore unknown field names.
discard
if dklen.isNone():
reader.raiseUnexpectedValue("Field `dklen` is missing")
if n.isNone():
reader.raiseUnexpectedValue("Field `n` is missing")
if p.isNone():
reader.raiseUnexpectedValue("Field `p` is missing")
if r.isNone():
reader.raiseUnexpectedValue("Field `r` is missing")
if salt.isNone():
reader.raiseUnexpectedValue("Field `salt` is missing")
value = ScryptParams(
dklen: dklen.get(),
n: n.get(), p: p.get(), r: r.get(),
salt: salt.get()
)
## Keystore
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[])
writer.writeField("pubkey", value.pubkey)
writer.writeField("path", string(value.path))
writer.writeField("uuid", value.uuid)
writer.writeField("version", JsonString(
Base10.toString(uint64(value.version))))
writer.endRecord()
proc readValue*(reader: var JsonReader[RestJson], value: var Keystore) {.
raises: [SerializationError, IOError, Defect].} =
var
crypto: Option[Crypto]
description: Option[string]
pubkey: Option[ValidatorPubKey]
path: Option[KeyPath]
uuid: Option[string]
version: Option[int]
for fieldName in readObjectFields(reader):
case fieldName
of "crypto":
if crypto.isSome():
reader.raiseUnexpectedField("Multiple `crypto` fields found",
"Keystore")
crypto = some(reader.readValue(Crypto))
of "description":
let res = reader.readValue(string)
if description.isSome():
description = some(description.get() & "\n" & res)
else:
description = some(res)
of "pubkey":
if pubkey.isSome():
reader.raiseUnexpectedField("Multiple `pubkey` fields found",
"Keystore")
pubkey = some(reader.readValue(ValidatorPubKey))
of "path":
if path.isSome():
reader.raiseUnexpectedField("Multiple `path` fields found",
"Keystore")
let res = validateKeyPath(reader.readValue(string))
if res.isErr():
reader.raiseUnexpectedValue("Invalid `path` value")
path = some(res.get())
of "uuid":
if uuid.isSome():
reader.raiseUnexpectedField("Multiple `uuid` fields found",
"Keystore")
uuid = some(reader.readValue(string))
of "version":
if version.isSome():
reader.raiseUnexpectedField("Multiple `version` fields found",
"Keystore")
let res = reader.readValue(int)
if res < 0:
reader.raiseUnexpectedValue("Unexpected negative `version` value")
version = some(res)
else:
# Ignore unknown field names.
discard
if crypto.isNone():
reader.raiseUnexpectedValue("Field `crypto` is missing")
if pubkey.isNone():
reader.raiseUnexpectedValue("Field `pubkey` is missing")
if path.isNone():
reader.raiseUnexpectedValue("Field `path` is missing")
if uuid.isNone():
reader.raiseUnexpectedValue("Field `uuid` is missing")
if version.isNone():
reader.raiseUnexpectedValue("Field `version` is missing")
value = Keystore(
crypto: crypto.get(),
pubkey: pubkey.get(),
path: path.get(),
uuid: uuid.get(),
description: if description.isNone(): nil else: newClone(description.get()),
version: version.get(),
)
## KeystoresAndSlashingProtection
proc writeValue*(writer: var JsonWriter[RestJson],
value: KeystoresAndSlashingProtection) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("keystores", value.keystores)
writer.writeField("passwords", value.passwords)
if value.slashing_protection.isSome():
writer.writeField("slashing_protection", value.slashing_protection)
writer.endRecord()
proc readValue*(reader: var JsonReader[RestJson],
value: var KeystoresAndSlashingProtection) {.
raises: [SerializationError, IOError, Defect].} =
var
keystores: seq[Keystore]
passwords: seq[string]
slashing: Option[SPDIR]
for fieldName in readObjectFields(reader):
case fieldName
of "keystores":
keystores = reader.readValue(seq[Keystore])
of "passwords":
passwords = reader.readValue(seq[string])
of "slashing_protection":
if slashing.isSome():
reader.raiseUnexpectedField(
"Multiple `slashing_protection` fields found",
"KeystoresAndSlashingProtection")
slashing = some(reader.readValue(SPDIR))
else:
# Ignore unknown field names.
discard
if len(keystores) == 0:
reader.raiseUnexpectedValue("Missing `keystores` value")
if len(passwords) == 0:
reader.raiseUnexpectedValue("Missing `passwords` value")
value = KeystoresAndSlashingProtection(
keystores: keystores, passwords: passwords, slashing_protection: slashing
)
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
try:
ok(Eth2Digest(data: hexToByteArray[32](value)))
@ -1277,8 +1607,8 @@ proc decodeBody*[T](t: typedesc[T],
return err("Unsupported content type")
let data =
try:
RestJson.decode(cast[string](body.data), T)
except SerializationError:
RestJson.decode(body.data, T)
except SerializationError as exc:
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")

View File

@ -11,14 +11,10 @@ import
".."/".."/validators/slashing_protection_common,
".."/datatypes/[phase0, altair],
".."/[helpers, forks, keystore, eth2_ssz_serialization],
"."/[rest_types, rest_keymanager_types, eth2_rest_serialization]
"."/[rest_types, rest_common, eth2_rest_serialization]
export chronos, client, rest_types, eth2_rest_serialization
UUID.serializesAsBaseIn RestJson
KeyPath.serializesAsBaseIn RestJson
WalletName.serializesAsBaseIn RestJson
proc getGenesis*(): RestResponse[GetGenesisResponse] {.
rest, endpoint: "/eth/v1/beacon/genesis",
meth: MethodGet.}
@ -119,24 +115,6 @@ proc getBlockPlain*(block_id: BlockIdent): RestPlainResponse {.
meth: MethodGet.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
proc raiseGenericError*(resp: RestPlainResponse)
{.noreturn, raises: [RestError, Defect].} =
let error =
block:
let res = decodeBytes(RestGenericError, resp.data, resp.contentType)
if res.isErr():
let msg = "Incorrect response error format (" & $resp.status &
") [" & $res.error() & "]"
raise newException(RestError, msg)
res.get()
let msg = "Error response (" & $resp.status & ") [" & error.message & "]"
raise newException(RestError, msg)
proc raiseUnknownStatusError(resp: RestPlainResponse)
{.noreturn, raises: [RestError, Defect].} =
let msg = "Unknown response status error (" & $resp.status & ")"
raise newException(RestError, msg)
proc getBlock*(client: RestClientRef, block_id: BlockIdent,
restAccept = preferSSZ): Future[ForkedSignedBeaconBlock] {.async.} =
# TODO restAccept should be "" by default, but for some reason that doesn't
@ -292,35 +270,3 @@ proc submitPoolVoluntaryExit*(body: SignedVoluntaryExit): RestPlainResponse {.
rest, endpoint: "/eth/v1/beacon/pool/voluntary_exits",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit
proc listKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores",
meth: MethodGet.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys
proc importKeystoresPlain*(body: KeystoresAndSlashingProtection): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores
proc deleteKeysPlain*(body: DeleteKeystoresBody): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores/delete",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/DeleteKeys
proc listKeys*(client: RestClientRef,
token: string): Future[GetKeystoresResponse] {.async.} =
let resp = await client.listKeysPlain(
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 200:
let keystoresRes = decodeBytes(
GetKeystoresResponse, resp.data, resp.contentType)
if keystoresRes.isErr():
raise newException(RestError, $keystoresRes.error)
return keystoresRes.get()
of 401, 403, 500:
raiseGenericError(resp)
else:
raiseUnknownStatusError(resp)

View File

@ -10,10 +10,12 @@ import
chronos, presto/client,
"."/[
rest_beacon_calls, rest_config_calls, rest_debug_calls,
rest_node_calls, rest_validator_calls
rest_node_calls, rest_validator_calls, rest_keymanager_calls,
rest_common
]
export
chronos, client,
rest_beacon_calls, rest_config_calls, rest_debug_calls,
rest_node_calls, rest_validator_calls
rest_node_calls, rest_validator_calls, rest_keymanager_calls,
rest_common

View File

@ -0,0 +1,31 @@
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * 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.
{.push raises: [Defect].}
import
chronos, presto/client, chronicles,
"."/[rest_types, eth2_rest_serialization]
export chronos, client, rest_types, eth2_rest_serialization
proc raiseGenericError*(resp: RestPlainResponse) {.
noreturn, raises: [RestError, Defect].} =
let error =
block:
let res = decodeBytes(RestGenericError, resp.data, resp.contentType)
if res.isErr():
let msg = "Incorrect response error format (" & $resp.status &
") [" & $res.error() & "]"
raise newException(RestError, msg)
res.get()
let msg = "Error response (" & $resp.status & ") [" & error.message & "]"
raise newException(RestError, msg)
proc raiseUnknownStatusError*(resp: RestPlainResponse) {.
noreturn, raises: [RestError, Defect].} =
let msg = "Unknown response status error (" & $resp.status & ")"
raise newException(RestError, msg)

View File

@ -0,0 +1,88 @@
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * 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.
{.push raises: [Defect].}
import
chronos, presto/client, chronicles,
".."/".."/validators/slashing_protection_common,
".."/datatypes/[phase0, altair],
".."/[helpers, forks, keystore, eth2_ssz_serialization],
"."/[rest_types, rest_common, rest_keymanager_types, eth2_rest_serialization]
export chronos, client, rest_types, eth2_rest_serialization,
rest_keymanager_types
UUID.serializesAsBaseIn RestJson
KeyPath.serializesAsBaseIn RestJson
WalletName.serializesAsBaseIn RestJson
proc listKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores",
meth: MethodGet.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys
proc importKeystoresPlain*(body: KeystoresAndSlashingProtection
): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores
proc deleteKeysPlain*(body: DeleteKeystoresBody): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores/delete",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Keymanager/DeleteKeys
proc listKeys*(client: RestClientRef,
token: string): Future[GetKeystoresResponse] {.async.} =
let resp = await client.listKeysPlain(
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 200:
let keystoresRes = decodeBytes(
GetKeystoresResponse, resp.data, resp.contentType)
if keystoresRes.isErr():
raise newException(RestError, $keystoresRes.error)
return keystoresRes.get()
of 401, 403, 500:
raiseGenericError(resp)
else:
raiseUnknownStatusError(resp)
proc listRemoteKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/remotekey",
meth: MethodGet.}
## https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys
proc importRemoteKeysPlain*(body: ImportRemoteKeystoresBody
): RestPlainResponse {.
rest, endpoint: "/eth/v1/remotekey",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ImportRemoteKeys
proc deleteRemoteKeysPlain*(body: DeleteKeystoresBody): RestPlainResponse {.
rest, endpoint: "/eth/v1/remotekey",
meth: MethodDelete.}
## https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys
proc listRemoteKeys*(client: RestClientRef,
token: string): Future[GetRemoteKeystoresResponse] {.
async.} =
let resp = await client.listRemoteKeysPlain(
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 200:
let res = decodeBytes(GetRemoteKeystoresResponse, resp.data,
resp.contentType)
if res.isErr():
raise newException(RestError, $res.error())
return res.get()
of 401, 403, 500:
raiseGenericError(resp)
else:
raiseUnknownStatusError(resp)

View File

@ -1,5 +1,5 @@
import
std/[tables, strutils],
std/[tables, strutils, uri],
".."/[crypto, keystore],
../../validators/slashing_protection_common
@ -9,6 +9,10 @@ type
derivation_path*: string
readonly*: bool
RemoteKeystoreInfo* = object
pubkey*: ValidatorPubKey
url*: HttpHostUri
RequestItemStatus* = object
status*: string
message*: string
@ -16,7 +20,7 @@ type
KeystoresAndSlashingProtection* = object
keystores*: seq[Keystore]
passwords*: seq[string]
slashing_protection*: SPDIR
slashing_protection*: Option[SPDIR]
DeleteKeystoresBody* = object
pubkeys*: seq[ValidatorPubKey]
@ -24,6 +28,12 @@ type
GetKeystoresResponse* = object
data*: seq[KeystoreInfo]
GetRemoteKeystoresResponse* = object
data*: seq[RemoteKeystoreInfo]
ImportRemoteKeystoresBody* = object
remote_keys*: seq[RemoteKeystoreInfo]
PostKeystoresResponse* = object
data*: seq[RequestItemStatus]
@ -31,6 +41,13 @@ type
data*: seq[RequestItemStatus]
slashing_protection*: SPDIR
RemoteKeystoreStatus* = object
status*: KeystoreStatus
message*: Option[string]
DeleteRemoteKeystoresResponse* = object
data*: seq[RemoteKeystoreStatus]
KeystoreStatus* = enum
error = "error"
notActive = "not_active"
@ -44,7 +61,7 @@ type
missingBearerScheme = "Bearer Authentication is not included in request"
incorrectToken = "Authentication token is incorrect"
proc `<`*(x, y: KeystoreInfo): bool =
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
for a, b in fields(x, y):
var c = cmp(a, b)
if c < 0: return true

View File

@ -14,7 +14,7 @@ import
# Third-party libraries
normalize,
# Status libraries
stew/[results, bitops2], stew/shims/macros,
stew/[results, bitops2, base10], stew/shims/macros,
bearssl, eth/keyfile/uuid, blscurve, json_serialization,
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
# Local modules
@ -72,9 +72,9 @@ type
ScryptSalt* = distinct seq[byte]
ScryptParams* = object
dklen: uint64
n, p, r: int
salt: ScryptSalt
dklen*: uint64
n*, p*, r*: int
salt*: ScryptSalt
Pbkdf2Salt* = distinct seq[byte]
@ -130,6 +130,8 @@ type
RemoteKeystoreFlag* {.pure.} = enum
IgnoreSSLVerification
HttpHostUri* = distinct Uri
KeystoreData* = object
version*: uint64
pubkey*: ValidatorPubKey
@ -140,7 +142,7 @@ type
path*: KeyPath
uuid*: string
of KeystoreKind.Remote:
remoteUrl*: Uri
remoteUrl*: HttpHostUri
flags*: set[RemoteKeystoreFlag]
NetKeystore* = object
@ -158,7 +160,7 @@ type
description*: Option[string]
remoteType*: RemoteSignerType
pubkey*: ValidatorPubKey
remote*: Uri
remote*: HttpHostUri
flags*: set[RemoteKeystoreFlag]
KsResult*[T] = Result[T, string]
@ -181,7 +183,7 @@ type
signingKey*: ValidatorPrivKey
withdrawalKey*: ValidatorPrivKey
SimpleHexEncodedTypes = ScryptSalt|ChecksumBytes|CipherBytes
SimpleHexEncodedTypes* = ScryptSalt|ChecksumBytes|CipherBytes
const
keyLen = 32
@ -217,6 +219,15 @@ CipherFunctionKind.serializesAsTextInJson
PrfKind.serializesAsTextInJson
KdfKind.serializesAsTextInJson
template `$`*(u: HttpHostUri): string =
`$`(Uri(u))
template `==`*(lhs, rhs: HttpHostUri): bool =
Uri(lhs) == Uri(rhs)
template `<`*(lhs, rhs: HttpHostUri): bool =
$Uri(lhs) < $Uri(rhs)
template `$`*(m: Mnemonic): string =
string(m)
@ -491,8 +502,8 @@ proc readValue*(r: var JsonReader, value: var Aes128CtrIv)
r.raiseUnexpectedValue(
"The aes-128-ctr IV must be a valid hex string")
proc readValue*[T: SimpleHexEncodedTypes](r: var JsonReader, value: var T)
{.raises: [SerializationError, IOError, Defect].} =
proc readValue*[T: SimpleHexEncodedTypes](r: var JsonReader, value: var T) {.
raises: [SerializationError, IOError, Defect].} =
value = T ncrutils.fromHex(r.readValue(string))
if len(seq[byte](value)) == 0:
r.raiseUnexpectedValue("Valid hex string expected")
@ -531,77 +542,121 @@ proc readValue*(r: var JsonReader, value: var Kdf)
r.raiseUnexpectedValue(
"The Kdf value should have sub-fields named 'function' and 'params'")
proc readValue*(r: var JsonReader, value: var RemoteKeystore)
# HttpHostUri
proc readValue*(reader: var JsonReader, value: var HttpHostUri) {.
raises: [IOError, SerializationError, Defect].} =
let svalue = reader.readValue(string)
let res = parseUri(svalue)
if res.scheme != "http" and res.scheme != "https":
reader.raiseUnexpectedValue("Incorrect URL scheme")
if len(res.hostname) == 0:
reader.raiseUnexpectedValue("Missing URL hostname")
value = HttpHostUri(res)
proc writeValue*(writer: var JsonWriter, value: HttpHostUri) {.
raises: [IOError, Defect].} =
writer.writeValue($distinctBase(value))
# RemoteKeystore
proc writeValue*(writer: var JsonWriter, value: RemoteKeystore) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("version", value.version)
writer.writeField("pubkey", "0x" & value.pubkey.toHex())
writer.writeField("remote", $distinctBase(value.remote))
case value.remoteType
of RemoteSignerType.Web3Signer:
writer.writeField("type", "web3signer")
if value.description.isSome():
writer.writeField("description", value.description.get())
if RemoteKeystoreFlag.IgnoreSSLVerification in value.flags:
writer.writeField("ignore_ssl_verification", true)
writer.endRecord()
template writeValue*(w: var JsonWriter,
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
writeJsonHexString(w.stream, distinctBase value)
proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
{.raises: [SerializationError, IOError, Defect].} =
var
versionWasPresent = false
version: Option[uint64]
description: Option[string]
remote: Option[Uri]
remote: Option[HttpHostUri]
remoteType: Option[string]
ignoreSslVerification: Option[bool]
pubkey: Option[ValidatorPubKey]
for fieldName in readObjectFields(r):
for fieldName in readObjectFields(reader):
case fieldName:
of "pubkey":
if pubkey.isSome():
r.raiseUnexpectedField("Multiple `pubkey` fields found",
"RemoteKeystore")
let res = r.readValue(ValidatorPubKey)
pubkey = some(res)
value.pubkey = res
reader.raiseUnexpectedField("Multiple `pubkey` fields found",
"RemoteKeystore")
pubkey = some(reader.readValue(ValidatorPubKey))
of "remote":
if remote.isSome():
r.raiseUnexpectedField("Multiple `remote` fields found",
"RemoteKeystore")
let res = r.readValue(Uri)
remote = some(res)
value.remote = res
reader.raiseUnexpectedField("Multiple `remote` fields found",
"RemoteKeystore")
remote = some(reader.readValue(HttpHostUri))
of "version":
if versionWasPresent:
r.raiseUnexpectedField("Multiple `version` fields found",
"RemoteKeystore")
value.version = r.readValue(uint64)
versionWasPresent = true
if version.isSome():
reader.raiseUnexpectedField("Multiple `version` fields found",
"RemoteKeystore")
version = some(reader.readValue(uint64))
of "description":
let res = r.readValue(string)
if value.description.isSome():
value.description = some(value.description.get() & "\n" & res)
let res = reader.readValue(string)
if description.isSome():
description = some(description.get() & "\n" & res)
else:
value.description = some(res)
description = some(res)
of "ignore_ssl_verification":
if ignoreSslVerification.isSome():
r.raiseUnexpectedField("Multiple conflicting options found",
"RemoteKeystore")
let res = r.readValue(bool)
ignoreSslVerification = some(res)
if res:
value.flags.incl(RemoteKeystoreFlag.IgnoreSSLVerification)
else:
value.flags.excl(RemoteKeystoreFlag.IgnoreSSLVerification)
reader.raiseUnexpectedField("Multiple conflicting options found",
"RemoteKeystore")
ignoreSslVerification = some(reader.readValue(bool))
of "type":
if remoteType.isSome():
r.raiseUnexpectedField("Multiple `type` fields found",
reader.raiseUnexpectedField("Multiple `type` fields found",
"RemoteKeystore")
let res = r.readValue(string)
remoteType = some(res)
case res
of "web3signer":
value.remoteType = RemoteSignerType.Web3Signer
else:
r.raiseUnexpectedValue("Unsupported remote signer `type` value")
remoteType = some(reader.readValue(string))
else:
# Ignore unknown field names.
discard
if not versionWasPresent:
r.raiseUnexpectedValue("Field version is missing")
if version.isNone():
reader.raiseUnexpectedValue("Field `version` is missing")
if remote.isNone():
r.raiseUnexpectedValue("Field remote is missing")
reader.raiseUnexpectedValue("Field `remote` is missing")
if pubkey.isNone():
r.raiseUnexpectedValue("Field pubkey is missing")
# Set default remote signer type to `Web3Signer`.
if remoteType.isNone():
value.remoteType = RemoteSignerType.Web3Signer
reader.raiseUnexpectedValue("Field `pubkey` is missing")
let keystoreType =
if remoteType.isSome():
let res = remoteType.get()
case res.toLowerAscii()
of "web3signer":
RemoteSignerType.Web3Signer
else:
reader.raiseUnexpectedValue("Unsupported remote signer `type` value")
else:
RemoteSignerType.Web3Signer
let keystoreFlags =
block:
var res: set[RemoteKeystoreFlag]
if ignoreSslVerification.isSome():
res.incl(RemoteKeystoreFlag.IgnoreSSLVerification)
res
value = RemoteKeystore(
version: version.get(),
remote: remote.get(),
pubkey: pubkey.get(),
description: description,
remoteType: keystoreType,
flags: keystoreFlags
)
template writeValue*(w: var JsonWriter,
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
@ -839,6 +894,20 @@ proc createKeystore*(kdfKind: KdfKind,
uuid: $uuid,
version: 4)
proc createRemoteKeystore*(pubKey: ValidatorPubKey, remoteUri: HttpHostUri,
version = 1'u64, description = "",
remoteType = RemoteSignerType.Web3Signer,
flags: set[RemoteKeystoreFlag] = {}): RemoteKeystore =
RemoteKeystore(
version: version,
description: if len(description) > 0: some(description)
else: none[string](),
remoteType: remoteType,
pubkey: pubKey,
remote: remoteUri,
flags: flags
)
proc createWallet*(kdfKind: KdfKind,
rng: var BrHmacDrbgContext,
seed: KeySeed,

View File

@ -34,6 +34,7 @@ const
DisableFileName* = ".disable"
DisableFileContent* = "Please do not remove this file manually. " &
"This can lead to slashing of this validator's key."
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters.
type
WalletPathPair* = object
@ -48,13 +49,19 @@ type
KmResult*[T] = Result[T, cstring]
RemoveValidatorStatus* = enum
RemoveValidatorStatus* {.pure.} = enum
deleted = "Deleted"
missingDir = "Could not find keystore directory to remove"
notFound = "Not found"
AddValidatorStatus* = enum
added = "Validator added"
AddValidatorStatus* {.pure.} = enum
existingArtifacts = "Keystore artifacts already exists"
failed = "Validator not added"
AddValidatorFailure* = object
status*: AddValidatorStatus
message*: string
ImportResult*[T] = Result[T, AddValidatorFailure]
const
minPasswordLen = 12
@ -100,6 +107,26 @@ func init*(T: type KeystoreData,
remoteUrl: keystore.remote
)
func init*(T: type KeystoreData, cookedKey: CookedPubKey,
remoteUrl: HttpHostUri): T =
KeystoreData(
kind: KeystoreKind.Remote,
pubkey: cookedKey.toPubKey(),
version: 1'u64,
remoteUrl: remoteUrl
)
func init(T: type AddValidatorFailure, status: AddValidatorStatus,
msg = ""): AddValidatorFailure {.raises: [Defect].} =
AddValidatorFailure(status: status, message: msg)
func toKeystoreKind*(kind: ValidatorKind): KeystoreKind {.raises: [Defect].} =
case kind
of ValidatorKind.Local:
KeystoreKind.Local
of ValidatorKind.Remote:
KeystoreKind.Remote
proc checkAndCreateDataDir*(dataDir: string): bool =
when defined(posix):
let requiredPerms = 0o700
@ -344,24 +371,34 @@ proc loadKeystoreUnsafe*(validatorsDir, secretsDir,
proc loadRemoteKeystoreImpl(validatorsDir,
keyName: string): Option[KeystoreData] =
let remoteKeystorePath = validatorsDir / keyName / RemoteKeystoreFileName
let privateItem =
let keystorePath = validatorsDir / keyName / RemoteKeystoreFileName
if not(checkSensitiveFilePermissions(keystorePath)):
error "Remote keystorage file has insecure permissions",
key_path = keystorePath
return
let keyStore =
block:
let keystore =
let remoteKeystore =
try:
Json.decode(remoteKeystorePath, RemoteKeystore)
except SerializationError as e:
error "Failed to read remote keystore file",
keystore_path = remoteKeystorePath,
err_msg = e.formatMsg(remoteKeystorePath)
Json.loadFile(keystorePath, RemoteKeystore)
except IOError as err:
error "Failed to read remote keystore file", err = err.msg,
path = keystorePath
return
let res = init(KeystoreData, keystore)
except SerializationError as e:
error "Invalid remote keystore file",
path = keystorePath,
err_msg = e.formatMsg(keystorePath)
return
let res = init(KeystoreData, remoteKeystore)
if res.isErr():
error "Invalid validator's public key in keystore file",
keystore_path = remoteKeystorePath
error "Invalid remote keystore file",
path = keystorePath
return
res.get()
some(privateItem)
some(keyStore)
proc loadKeystoreImpl(validatorsDir, secretsDir, keyName: string,
nonInteractive: bool): Option[KeystoreData] =
@ -433,43 +470,108 @@ proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
error "Unable to find any keystore files", keystorePath
none[KeystoreData]()
proc removeValidatorFiles*(validatorsDir,
secretsDir ,
publicKeyDir: string): KmResult[RemoveValidatorStatus] {.
proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
kind: KeystoreKind
): KmResult[RemoveValidatorStatus] {.
raises: [Defect].} =
let keystoreDir = validatorsDir / publicKeyDir
let keystoreFile = keystoreDir / KeystoreFileName
let secretFile = secretsDir / publicKeyDir
let
keystoreDir = validatorsDir / keyName
keystoreFile =
case kind
of KeystoreKind.Local:
keystoreDir / KeystoreFileName
of KeystoreKind.Remote:
keystoreDir / RemoteKeystoreFileName
secretFile = secretsDir / keyName
if not (dirExists(keystoreDir)):
return ok(missingDir)
try:
removeDir(keystoreDir, false)
except OSError:
return err("Could not remove keystore directory")
if not(existsDir(keystoreDir)):
return ok(RemoveValidatorStatus.notFound)
let res = io2.removeFile(secretFile)
if res.isErr():
return err("Could not remove password file")
if not(existsFile(keystoreFile)):
return ok(RemoveValidatorStatus.notFound)
ok deleted
case kind
of KeystoreKind.Local:
block:
let res = io2.removeFile(keystoreFile)
if res.isErr():
return err("Could not remove keystore file")
block:
let res = io2.removeFile(secretFile)
if res.isErr() and existsFile(secretFile):
return err("Could not remove password file")
# We remove folder with all subfolders and files inside.
try:
removeDir(keystoreDir, false)
except OSError:
return err("Could not remove keystore directory")
of KeystoreKind.Remote:
block:
let res = io2.removeFile(keystoreFile)
if res.isErr():
return err("Could not remove keystore file")
# We remove folder with all subfolders and files inside.
try:
removeDir(keystoreDir, false)
except OSError:
return err("Could not remove keystore directory")
ok(RemoveValidatorStatus.deleted)
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,
publicKey: ValidatorPubKey): KmResult[RemoveValidatorStatus] {.
publicKey: ValidatorPubKey,
kind: KeystoreKind): KmResult[RemoveValidatorStatus] {.
raises: [Defect].} =
let publicKeyHex: string = "0x" & publicKey.toHex()
let res = removeValidatorFiles(conf.validatorsDir(),
conf.secretsDir(),
publicKeyHex)
let validator = pool.getValidator(publicKey)
if isNil(validator):
return ok(RemoveValidatorStatus.notFound)
if validator.kind.toKeystoreKind() != kind:
return ok(RemoveValidatorStatus.notFound)
let publicKeyName: string = "0x" & publicKey.toHex()
let res = removeValidatorFiles(conf, publicKeyName, kind)
if res.isErr():
return err(res.error())
pool.removeValidator(publicKey)
ok res.value()
ok(res.value())
iterator listLoadableKeystores*(validatorsDir,
secretsDir: string,
nonInteractive: bool): KeystoreData =
proc checkKeyName*(keyName: string): bool =
const keyAlphabet = {'a'..'f', 'A'..'F', '0'..'9'}
if len(keyName) != KeyNameSize:
return false
if keyName[0] != '0' and keyName[1] != 'x':
return false
for index in 2 ..< len(keyName):
if keyName[index] notin keyAlphabet:
return false
true
proc existsKeystore*(keystoreDir: string, keyKind: KeystoreKind): bool {.
raises: [Defect].} =
case keyKind
of KeystoreKind.Local:
existsFile(keystoreDir / KeystoreFileName)
of KeystoreKind.Remote:
existsFile(keystoreDir / RemoteKeystoreFileName)
proc existsKeystore*(keystoreDir: string,
keysMask: set[KeystoreKind]): bool {.raises: [Defect].} =
if KeystoreKind.Local in keysMask:
if existsKeystore(keystoreDir, KeystoreKind.Local):
return true
if KeystoreKind.Remote in keysMask:
if existsKeystore(keystoreDir, KeystoreKind.Remote):
return true
false
iterator listLoadableKeystores*(validatorsDir, secretsDir: string,
nonInteractive: bool,
keysMask: set[KeystoreKind]): KeystoreData =
try:
for kind, file in walkDir(validatorsDir):
if kind == pcDir:
@ -479,26 +581,22 @@ iterator listLoadableKeystores*(validatorsDir,
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / KeystoreFileName
if not(fileExists(keystoreFile)):
if not(checkKeyName(keyName)):
# Skip folders which name do not satisfy "0x[a-fA-F0-9]{96, 96}".
continue
if not(existsKeystore(keystoreDir, keysMask)):
# Skip folders which do not have keystore file inside.
continue
let
secretFile = secretsDir / keyName
keystore = loadKeystore(validatorsDir, secretsDir, keyName, nonInteractive)
keystore = loadKeystore(validatorsDir, secretsDir, keyName,
nonInteractive)
if keystore.isSome():
let pubkey = keystore.get().privateKey.toPubKey().toPubKey()
yield KeystoreData(kind: KeystoreKind.Local,
privateKey: keystore.get().privateKey,
description: keystore.get().description,
path: keystore.get().path,
uuid: keystore.get().uuid,
version: keystore.get().version,
pubkey: pubkey)
yield keystore.get()
else:
fatal "Unable to load keystore", keystore_file = keystoreFile
fatal "Unable to load keystore", keystore = file
quit 1
except OSError as err:
@ -509,7 +607,8 @@ iterator listLoadableKeystores*(validatorsDir,
iterator listLoadableKeystores*(config: AnyConf): KeystoreData =
for el in listLoadableKeystores(config.validatorsDir(),
config.secretsDir(),
config.nonInteractive):
config.nonInteractive,
{KeystoreKind.Local, KeystoreKind.Remote}):
yield el
type
@ -519,6 +618,8 @@ type
FailedToCreateSecretsDir
FailedToCreateSecretFile
FailedToCreateKeystoreFile
DuplicateKeystoreDir
DuplicateKeystoreFile
KeystoreGenerationError* = object
case kind*: KeystoreGenerationErrorKind
@ -526,7 +627,9 @@ type
FailedToCreateValidatorsDir,
FailedToCreateSecretsDir,
FailedToCreateSecretFile,
FailedToCreateKeystoreFile:
FailedToCreateKeystoreFile,
DuplicateKeystoreDir,
DuplicateKeystoreFile:
error*: string
proc mapErrTo*[T, E](r: Result[T, E], v: static KeystoreGenerationErrorKind):
@ -555,7 +658,8 @@ proc loadNetKeystore*(keyStorePath: string,
if insecurePwd.isSome():
warn "Using insecure password to unlock networking key"
let decrypted = decryptNetKeystore(keystore, KeystorePass.init insecurePwd.get)
let decrypted = decryptNetKeystore(keystore,
KeystorePass.init(insecurePwd.get()))
if decrypted.isOk:
return some(decrypted.get())
else:
@ -607,11 +711,10 @@ proc saveNetKeystore*(rng: var BrHmacDrbgContext, keyStorePath: string,
key_path = keyStorePath
res.mapErrTo(FailedToCreateKeystoreFile)
proc createValidatorFiles*(
secretsDir, validatorsDir,
keystoreDir, secretFile,
passwordAsString, keystoreFile,
encodedStorage: string): Result[void, KeystoreGenerationError] =
proc createValidatorFiles*(secretsDir, validatorsDir, keystoreDir, secretFile,
passwordAsString, keystoreFile,
encodedStorage: string
): Result[void, KeystoreGenerationError] =
var
success = false # becomes true when everything is created successfully
@ -641,17 +744,45 @@ proc createValidatorFiles*(
discard io2.removeDir(keystoreDir)
# secretFile:
? secureWriteFile(secretFile, passwordAsString).mapErrTo(FailedToCreateSecretFile)
? secureWriteFile(secretFile,
passwordAsString).mapErrTo(FailedToCreateSecretFile)
defer:
if not success:
discard io2.removeFile(secretFile)
# keystoreFile:
? secureWriteFile(keystoreFile, encodedStorage).mapErrTo(FailedToCreateKeystoreFile)
? secureWriteFile(keystoreFile,
encodedStorage).mapErrTo(FailedToCreateKeystoreFile)
success = true
ok()
proc createValidatorFiles*(validatorsDir, keystoreDir, keystoreFile,
encodedStorage: string
): Result[void, KeystoreGenerationError] =
var
success = false # becomes true when everything is created successfully
# validatorsDir:
let validatorsDirExisted: bool = dirExists(validatorsDir)
if not(validatorsDirExisted):
? secureCreatePath(validatorsDir).mapErrTo(FailedToCreateValidatorsDir)
defer:
if not (success or validatorsDirExisted):
discard io2.removeDir(validatorsDir)
# keystoreDir:
? secureCreatePath(keystoreDir).mapErrTo(FailedToCreateKeystoreDir)
defer:
if not success:
discard io2.removeDir(keystoreDir)
# keystoreFile:
? secureWriteFile(keystoreFile,
encodedStorage).mapErrTo(FailedToCreateKeystoreFile)
success = true
ok()
proc saveKeystore*(rng: var BrHmacDrbgContext,
validatorsDir, secretsDir: string,
signingKey: ValidatorPrivKey,
@ -663,35 +794,118 @@ proc saveKeystore*(rng: var BrHmacDrbgContext,
keypass = KeystorePass.init(password)
keyName = "0x" & signingPubKey.toHex()
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / KeystoreFileName
if not existsDir(keystoreDir):
if existsDir(keystoreDir):
return err(KeystoreGenerationError(kind: DuplicateKeystoreDir,
error: "Keystore directory already exists"))
if existsFile(keystoreFile):
return err(KeystoreGenerationError(kind: DuplicateKeystoreFile,
error: "Keystore file already exists"))
let
keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
let keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
keypass, signingKeyPath,
mode = mode)
keystoreFile = keystoreDir / KeystoreFileName
var encodedStorage: string
try:
encodedStorage = Json.encode(keyStore)
except SerializationError as e:
error "Could not serialize keystorage", key_path = keystoreFile
return err(KeystoreGenerationError(
kind: FailedToCreateKeystoreFile, error: e.msg))
var encodedStorage: string
try:
encodedStorage = Json.encode(keyStore)
except SerializationError as e:
error "Could not serialize keystorage", key_path = keystoreFile
return err(KeystoreGenerationError(
kind: FailedToCreateKeystoreFile, error: e.msg))
? createValidatorFiles(secretsDir, validatorsDir,
keystoreDir,
secretsDir / keyName, keypass.str,
keystoreFile, encodedStorage)
? createValidatorFiles(secretsDir, validatorsDir,
keystoreDir,
secretsDir / keyName, keypass.str,
keystoreFile, encodedStorage)
ok()
proc saveKeystore*(validatorsDir: string,
publicKey: ValidatorPubKey, url: HttpHostUri,
version = 1'u64,
flags: set[RemoteKeystoreFlag] = {},
remoteType = RemoteSignerType.Web3Signer,
desc = ""): Result[void, KeystoreGenerationError] {.
raises: [Defect].} =
let
keyName = "0x" & publicKey.toHex()
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / RemoteKeystoreFileName
keystoreDesc = if len(desc) == 0: none[string]() else: some(desc)
keyStore = RemoteKeystore(
version: version, description: keystoreDesc, remoteType: remoteType,
pubkey: publicKey, remote: url, flags: flags
)
if existsDir(keystoreDir):
return err(KeystoreGenerationError(kind: DuplicateKeystoreDir,
error: "Keystore directory already exists"))
if existsFile(keystoreFile):
return err(KeystoreGenerationError(kind: DuplicateKeystoreFile,
error: "Keystore file already exists"))
let encodedStorage =
try:
Json.encode(keyStore)
except SerializationError as exc:
error "Could not serialize keystorage", key_path = keystoreFile
return err(KeystoreGenerationError(
kind: FailedToCreateKeystoreFile, error: exc.msg))
? createValidatorFiles(validatorsDir, keystoreDir, keystoreFile,
encodedStorage)
ok()
proc saveKeystore*(conf: AnyConf, publicKey: ValidatorPubKey, url: HttpHostUri,
version = 1'u64,
flags: set[RemoteKeystoreFlag] = {},
remoteType = RemoteSignerType.Web3Signer,
desc = ""): Result[void, KeystoreGenerationError] {.
raises: [Defect].} =
saveKeystore(conf.validatorsDir(), publicKey, url, version, flags,
remoteType, desc)
proc importKeystore*(pool: var ValidatorPool, conf: AnyConf,
keystore: RemoteKeystore): ImportResult[KeystoreData] {.
raises: [Defect].} =
let
publicKey = keystore.pubkey
keyName = "0x" & publicKey.toHex()
validatorsDir = conf.validatorsDir()
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / RemoteKeystoreFileName
# We check `publicKey`.
let cookedKey =
block:
let res = publicKey.load()
if res.isNone():
return err(
AddValidatorFailure.init(AddValidatorStatus.failed,
"Invalid validator's public key"))
res.get()
# We check `publicKey` in memory storage first.
if publicKey in pool:
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
# We check `publicKey` in filesystem.
if existsKeystore(keystoreDir, {KeystoreKind.Local, KeystoreKind.Remote}):
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
let res = saveKeystore(conf, publicKey, keystore.remote)
if res.isErr():
return err(AddValidatorFailure.init(AddValidatorStatus.failed,
$res.error()))
ok(KeystoreData.init(cookedKey, keystore.remote))
proc importKeystore*(pool: var ValidatorPool,
rng: var BrHmacDrbgContext,
conf: AnyConf, keystore: Keystore,
password: string): KmResult[AddValidatorStatus]
{.raises: [Defect].} =
password: string): ImportResult[KeystoreData] {.
raises: [Defect].} =
let keypass = KeystorePass.init(password)
let privateKey =
block:
@ -699,31 +913,33 @@ proc importKeystore*(pool: var ValidatorPool,
if res.isOk():
res.get()
else:
return err("Keystore decryption failed")
return err(
AddValidatorFailure.init(AddValidatorStatus.failed, res.error()))
let
publicKey = privateKey.toPubKey()
keyName = "0x" & publicKey.toHex()
validatorsDir = conf.validatorsDir()
secretsDir = conf.secretsDir()
secretFile = secretsDir / keyName
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / KeystoreFileName
let publicKey = privateKey.toPubKey()
let keyName = "0x" & publicKey.toHex()
# We check `publicKey` in memory storage first.
if publicKey.toPubKey() in pool:
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
let validatorsDir = conf.validatorsDir()
let secretsDir = conf.secretsDir()
# We check `publicKey` in filesystem.
if existsKeystore(keystoreDir, {KeystoreKind.Local, KeystoreKind.Remote}):
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
let secretFile = secretsDir / keyName
let keystoreDir = validatorsDir / keyName
let keystoreFile = keystoreDir / KeystoreFileName
if fileExists(keystoreFile) or fileExists(secretFile):
return ok(AddValidatorStatus.existingArtifacts)
let res = saveKeystore(rng,
validatorsDir, secretsDir,
privateKey, publicKey,
keystoreDir.KeyPath, password)
let res = saveKeystore(rng, validatorsDir, secretsDir,
privateKey, publicKey, keystore.path, password)
if res.isErr():
return err("Keystore Generation Error")
return err(AddValidatorFailure.init(AddValidatorStatus.failed,
$res.error()))
pool.addLocalValidator(KeystoreData.init(privateKey, keystore))
ok AddValidatorStatus.added
ok(KeystoreData.init(privateKey, keystore))
proc generateDeposits*(cfg: RuntimeConfig,
rng: var BrHmacDrbgContext,
@ -731,7 +947,8 @@ proc generateDeposits*(cfg: RuntimeConfig,
firstValidatorIdx, totalNewValidators: int,
validatorsDir: string,
secretsDir: string,
mode = Secure): Result[seq[DepositData], KeystoreGenerationError] =
mode = Secure): Result[seq[DepositData],
KeystoreGenerationError] =
var deposits: seq[DepositData]
notice "Generating deposits", totalNewValidators, validatorsDir, secretsDir

View File

@ -84,15 +84,14 @@ proc findValidator(validators: auto, pubkey: ValidatorPubKey):
else:
some(idx.ValidatorIndex)
proc addLocalValidator(node: BeaconNode,
validators: auto,
proc addLocalValidator(node: BeaconNode, validators: auto,
item: KeystoreData) =
let
pubkey = item.pubkey
index = findValidator(validators, pubkey)
node.attachedValidators[].addLocalValidator(item, index)
proc addRemoteValidator(node: BeaconNode, validators: auto,
proc addRemoteValidator(pool: var ValidatorPool, validators: auto,
item: KeystoreData) =
let httpFlags =
block:
@ -108,7 +107,7 @@ proc addRemoteValidator(node: BeaconNode, validators: auto,
remote_url = $item.remoteUrl, validator = item.pubkey
return
let index = findValidator(validators, item.pubkey)
node.attachedValidators[].addRemoteValidator(item, client.get(), index)
pool.addRemoteValidator(item, client.get(), index)
proc addLocalValidators*(node: BeaconNode,
validators: openArray[KeystoreData]) =
@ -120,7 +119,8 @@ proc addRemoteValidators*(node: BeaconNode,
validators: openArray[KeystoreData]) =
withState(node.dag.headState.data):
for item in validators:
node.addRemoteValidator(state.data.validators.asSeq(), item)
node.attachedValidators[].addRemoteValidator(
state.data.validators.asSeq(), item)
proc addValidators*(node: BeaconNode) =
let (localValidators, remoteValidators) =

View File

@ -114,8 +114,12 @@ proc removeValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey) =
let validator = pool.validators.getOrDefault(pubkey)
if not(isNil(validator)):
pool.validators.del(pubkey)
notice "Local or remote validator detached", pubkey,
validator = shortLog(validator)
case validator.kind
of ValidatorKind.Local:
notice "Local validator detached", pubkey, validator = shortLog(validator)
of ValidatorKind.Remote:
notice "Remote validator detached", pubkey,
validator = shortLog(validator)
validators.set(pool.count().int64)
proc updateValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey,

View File

@ -53,7 +53,8 @@ cli do(validatorsDir: string, secretsDir: string,
validators: Table[ValidatorIndex, ValidatorPrivKey]
validatorKeys: Table[ValidatorPubKey, ValidatorPrivKey]
for item in listLoadableKeystores(validatorsDir, secretsDir, true):
for item in listLoadableKeystores(validatorsDir, secretsDir, true,
{KeystoreKind.Local}):
let
pubkey = item.privateKey.toPubKey().toPubKey()
idx = findValidator(getStateField(state[], validators).toSeq, pubkey)

View File

@ -8,7 +8,7 @@ import
../beacon_chain/spec/[crypto, keystore, eth2_merkleization],
../beacon_chain/spec/datatypes/base,
../beacon_chain/spec/eth2_apis/[rest_beacon_client, rest_keymanager_types],
../beacon_chain/spec/eth2_apis/[rest_keymanager_calls, rest_keymanager_types],
../beacon_chain/validators/[keystore_management, slashing_protection_common],
../beacon_chain/networking/network_metadata,
../beacon_chain/rpc/rest_key_management_api,
@ -28,6 +28,48 @@ const
tokenFilePath = dataDir / "keymanager-token.txt"
keymanagerPort = 47000
correctTokenValue = "some secret token"
newPrivateKeys = [
"0x598c9b81749ba7bb8eb37781027359e3ffe87d0e1579e21c453ce22af0c05e35",
"0x14e4470a1d8913ec0602048af78addf0fd7a37f591dd3feda828d10a10c0f6ff",
"0x3b4498c4e26f83702ceeed5e32600ecb3e71f08fc4561215d0f0ced13bf5dbdf",
"0x3cae8cf27c7e12549486f5613974661285d40596907a7fc39bac7c55a56660ab",
"0x71fd9bb8eadcf64df9cc8e716652709492c16518f73f87c770a54fe8c80ac5ae",
"0x4be74b7b0b0058dea2d4744e0069486500770f68296ac9b9bbd26df6749ed0ca",
"0x10052305a5fda7805fb1e762fe6cbc47e43c5a54f34f008fa79c48fee1749db7",
"0x3630f086fb9f1136fe077751031a16630e43d65ff64bb9fd3708adff81df5926"
]
oldPublicKeys = [
"0x94effccb0514f0f110a9680827e4f3769e53349e3b1c177e8c4f38b0e52e7842a4990212fe2edd2ce48b9b0bd02f3b04",
"0x950bcb136ef15e737cd28cc8ba94a5584e30cf6cfa4f3d16215acbe46917633c09630208f379898a898b29bd59b2bd34",
"0xaa96fddc809e0678b192cebd3a64873a339c7352eafaa88ab13bac84244e19b9afe2de8282320f5e0e7c155573f80ac3",
"0xa0f1da63e35c7a159fc2f187d300cad9ef5f5e73e55f78c391e7bc2c2feabc2d9d63dfe99edd7058ad0ab9d7f14a1e1a",
"0x9315ea03755881989b0d34e9594520d2ebca4d2f0fd955dafe42948a91840a2e812d1d61f26684c603a60c99e3537151",
"0x88c9737238fa23ed8e485e17349c523fe3fe848eab173959d34e7f7f2c731fb896ab7c0b0877a40782a5cd529dc7b080",
"0x995e1d9d9d467ca25b981a7ca0880e932ac418e5ebed9a834f3ead3fbec267986e28eb0243c562ae3b1995a600c1495c",
"0x945ab594e8c9cf3d6251b86fddf6fbf970c1835cd14113098554f135a6c2cf7f21d2f7a08ae33726785a59ae4910fa51",
]
oldPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/local"))
newPublicKeys = [
"0x80eadf027ad564a2f004616fa58f3add9caa700b20e9bf7e0b101be61406feb79f5e28ec8a5bb2a0689cc7b4c807afba",
"0x8c6585f39fd3d2ed950ba4958f0050ec68e4e7e3200147687fa101bcf98977ebe144b03edc45906faae144549f11d8b9",
"0xb3939c9ecfb3679de8aa7f81e8dfb9eaa51e958d165e8b963aa88767217ce03316e4bad74e7a475ed6009365d297e0cd",
"0xb093029010dd400f49350db77b13e70c3d75f5286c2cc5d7f1d0865e251cc547764de85371583eba2b1810cf36a4feb1",
"0x8893a6f03de181cc93537ebb89ed242f65f3722fe22cd7aaab71a4149a792b231e23e1575c12efb0d2934e6d7b755431",
"0x88c475e022971f0698b50aa2c9dd91df8b1c9f1079cbe7b2243bb5dee3a5cb5c46e170f90165efecdc794e14ae5b8fd9",
"0xa782e5161ba8e9ac135b0db3203a8c23aa61e19be6b9c198393d8b2b902bad8139863d9cf26bc2cbdc3b747bafc64606",
"0xb33f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7189e"
]
newPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/remote"))
proc contains*(keylist: openArray[KeystoreInfo], key: ValidatorPubKey): bool =
for item in keylist:
if item.validating_pubkey == key:
return true
false
proc contains*(keylist: openArray[KeystoreInfo], key: string): bool =
let pubkey = ValidatorPubKey.fromHex(key).tryGet()
contains(keylist, pubkey)
proc startSingleNodeNetwork =
let
@ -65,6 +107,13 @@ proc startSingleNodeNetwork =
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
@ -125,6 +174,40 @@ const
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
proc listLocalValidators(validatorsDir,
secretsDir: string): seq[KeystoreInfo] {.
raises: [Defect].} =
var validators: seq[KeystoreInfo]
try:
for el in listLoadableKeystores(validatorsDir, secretsDir, true,
{KeystoreKind.Local}):
validators.add KeystoreInfo(validating_pubkey: el.pubkey,
derivation_path: el.path.string,
readonly: false)
except OSError as err:
error "Failure to list the validator directories",
validatorsDir, secretsDir, err = err.msg
validators
proc listRemoteValidators(validatorsDir,
secretsDir: string): seq[RemoteKeystoreInfo] {.
raises: [Defect].} =
var validators: seq[RemoteKeystoreInfo]
try:
for el in listLoadableKeystores(validatorsDir, secretsDir, true,
{KeystoreKind.Remote}):
validators.add RemoteKeystoreInfo(pubkey: el.pubkey,
url: el.remoteUrl)
except OSError as err:
error "Failure to list the validator directories",
validatorsDir, secretsDir, err = err.msg
validators
proc runTests {.async.} =
while bnStatus != BeaconNodeStatus.Running:
await sleepAsync(1.seconds)
@ -136,6 +219,8 @@ proc runTests {.async.} =
rng = keys.newRng()
privateKey = ValidatorPrivKey.fromRaw(secretBytes).get
localList = listLocalValidators(validatorsDir, secretsDir)
newKeystore = createKeystore(
kdfPbkdf2, rng[], privateKey,
KeystorePass.init password,
@ -143,18 +228,102 @@ proc runTests {.async.} =
description = "This is a test keystore that uses PBKDF2 to secure the secret",
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
importKeystoresBody1 =
block:
var
res1: seq[Keystore]
res2: seq[string]
for key in newPrivateKeys:
let privateKey = ValidatorPrivKey.fromHex(key).tryGet()
let store = createKeystore(kdfPbkdf2, rng[], privateKey,
KeystorePass.init password, salt = salt, iv = iv,
description = "Test keystore",
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
res1.add(store)
res2.add(password)
KeystoresAndSlashingProtection(
keystores: res1,
passwords: res2,
)
deleteKeysBody1 =
block:
var res: seq[ValidatorPubKey]
for item in newPrivateKeys:
let privateKey = ValidatorPrivKey.fromHex(item).tryGet()
let publicKey = privateKey.toPubKey().toPubKey()
res.add(publicKey)
DeleteKeystoresBody(
pubkeys: res
)
importKeystoresBody = KeystoresAndSlashingProtection(
keystores: @[newKeystore],
passwords: @[password],
slashing_protection: SPDIR())
)
deleteKeysBody = DeleteKeystoresBody(
pubkeys: @[privateKey.toPubKey.toPubKey])
importRemoteKeystoresBody =
block:
var res: seq[RemoteKeystoreInfo]
# Adding keys which are already present in filesystem
for item in oldPublicKeys:
let key = ValidatorPubKey.fromHex(item).tryGet()
res.add(RemoteKeystoreInfo(pubkey: key, url: newPublicKeysUrl))
# Adding keys which are new
for item in newPublicKeys:
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].validating_pubkey,
url: newPublicKeysUrl))
res.add(RemoteKeystoreInfo(pubkey: localList[1].validating_pubkey,
url: newPublicKeysUrl))
ImportRemoteKeystoresBody(remote_keys: res)
deleteRemoteKeystoresBody1 =
block:
var res: seq[ValidatorPubKey]
for item in oldPublicKeys:
let key = ValidatorPubKey.fromHex(item).tryGet()
res.add(key)
DeleteKeystoresBody(pubkeys: res)
deleteRemoteKeystoresBody2 =
block:
var res: seq[ValidatorPubKey]
for item in newPublicKeys:
let key = ValidatorPubKey.fromHex(item).tryGet()
res.add(key)
DeleteKeystoresBody(pubkeys: res)
deleteRemoteKeystoresBody3 =
block:
DeleteKeystoresBody(
pubkeys: @[
ValidatorPubKey.fromHex(newPublicKeys[0]).tryGet(),
ValidatorPubKey.fromHex(newPublicKeys[1]).tryGet()
]
)
deleteRemoteKeystoresBody4 =
block:
DeleteKeystoresBody(
pubkeys: @[
ValidatorPubKey.fromHex(oldPublicKeys[0]).tryGet(),
ValidatorPubKey.fromHex(oldPublicKeys[1]).tryGet(),
localList[0].validating_pubkey,
localList[1].validating_pubkey
]
)
suite "ListKeys requests" & preset():
asyncTest "Correct token provided" & preset():
let
filesystemKeystores = sorted(listValidators(validatorsDir, secretsDir))
filesystemKeystores = sorted(
listLocalValidators(validatorsDir, secretsDir))
apiKeystores = sorted((await client.listKeys(correctTokenValue)).data)
check filesystemKeystores == apiKeystores
@ -198,6 +367,75 @@ proc runTests {.async.} =
let keystores = await client.listKeys("Invalid Token")
suite "ImportKeystores requests" & preset():
asyncTest "ImportKeystores/ListKeystores/DeleteKeystores" & preset():
let
response1 = await client.importKeystoresPlain(
importKeystoresBody1,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson1 = Json.decode(response1.data, JsonNode)
check response1.status == 200
for i in 0 ..< 8:
check:
responseJson1["data"][i]["status"].getStr() == "imported"
responseJson1["data"][i]["message"].getStr() == ""
let
filesystemKeystores1 = sorted(
listLocalValidators(validatorsDir, secretsDir))
apiKeystores1 = sorted((await client.listKeys(correctTokenValue)).data)
check:
filesystemKeystores1 == apiKeystores1
importKeystoresBody1.keystores[0].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[1].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[2].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[3].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[4].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[5].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[6].pubkey in filesystemKeystores1
importKeystoresBody1.keystores[7].pubkey in filesystemKeystores1
let
response2 = await client.importKeystoresPlain(
importKeystoresBody1,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson2 = Json.decode(response2.data, JsonNode)
check response2.status == 200
for i in 0 ..< 8:
check:
responseJson2["data"][i]["status"].getStr() == "duplicate"
responseJson2["data"][i]["message"].getStr() == ""
let
response3 = await client.deleteKeysPlain(
deleteKeysBody1,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson3 = Json.decode(response3.data, JsonNode)
check response3.status == 200
for i in 0 ..< 8:
check:
responseJson3["data"][i]["status"].getStr() == "deleted"
responseJson3["data"][i]["message"].getStr() == ""
let
filesystemKeystores2 = sorted(
listLocalValidators(validatorsDir, secretsDir))
apiKeystores2 = sorted((await client.listKeys(correctTokenValue)).data)
check:
filesystemKeystores2 == apiKeystores2
deleteKeysBody1.pubkeys[0] notin filesystemKeystores2
deleteKeysBody1.pubkeys[1] notin filesystemKeystores2
deleteKeysBody1.pubkeys[2] notin filesystemKeystores2
deleteKeysBody1.pubkeys[3] notin filesystemKeystores2
deleteKeysBody1.pubkeys[4] notin filesystemKeystores2
deleteKeysBody1.pubkeys[5] notin filesystemKeystores2
deleteKeysBody1.pubkeys[6] notin filesystemKeystores2
deleteKeysBody1.pubkeys[7] notin filesystemKeystores2
asyncTest "Missing Authorization header" & preset():
let
response = await client.importKeystoresPlain(importKeystoresBody)
@ -285,6 +523,245 @@ proc runTests {.async.} =
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
suite "ListRemoteKeys requests" & preset():
asyncTest "Correct token provided" & preset():
let
filesystemKeystores = sorted(
listRemoteValidators(validatorsDir, secretsDir))
apiKeystores = sorted((
await client.listRemoteKeys(correctTokenValue)).data)
check filesystemKeystores == apiKeystores
asyncTest "Missing Authorization header" & preset():
let
response = await client.listRemoteKeysPlain()
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
asyncTest "Invalid Authorization Header" & preset():
let
response = await client.listRemoteKeysPlain(
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
asyncTest "Invalid Authorization Token" & preset():
let
response = await client.listRemoteKeysPlain(
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
expect RestError:
let keystores = await client.listKeys("Invalid Token")
suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & preset():
asyncTest "Importing list of remote keys" & preset():
let
response1 = await client.importRemoteKeysPlain(
importRemoteKeystoresBody,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson1 = Json.decode(response1.data, JsonNode)
check:
response1.status == 200
for i in [0, 1, 2, 3, 4, 5, 6, 7, 16, 17]:
check:
responseJson1["data"][i]["status"].getStr() == "duplicate"
responseJson1["data"][i]["message"].getStr() == ""
for i in 8 ..< 16:
check:
responseJson1["data"][i]["status"].getStr() == "imported"
responseJson1["data"][i]["message"].getStr() == ""
let
filesystemKeystores1 = sorted(
listRemoteValidators(validatorsDir, secretsDir))
apiKeystores1 = sorted((
await client.listRemoteKeys(correctTokenValue)).data)
check:
filesystemKeystores1 == apiKeystores1
for item in newPublicKeys:
let key = ValidatorPubKey.fromHex(item).tryGet()
let found =
block:
var res = false
for keystore in filesystemKeystores1:
if keystore.pubkey == key:
res = true
break
res
check found == true
let
response2 = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody2,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson2 = Json.decode(response2.data, JsonNode)
check:
response2.status == 200
responseJson2["data"][0]["status"].getStr() == "deleted"
responseJson2["data"][1]["status"].getStr() == "deleted"
responseJson2["data"][2]["status"].getStr() == "deleted"
responseJson2["data"][3]["status"].getStr() == "deleted"
responseJson2["data"][4]["status"].getStr() == "deleted"
responseJson2["data"][5]["status"].getStr() == "deleted"
responseJson2["data"][6]["status"].getStr() == "deleted"
responseJson2["data"][7]["status"].getStr() == "deleted"
let
filesystemKeystores2 = sorted(
listRemoteValidators(validatorsDir, secretsDir))
apiKeystores2 = sorted((
await client.listRemoteKeys(correctTokenValue)).data)
check:
filesystemKeystores2 == apiKeystores2
for keystore in filesystemKeystores2:
let key = "0x" & keystore.pubkey.toHex()
check:
key notin newPublicKeys
asyncTest "Missing Authorization header" & preset():
let
response = await client.importRemoteKeysPlain(importRemoteKeystoresBody)
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
asyncTest "Invalid Authorization Header" & preset():
let
response = await client.importRemoteKeysPlain(
importRemoteKeystoresBody,
extraHeaders = @[("Authorization", "Basic XYZ")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
asyncTest "Invalid Authorization Token" & preset():
let
response = await client.importRemoteKeysPlain(
importRemoteKeystoresBody,
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
suite "DeleteRemoteKeys requests" & preset():
asyncTest "Deleting not existing key" & preset():
let
response = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody3,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 200
responseJson["data"][0]["status"].getStr() == "not_found"
responseJson["data"][1]["status"].getStr() == "not_found"
asyncTest "Deleting existing local key and remote key" & preset():
let
response = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody4,
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 200
responseJson["data"][0]["status"].getStr() == "deleted"
responseJson["data"][1]["status"].getStr() == "deleted"
responseJson["data"][2]["status"].getStr() == "not_found"
responseJson["data"][3]["status"].getStr() == "not_found"
let
filesystemKeystores = sorted(
listRemoteValidators(validatorsDir, secretsDir))
apiKeystores = sorted((
await client.listRemoteKeys(correctTokenValue)).data)
check:
filesystemKeystores == apiKeystores
let
removedKey0 = ValidatorPubKey.fromHex(oldPublicKeys[0]).tryGet()
removedKey1 = ValidatorPubKey.fromHex(oldPublicKeys[1]).tryGet()
for item in apiKeystores:
check:
removedKey0 != item.pubkey
removedKey1 != item.pubkey
asyncTest "Missing Authorization header" & preset():
let
response = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody1)
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
asyncTest "Invalid Authorization Header" & preset():
let
response = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody1,
extraHeaders = @[("Authorization", "Basic XYZ")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
asyncTest "Invalid Authorization Token" & preset():
let
response = await client.deleteRemoteKeysPlain(
deleteRemoteKeystoresBody1,
extraHeaders = @[("Authorization", "Bearer XYZ")])
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
bnStatus = BeaconNodeStatus.Stopping
proc main() {.async.} =

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[os, options, json, typetraits],
std/[os, options, json, typetraits, uri, algorithm],
unittest2, chronos, chronicles, stint, json_serialization,
blscurve, eth/keys, nimcrypto/utils,
libp2p/crypto/crypto as lcrypto,
@ -24,9 +24,6 @@ proc directoryItemsCount(dir: string): int {.raises: [OSError].} =
for el in walkDir(dir):
result += 1
proc isEmptyDir(dir: string): bool =
directoryItemsCount(dir) == 0
proc validatorPubKeysInDir(dir: string): seq[string] =
for kind, file in walkDir(dir):
if kind == pcDir:
@ -47,6 +44,13 @@ let
cfg = defaultRuntimeConfig
validatorDirRes = secureCreatePath(testValidatorsDir)
proc namesEqual(a, b: openarray[string]): bool =
sorted(a) == sorted(b)
when not defined(windows):
proc isEmptyDir(dir: string): bool =
directoryItemsCount(dir) == 0
if validatorDirRes.isErr():
warn "Could not create validators folder",
path = testValidatorsDir, err = ioErrorMsg(validatorDirRes.error)
@ -70,14 +74,59 @@ if deposits.isErr:
let validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
suite "removeValidatorFiles":
const
MultiplePassword = string.fromBytes(
hexToSeqByte("7465737470617373776f7264f09f9491"))
MultipleSalt = hexToSeqByte(
"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
MultipleIv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6")
MultipleRemoteUri = HttpHostUri(parseUri("https://127.0.0.1/eth/web3signer"))
MultiplePrivateKeys = [
"3b89cdf5c62b423dab64dd69476c6c74bdbccc684abc89f3b392ac1f679e06c3",
"5140621611300ed419f901d8c56baf32d89d876272bbb3ab16e1c9f0884487d4"
]
var
MultipleKeystoreNames: seq[string]
MultipleSigningKeys: seq[ValidatorPrivKey]
MultipleLocalKeystores: seq[Keystore]
MultipleLocalKeystoreJsons: seq[string]
MultipleRemoteKeystores: seq[RemoteKeystore]
MultipleRemoteKeystoreJsons: seq[string]
for key in MultiplePrivateKeys:
let
nsecret = ValidatorPrivKey.fromRaw(hexToSeqByte(key)).get()
npubkey = nsecret.toPubKey().toPubKey()
keystoreName = "0x" & npubkey.toHex()
localKeystore = createKeystore(
kdfPbkdf2, rng[], nsecret,
KeystorePass.init MultiplePassword,
salt = MultipleSalt, iv = MultipleIv,
description = "This is a test keystore.",
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
localKeystoreJson = Json.encode(localKeystore)
remoteKeystore = createRemoteKeystore(npubkey, MultipleRemoteUri)
remoteKeystoreJson = Json.encode(remoteKeystore)
MultipleSigningKeys.add(nsecret)
MultipleKeystoreNames.add(keystoreName)
MultipleLocalKeystores.add(localKeystore)
MultipleLocalKeystoreJsons.add(localKeystoreJson)
MultipleRemoteKeystores.add(remoteKeystore)
MultipleRemoteKeystoreJsons.add(remoteKeystoreJson)
suite "removeValidatorFiles()":
test "Remove validator files":
let
validatorsCountBefore = directoryItemsCount(testValidatorsDir)
secretsCountBefore = directoryItemsCount(testSecretsDir)
firstValidator = validatorPubKeys[0]
removeValidatorFilesRes = removeValidatorFiles(
testValidatorsDir, testSecretsDir, firstValidator)
testValidatorsDir, testSecretsDir, firstValidator, KeystoreKind.Local)
validatorsCountAfter = directoryItemsCount(testValidatorsDir)
secretsCountAfter = directoryItemsCount(testSecretsDir)
@ -94,31 +143,263 @@ suite "removeValidatorFiles":
let
nonexistentValidator =
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
res = removeValidatorFiles(testValidatorsDir, testSecretsDir, nonexistentValidator)
res = removeValidatorFiles(testValidatorsDir, testSecretsDir,
nonexistentValidator, KeystoreKind.Local)
check(res.isOk and res.value == RemoveValidatorStatus.missingDir)
check(res.isOk and res.value == RemoveValidatorStatus.notFound)
test "Remove validator files twice":
let
secondValidator = validatorPubKeys[1]
res1 = removeValidatorFiles(testValidatorsDir, testSecretsDir, secondValidator)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir, secondValidator)
res1 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
secondValidator, KeystoreKind.Local)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
secondValidator, KeystoreKind.Local)
check:
not fileExists(testValidatorsDir / secondValidator)
not fileExists(testSecretsDir / secondValidator)
res1.isOk and res1.value() == RemoveValidatorStatus.deleted
res2.isOk and res2.value() == RemoveValidatorStatus.missingDir
res2.isOk and res2.value() == RemoveValidatorStatus.notFound
os.removeDir testValidatorsDir
os.removeDir testSecretsDir
suite "createValidatorFiles":
suite "removeValidatorFiles() multiple keystore types":
setup:
let
curKeystoreDir0 {.used.} = testValidatorsDir / MultipleKeystoreNames[0]
curSecretsFile0 {.used.} = testSecretsDir / MultipleKeystoreNames[0]
remoteKeystoreFile0 {.used.} = curKeystoreDir0 / RemoteKeystoreFileName
localKeystoreFile0 {.used.} = curKeystoreDir0 / KeystoreFileName
curSigningKey0 {.used.} = MultipleSigningKeys[0]
curCookedKey0 {.used.} = curSigningKey0.toPubKey()
curPublicKey0 {.used.} = curCookedKey0.toPubKey()
curKeystoreDir1 {.used.} = testValidatorsDir / MultipleKeystoreNames[1]
curSecretsFile1 {.used.} = testSecretsDir / MultipleKeystoreNames[1]
remoteKeystoreFile1 {.used.} = curKeystoreDir1 / RemoteKeystoreFileName
localKeystoreFile1 {.used.} = curKeystoreDir1 / KeystoreFileName
curSigningKey1 {.used.} = MultipleSigningKeys[1]
curCookedKey1 {.used.} = curSigningKey1.toPubKey()
curPublicKey1 {.used.} = curCookedKey1.toPubKey()
curSigningPath {.used.} =
validateKeyPath("m/12381/60/0/0").expect("Valid Keypath")
teardown:
os.removeDir testValidatorsDir
os.removeDir testSecretsDir
test "Remove [LOCAL] when [LOCAL] is present":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[0], KeystoreKind.Local)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.deleted
validatorsCount1 == 1
secretsCount1 == 1
validatorsCount2 == 0
secretsCount2 == 0
not(dirExists(curKeystoreDir0))
not(fileExists(remoteKeystoreFile0))
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [])
test "Remove [LOCAL] when [LOCAL] is missing":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[1], KeystoreKind.Local)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.notFound
validatorsCount1 == 1
secretsCount1 == 1
validatorsCount2 == 1
secretsCount2 == 1
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [MultipleKeystoreNames[0]])
test "Remove [REMOTE] when [REMOTE] is present":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[0], KeystoreKind.Remote)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.deleted
validatorsCount1 == 1
secretsCount1 == 0
validatorsCount2 == 0
secretsCount2 == 0
not(dirExists(curKeystoreDir0))
not(fileExists(remoteKeystoreFile0))
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [])
test "Remove [REMOTE] when [REMOTE] is missing":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[1], KeystoreKind.Remote)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.notFound
validatorsCount1 == 1
secretsCount1 == 0
validatorsCount2 == 1
secretsCount2 == 0
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [MultipleKeystoreNames[0]])
test "Remove [LOCAL] when [REMOTE] is present":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[0], KeystoreKind.Local)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.notFound
validatorsCount1 == 1
secretsCount1 == 0
validatorsCount2 == 1
secretsCount2 == 0
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [MultipleKeystoreNames[0]])
test "Remove [REMOTE] when [LOCAL] is present":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
validatorsCount1 = directoryItemsCount(testValidatorsDir)
secretsCount1 = directoryItemsCount(testSecretsDir)
validatorPubKeys1 = validatorPubKeysInDir(testValidatorsDir)
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
MultipleKeystoreNames[0], KeystoreKind.Remote)
validatorsCount2 = directoryItemsCount(testValidatorsDir)
secretsCount2 = directoryItemsCount(testSecretsDir)
validatorPubKeys2 = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
res2.value == RemoveValidatorStatus.notFound
validatorsCount1 == 1
secretsCount1 == 1
validatorsCount2 == 1
secretsCount2 == 1
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
namesEqual(validatorPubKeys1, [MultipleKeystoreNames[0]])
namesEqual(validatorPubKeys2, [MultipleKeystoreNames[0]])
os.removeDir testValidatorsDir
os.removeDir testSecretsDir
suite "createValidatorFiles()":
setup:
const
password = string.fromBytes hexToSeqByte("7465737470617373776f7264f09f9491")
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
@ -131,18 +412,19 @@ suite "createValidatorFiles":
salt=salt, iv=iv,
description = "This is a test keystore that uses PBKDF2 to secure the secret.",
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
keystoreJsonContents = Json.encode(keystore)
keystoreJsonContents {.used.} = Json.encode(keystore)
hexEncodedPubkey = "0x" & keystore.pubkey.toHex()
keystoreDir = testValidatorsDir / hexEncodedPubkey
secretFile = testSecretsDir / hexEncodedPubkey
keystoreFile = testValidatorsDir / hexEncodedPubkey / KeystoreFileName
keystoreDir {.used.} = testValidatorsDir / hexEncodedPubkey
secretFile {.used.} = testSecretsDir / hexEncodedPubkey
keystoreFile {.used.} = testValidatorsDir / hexEncodedPubkey /
KeystoreFileName
teardown:
os.removeDir testValidatorsDir
os.removeDir testSecretsDir
test "Add keystore files":
test "Add keystore files [LOCAL]":
let
res = createValidatorFiles(testSecretsDir, testValidatorsDir,
keystoreDir,
@ -166,7 +448,9 @@ suite "createValidatorFiles":
secretFile.contentEquals password
keystoreFile.contentEquals keystoreJsonContents
test "Add keystore files twice":
namesEqual(validatorPubKeys, [hexEncodedPubkey])
test "Add keystore files twice [LOCAL]":
let
res1 = createValidatorFiles(testSecretsDir, testValidatorsDir,
keystoreDir,
@ -194,6 +478,76 @@ suite "createValidatorFiles":
secretFile.contentEquals password
keystoreFile.contentEquals keystoreJsonContents
namesEqual(validatorPubKeys, [hexEncodedPubkey])
test "Add keystore files [REMOTE]":
let
curKeystoreDir = testValidatorsDir / MultipleKeystoreNames[0]
curSecretsFile = testSecretsDir / MultipleKeystoreNames[0]
remoteKeystoreFile = curKeystoreDir / RemoteKeystoreFileName
localKeystoreFile = curKeystoreDir / KeystoreFileName
res = createValidatorFiles(testValidatorsDir, curKeystoreDir,
remoteKeystoreFile,
MultipleRemoteKeystoreJsons[0])
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res.isOk
validatorsCount == 1
secretsCount == 0
dirExists(curKeystoreDir)
fileExists(remoteKeystoreFile)
not(fileExists(localKeystoreFile))
not(fileExists(curSecretsFile))
remoteKeystoreFile.contentEquals MultipleRemoteKeystoreJsons[0]
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
test "Add keystore files twice [REMOTE]":
let
curKeystoreDir = testValidatorsDir / MultipleKeystoreNames[0]
curSecretsFile = testSecretsDir / MultipleKeystoreNames[0]
remoteKeystoreFile = curKeystoreDir / RemoteKeystoreFileName
localKeystoreFile = curKeystoreDir / KeystoreFileName
res1 = createValidatorFiles(testValidatorsDir, curKeystoreDir,
remoteKeystoreFile,
MultipleRemoteKeystoreJsons[0])
res2 = createValidatorFiles(testValidatorsDir, curKeystoreDir,
remoteKeystoreFile,
MultipleRemoteKeystoreJsons[0])
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk # The second call should just overwrite the results of the first
validatorsCount == 1
secretsCount == 0
dirExists(curKeystoreDir)
fileExists(remoteKeystoreFile)
not(fileExists(localKeystoreFile))
not(fileExists(curSecretsFile))
remoteKeystoreFile.contentEquals MultipleRemoteKeystoreJsons[0]
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
# TODO The following tests are disabled on Windows because the io2 module
# doesn't implement the permission/mode parameter at the moment:
when not defined(windows):
@ -298,3 +652,263 @@ suite "createValidatorFiles":
secretsCountBefore == secretsCountAfter
os.removeDir testDataDir
suite "saveKeystore()":
setup:
let
curKeystoreDir0 = testValidatorsDir / MultipleKeystoreNames[0]
curSecretsFile0 = testSecretsDir / MultipleKeystoreNames[0]
remoteKeystoreFile0 = curKeystoreDir0 / RemoteKeystoreFileName
localKeystoreFile0 = curKeystoreDir0 / KeystoreFileName
curSigningKey0 = MultipleSigningKeys[0]
curCookedKey0 = curSigningKey0.toPubKey()
curPublicKey0 {.used.} = curCookedKey0.toPubKey()
curKeystoreDir1 = testValidatorsDir / MultipleKeystoreNames[1]
curSecretsFile1 {.used.} = testSecretsDir / MultipleKeystoreNames[1]
remoteKeystoreFile1 {.used.} = curKeystoreDir1 / RemoteKeystoreFileName
localKeystoreFile1 {.used.} = curKeystoreDir1 / KeystoreFileName
curSigningKey1 = MultipleSigningKeys[1]
curCookedKey1 = curSigningKey1.toPubKey()
curPublicKey1 {.used.} = curCookedKey1.toPubKey()
curSigningPath {.used.} =
validateKeyPath("m/12381/60/0/0").expect("Valid Keypath")
teardown:
os.removeDir testValidatorsDir
os.removeDir testSecretsDir
test "Save [LOCAL] keystore after [LOCAL] keystore with same id":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
res2 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isErr
res2.error().kind == DuplicateKeystoreDir
validatorsCount == 1
secretsCount == 1
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
test "Save [REMOTE] keystore after [REMOTE] keystore with same id":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
res2 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isErr
res2.error().kind == DuplicateKeystoreDir
validatorsCount == 1
secretsCount == 0
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
test "Save [REMOTE] keystore after [LOCAL] keystore with same id":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
res2 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isErr
res2.error().kind == DuplicateKeystoreDir
validatorsCount == 1
secretsCount == 1
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
test "Save [LOCAL] keystore after [REMOTE] keystore with same id":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
res2 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isErr
res2.error().kind == DuplicateKeystoreDir
validatorsCount == 1
secretsCount == 0
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
namesEqual(validatorPubKeys, [MultipleKeystoreNames[0]])
test "Save [LOCAL] keystore after [LOCAL] keystore with different id":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
res2 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey1, curCookedKey1, curSigningPath,
"", mode = Fast)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
validatorsCount == 2
secretsCount == 2
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
dirExists(curKeystoreDir1)
not(fileExists(remoteKeystoreFile1))
fileExists(localKeystoreFile1)
fileExists(curSecretsFile1)
namesEqual(validatorPubKeys,
[MultipleKeystoreNames[0], MultipleKeystoreNames[1]])
test "Save [REMOTE] keystore after [REMOTE] keystore with different id":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
res2 = saveKeystore(testValidatorsDir, curPublicKey1, MultipleRemoteUri)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
validatorsCount == 2
secretsCount == 0
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
dirExists(curKeystoreDir1)
fileExists(remoteKeystoreFile1)
not(fileExists(localKeystoreFile1))
not(fileExists(curSecretsFile1))
namesEqual(validatorPubKeys,
[MultipleKeystoreNames[0], MultipleKeystoreNames[1]])
test "Save [LOCAL] keystore after [REMOTE] keystore with different id":
let
res1 = saveKeystore(testValidatorsDir, curPublicKey0, MultipleRemoteUri)
res2 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey1, curCookedKey1, curSigningPath,
"", mode = Fast)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
validatorsCount == 2
secretsCount == 1
dirExists(curKeystoreDir0)
fileExists(remoteKeystoreFile0)
not(fileExists(localKeystoreFile0))
not(fileExists(curSecretsFile0))
dirExists(curKeystoreDir1)
not(fileExists(remoteKeystoreFile1))
fileExists(localKeystoreFile1)
fileExists(curSecretsFile1)
namesEqual(validatorPubKeys,
[MultipleKeystoreNames[0], MultipleKeystoreNames[1]])
test "Save [REMOTE] keystore after [LOCAL] keystore with different id":
let
res1 = saveKeystore(rng[], testValidatorsDir, testSecretsDir,
curSigningKey0, curCookedKey0, curSigningPath,
"", mode = Fast)
res2 = saveKeystore(testValidatorsDir, curPublicKey1, MultipleRemoteUri)
validatorsCount = directoryItemsCount(testValidatorsDir)
secretsCount = directoryItemsCount(testSecretsDir)
validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
check:
res1.isOk
res2.isOk
validatorsCount == 2
secretsCount == 1
dirExists(curKeystoreDir0)
not(fileExists(remoteKeystoreFile0))
fileExists(localKeystoreFile0)
fileExists(curSecretsFile0)
dirExists(curKeystoreDir1)
fileExists(remoteKeystoreFile1)
not(fileExists(localKeystoreFile1))
not(fileExists(curSecretsFile1))
namesEqual(validatorPubKeys,
[MultipleKeystoreNames[0], MultipleKeystoreNames[1]])