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:
parent
c7abc97545
commit
40c77e5928
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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]])
|
||||
|
|
Loading…
Reference in New Issue