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,
|
blockchain_dag, block_quarantine, block_clearance, attestation_pool,
|
||||||
sync_committee_msg_pool, exit_pool, spec_cache],
|
sync_committee_msg_pool, exit_pool, spec_cache],
|
||||||
./eth1/eth1_monitor,
|
./eth1/eth1_monitor,
|
||||||
./spec/eth2_apis/rest_beacon_calls
|
./spec/eth2_apis/[rest_beacon_calls, rest_common]
|
||||||
|
|
||||||
from eth/common/eth_types import BlockHashOrNumber
|
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).
|
# * 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.
|
# 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,
|
import chronos, chronicles, confutils,
|
||||||
stew/[base10, results, io2], bearssl, blscurve
|
stew/[base10, results, io2], bearssl, blscurve
|
||||||
import ".."/validators/slashing_protection
|
import ".."/validators/slashing_protection
|
||||||
import ".."/[conf, version, filepath, beacon_node]
|
import ".."/[conf, version, filepath, beacon_node]
|
||||||
import ".."/spec/[keystore, crypto]
|
import ".."/spec/[keystore, crypto]
|
||||||
import ".."/rpc/rest_utils
|
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
|
import ".."/spec/eth2_apis/rest_keymanager_types
|
||||||
|
|
||||||
export
|
export rest_utils, results
|
||||||
rest_utils,
|
|
||||||
results
|
|
||||||
|
|
||||||
proc listValidators*(validatorsDir,
|
proc listLocalValidators*(node: BeaconNode): seq[KeystoreInfo] {.
|
||||||
secretsDir: string): seq[KeystoreInfo]
|
raises: [Defect].} =
|
||||||
{.raises: [Defect].} =
|
|
||||||
var validators: seq[KeystoreInfo]
|
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:
|
proc listRemoteValidators*(node: BeaconNode): seq[RemoteKeystoreInfo] {.
|
||||||
for el in listLoadableKeystores(validatorsDir, secretsDir, true):
|
raises: [Defect].} =
|
||||||
validators.add KeystoreInfo(validating_pubkey: el.pubkey,
|
var validators: seq[RemoteKeystoreInfo]
|
||||||
derivation_path: el.path.string,
|
for item in node.attachedValidators[].items():
|
||||||
readonly: false)
|
if item.kind == ValidatorKind.Remote:
|
||||||
except OSError as err:
|
validators.add RemoteKeystoreInfo(
|
||||||
error "Failure to list the validator directories",
|
pubkey: item.pubkey,
|
||||||
validatorsDir, secretsDir, err = err.msg
|
url: HttpHostUri(item.data.remoteUrl)
|
||||||
|
)
|
||||||
validators
|
validators
|
||||||
|
|
||||||
proc checkAuthorization*(request: HttpRequestRef,
|
proc checkAuthorization*(request: HttpRequestRef,
|
||||||
|
@ -49,6 +54,14 @@ proc checkAuthorization*(request: HttpRequestRef,
|
||||||
else:
|
else:
|
||||||
return err noAuthorizationHeader
|
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) =
|
proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys
|
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys
|
||||||
router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse:
|
||||||
|
@ -56,9 +69,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
||||||
$authStatus.error())
|
$authStatus.error())
|
||||||
let response = GetKeystoresResponse(
|
let response = GetKeystoresResponse(data: listLocalValidators(node))
|
||||||
data: listValidators(node.config.validatorsDir(),
|
|
||||||
node.config.secretsDir()))
|
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
return RestApiResponse.jsonResponsePlain(response)
|
||||||
|
|
||||||
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores
|
# https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores
|
||||||
|
@ -78,34 +89,39 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
$dres.error())
|
$dres.error())
|
||||||
dres.get()
|
dres.get()
|
||||||
|
|
||||||
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
|
if request.slashing_protection.isSome():
|
||||||
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
|
let slashing_protection = request.slashing_protection.get()
|
||||||
request.slashing_protection.metadata.genesis_validators_root.Eth2Digest:
|
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
|
||||||
return RestApiResponse.jsonError(
|
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
|
||||||
Http400,
|
slashing_protection.metadata.genesis_validators_root.Eth2Digest:
|
||||||
"The slashing protection database and imported file refer to different blockchains.")
|
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
|
var response: PostKeystoresResponse
|
||||||
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")
|
|
||||||
|
|
||||||
for index, item in request.keystores.pairs():
|
for index, item in request.keystores.pairs():
|
||||||
let res = importKeystore(node.attachedValidators[], node.network.rng[],
|
let res = importKeystore(node.attachedValidators[], node.network.rng[],
|
||||||
node.config, item, request.passwords[index])
|
node.config, item, request.passwords[index])
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
response.data.add(RequestItemStatus(status: $KeystoreStatus.error,
|
let failure = res.error()
|
||||||
message: $res.error()))
|
case failure.status
|
||||||
|
of AddValidatorStatus.failed:
|
||||||
elif res.value() == AddValidatorStatus.existingArtifacts:
|
response.data.add(
|
||||||
response.data.add(RequestItemStatus(status: $KeystoreStatus.duplicate))
|
RequestItemStatus(status: $KeystoreStatus.error,
|
||||||
|
message: failure.message))
|
||||||
|
of AddValidatorStatus.existingArtifacts:
|
||||||
|
response.data.add(
|
||||||
|
RequestItemStatus(status: $KeystoreStatus.duplicate))
|
||||||
else:
|
else:
|
||||||
response.data.add(RequestItemStatus(status: $KeystoreStatus.imported))
|
node.addLocalValidators([res.get()])
|
||||||
|
response.data.add(
|
||||||
|
RequestItemStatus(status: $KeystoreStatus.imported))
|
||||||
|
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
return RestApiResponse.jsonResponsePlain(response)
|
||||||
|
|
||||||
|
@ -136,19 +152,21 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
for index, key in keys.pairs():
|
for index, key in keys.pairs():
|
||||||
let
|
let
|
||||||
res = removeValidator(node.attachedValidators[], node.config, key)
|
res = removeValidator(node.attachedValidators[], node.config, key,
|
||||||
|
KeystoreKind.Local)
|
||||||
pubkey = key.blob.PubKey0x.PubKeyBytes
|
pubkey = key.blob.PubKey0x.PubKeyBytes
|
||||||
|
|
||||||
if res.isOk:
|
if res.isOk:
|
||||||
case res.value()
|
case res.value()
|
||||||
of RemoveValidatorStatus.deleted:
|
of RemoveValidatorStatus.deleted:
|
||||||
keysAndDeleteStatus.add(pubkey,
|
keysAndDeleteStatus.add(
|
||||||
RequestItemStatus(status: $KeystoreStatus.deleted))
|
pubkey, RequestItemStatus(status: $KeystoreStatus.deleted))
|
||||||
|
|
||||||
# At first all keys with status missing directory after removal receive status 'not_found'
|
# At first all keys with status missing directory after removal receive
|
||||||
of RemoveValidatorStatus.missingDir:
|
# status 'not_found'
|
||||||
keysAndDeleteStatus.add(pubkey,
|
of RemoveValidatorStatus.notFound:
|
||||||
RequestItemStatus(status: $KeystoreStatus.notFound))
|
keysAndDeleteStatus.add(
|
||||||
|
pubkey, RequestItemStatus(status: $KeystoreStatus.notFound))
|
||||||
else:
|
else:
|
||||||
keysAndDeleteStatus.add(pubkey,
|
keysAndDeleteStatus.add(pubkey,
|
||||||
RequestItemStatus(status: $KeystoreStatus.error,
|
RequestItemStatus(status: $KeystoreStatus.error,
|
||||||
|
@ -169,6 +187,96 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
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(
|
router.redirect(
|
||||||
MethodGet,
|
MethodGet,
|
||||||
"/eth/v1/keystores",
|
"/eth/v1/keystores",
|
||||||
|
@ -183,3 +291,18 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
MethodPost,
|
MethodPost,
|
||||||
"/eth/v1/keystores/delete",
|
"/eth/v1/keystores/delete",
|
||||||
"/api/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 std/typetraits
|
||||||
import stew/[assign2, results, base10, byteutils], presto/common,
|
import stew/[assign2, results, base10, byteutils], presto/common,
|
||||||
libp2p/peerid,
|
libp2p/peerid, serialization, json_serialization,
|
||||||
serialization, json_serialization, json_serialization/std/[options, net, sets]
|
json_serialization/std/[options, net, sets]
|
||||||
import ".."/[eth2_ssz_serialization, forks],
|
import ".."/[eth2_ssz_serialization, forks, keystore],
|
||||||
".."/datatypes/[phase0, altair, bellatrix],
|
".."/datatypes/[phase0, altair, bellatrix],
|
||||||
|
".."/../validators/slashing_protection_common,
|
||||||
"."/[rest_types, rest_keymanager_types]
|
"."/[rest_types, rest_keymanager_types]
|
||||||
|
import nimcrypto/utils as ncrutils
|
||||||
|
|
||||||
export
|
export
|
||||||
eth2_ssz_serialization, results, peerid, common, serialization,
|
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
|
from web3/ethtypes import BlockHash
|
||||||
export ethtypes.BlockHash
|
export ethtypes.BlockHash
|
||||||
|
@ -54,7 +56,8 @@ type
|
||||||
SignedVoluntaryExit |
|
SignedVoluntaryExit |
|
||||||
Web3SignerRequest |
|
Web3SignerRequest |
|
||||||
KeystoresAndSlashingProtection |
|
KeystoresAndSlashingProtection |
|
||||||
DeleteKeystoresBody
|
DeleteKeystoresBody |
|
||||||
|
ImportRemoteKeystoresBody
|
||||||
|
|
||||||
EncodeArrays* =
|
EncodeArrays* =
|
||||||
seq[ValidatorIndex] |
|
seq[ValidatorIndex] |
|
||||||
|
@ -63,7 +66,8 @@ type
|
||||||
seq[RestCommitteeSubscription] |
|
seq[RestCommitteeSubscription] |
|
||||||
seq[RestSyncCommitteeSubscription] |
|
seq[RestSyncCommitteeSubscription] |
|
||||||
seq[RestSyncCommitteeMessage] |
|
seq[RestSyncCommitteeMessage] |
|
||||||
seq[RestSignedContributionAndProof]
|
seq[RestSignedContributionAndProof] |
|
||||||
|
seq[RemoteKeystoreInfo]
|
||||||
|
|
||||||
DecodeTypes* =
|
DecodeTypes* =
|
||||||
DataEnclosedObject |
|
DataEnclosedObject |
|
||||||
|
@ -71,6 +75,7 @@ type
|
||||||
DataRootEnclosedObject |
|
DataRootEnclosedObject |
|
||||||
GetBlockV2Response |
|
GetBlockV2Response |
|
||||||
GetKeystoresResponse |
|
GetKeystoresResponse |
|
||||||
|
GetRemoteKeystoresResponse |
|
||||||
GetStateV2Response |
|
GetStateV2Response |
|
||||||
GetStateForkResponse |
|
GetStateForkResponse |
|
||||||
ProduceBlockResponseV2 |
|
ProduceBlockResponseV2 |
|
||||||
|
@ -1265,6 +1270,331 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
syncCommitteeContributionAndProof: data
|
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] =
|
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
|
||||||
try:
|
try:
|
||||||
ok(Eth2Digest(data: hexToByteArray[32](value)))
|
ok(Eth2Digest(data: hexToByteArray[32](value)))
|
||||||
|
@ -1277,8 +1607,8 @@ proc decodeBody*[T](t: typedesc[T],
|
||||||
return err("Unsupported content type")
|
return err("Unsupported content type")
|
||||||
let data =
|
let data =
|
||||||
try:
|
try:
|
||||||
RestJson.decode(cast[string](body.data), T)
|
RestJson.decode(body.data, T)
|
||||||
except SerializationError:
|
except SerializationError as exc:
|
||||||
return err("Unable to deserialize data")
|
return err("Unable to deserialize data")
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return err("Unexpected deserialization error")
|
return err("Unexpected deserialization error")
|
||||||
|
|
|
@ -11,14 +11,10 @@ import
|
||||||
".."/".."/validators/slashing_protection_common,
|
".."/".."/validators/slashing_protection_common,
|
||||||
".."/datatypes/[phase0, altair],
|
".."/datatypes/[phase0, altair],
|
||||||
".."/[helpers, forks, keystore, eth2_ssz_serialization],
|
".."/[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
|
export chronos, client, rest_types, eth2_rest_serialization
|
||||||
|
|
||||||
UUID.serializesAsBaseIn RestJson
|
|
||||||
KeyPath.serializesAsBaseIn RestJson
|
|
||||||
WalletName.serializesAsBaseIn RestJson
|
|
||||||
|
|
||||||
proc getGenesis*(): RestResponse[GetGenesisResponse] {.
|
proc getGenesis*(): RestResponse[GetGenesisResponse] {.
|
||||||
rest, endpoint: "/eth/v1/beacon/genesis",
|
rest, endpoint: "/eth/v1/beacon/genesis",
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
|
@ -119,24 +115,6 @@ proc getBlockPlain*(block_id: BlockIdent): RestPlainResponse {.
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
|
## 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,
|
proc getBlock*(client: RestClientRef, block_id: BlockIdent,
|
||||||
restAccept = preferSSZ): Future[ForkedSignedBeaconBlock] {.async.} =
|
restAccept = preferSSZ): Future[ForkedSignedBeaconBlock] {.async.} =
|
||||||
# TODO restAccept should be "" by default, but for some reason that doesn't
|
# 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",
|
rest, endpoint: "/eth/v1/beacon/pool/voluntary_exits",
|
||||||
meth: MethodPost.}
|
meth: MethodPost.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit
|
## 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,
|
chronos, presto/client,
|
||||||
"."/[
|
"."/[
|
||||||
rest_beacon_calls, rest_config_calls, rest_debug_calls,
|
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
|
export
|
||||||
chronos, client,
|
chronos, client,
|
||||||
rest_beacon_calls, rest_config_calls, rest_debug_calls,
|
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
|
import
|
||||||
std/[tables, strutils],
|
std/[tables, strutils, uri],
|
||||||
".."/[crypto, keystore],
|
".."/[crypto, keystore],
|
||||||
../../validators/slashing_protection_common
|
../../validators/slashing_protection_common
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ type
|
||||||
derivation_path*: string
|
derivation_path*: string
|
||||||
readonly*: bool
|
readonly*: bool
|
||||||
|
|
||||||
|
RemoteKeystoreInfo* = object
|
||||||
|
pubkey*: ValidatorPubKey
|
||||||
|
url*: HttpHostUri
|
||||||
|
|
||||||
RequestItemStatus* = object
|
RequestItemStatus* = object
|
||||||
status*: string
|
status*: string
|
||||||
message*: string
|
message*: string
|
||||||
|
@ -16,7 +20,7 @@ type
|
||||||
KeystoresAndSlashingProtection* = object
|
KeystoresAndSlashingProtection* = object
|
||||||
keystores*: seq[Keystore]
|
keystores*: seq[Keystore]
|
||||||
passwords*: seq[string]
|
passwords*: seq[string]
|
||||||
slashing_protection*: SPDIR
|
slashing_protection*: Option[SPDIR]
|
||||||
|
|
||||||
DeleteKeystoresBody* = object
|
DeleteKeystoresBody* = object
|
||||||
pubkeys*: seq[ValidatorPubKey]
|
pubkeys*: seq[ValidatorPubKey]
|
||||||
|
@ -24,6 +28,12 @@ type
|
||||||
GetKeystoresResponse* = object
|
GetKeystoresResponse* = object
|
||||||
data*: seq[KeystoreInfo]
|
data*: seq[KeystoreInfo]
|
||||||
|
|
||||||
|
GetRemoteKeystoresResponse* = object
|
||||||
|
data*: seq[RemoteKeystoreInfo]
|
||||||
|
|
||||||
|
ImportRemoteKeystoresBody* = object
|
||||||
|
remote_keys*: seq[RemoteKeystoreInfo]
|
||||||
|
|
||||||
PostKeystoresResponse* = object
|
PostKeystoresResponse* = object
|
||||||
data*: seq[RequestItemStatus]
|
data*: seq[RequestItemStatus]
|
||||||
|
|
||||||
|
@ -31,6 +41,13 @@ type
|
||||||
data*: seq[RequestItemStatus]
|
data*: seq[RequestItemStatus]
|
||||||
slashing_protection*: SPDIR
|
slashing_protection*: SPDIR
|
||||||
|
|
||||||
|
RemoteKeystoreStatus* = object
|
||||||
|
status*: KeystoreStatus
|
||||||
|
message*: Option[string]
|
||||||
|
|
||||||
|
DeleteRemoteKeystoresResponse* = object
|
||||||
|
data*: seq[RemoteKeystoreStatus]
|
||||||
|
|
||||||
KeystoreStatus* = enum
|
KeystoreStatus* = enum
|
||||||
error = "error"
|
error = "error"
|
||||||
notActive = "not_active"
|
notActive = "not_active"
|
||||||
|
@ -44,7 +61,7 @@ type
|
||||||
missingBearerScheme = "Bearer Authentication is not included in request"
|
missingBearerScheme = "Bearer Authentication is not included in request"
|
||||||
incorrectToken = "Authentication token is incorrect"
|
incorrectToken = "Authentication token is incorrect"
|
||||||
|
|
||||||
proc `<`*(x, y: KeystoreInfo): bool =
|
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
|
||||||
for a, b in fields(x, y):
|
for a, b in fields(x, y):
|
||||||
var c = cmp(a, b)
|
var c = cmp(a, b)
|
||||||
if c < 0: return true
|
if c < 0: return true
|
||||||
|
|
|
@ -14,7 +14,7 @@ import
|
||||||
# Third-party libraries
|
# Third-party libraries
|
||||||
normalize,
|
normalize,
|
||||||
# Status libraries
|
# Status libraries
|
||||||
stew/[results, bitops2], stew/shims/macros,
|
stew/[results, bitops2, base10], stew/shims/macros,
|
||||||
bearssl, eth/keyfile/uuid, blscurve, json_serialization,
|
bearssl, eth/keyfile/uuid, blscurve, json_serialization,
|
||||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
||||||
# Local modules
|
# Local modules
|
||||||
|
@ -72,9 +72,9 @@ type
|
||||||
ScryptSalt* = distinct seq[byte]
|
ScryptSalt* = distinct seq[byte]
|
||||||
|
|
||||||
ScryptParams* = object
|
ScryptParams* = object
|
||||||
dklen: uint64
|
dklen*: uint64
|
||||||
n, p, r: int
|
n*, p*, r*: int
|
||||||
salt: ScryptSalt
|
salt*: ScryptSalt
|
||||||
|
|
||||||
Pbkdf2Salt* = distinct seq[byte]
|
Pbkdf2Salt* = distinct seq[byte]
|
||||||
|
|
||||||
|
@ -130,6 +130,8 @@ type
|
||||||
RemoteKeystoreFlag* {.pure.} = enum
|
RemoteKeystoreFlag* {.pure.} = enum
|
||||||
IgnoreSSLVerification
|
IgnoreSSLVerification
|
||||||
|
|
||||||
|
HttpHostUri* = distinct Uri
|
||||||
|
|
||||||
KeystoreData* = object
|
KeystoreData* = object
|
||||||
version*: uint64
|
version*: uint64
|
||||||
pubkey*: ValidatorPubKey
|
pubkey*: ValidatorPubKey
|
||||||
|
@ -140,7 +142,7 @@ type
|
||||||
path*: KeyPath
|
path*: KeyPath
|
||||||
uuid*: string
|
uuid*: string
|
||||||
of KeystoreKind.Remote:
|
of KeystoreKind.Remote:
|
||||||
remoteUrl*: Uri
|
remoteUrl*: HttpHostUri
|
||||||
flags*: set[RemoteKeystoreFlag]
|
flags*: set[RemoteKeystoreFlag]
|
||||||
|
|
||||||
NetKeystore* = object
|
NetKeystore* = object
|
||||||
|
@ -158,7 +160,7 @@ type
|
||||||
description*: Option[string]
|
description*: Option[string]
|
||||||
remoteType*: RemoteSignerType
|
remoteType*: RemoteSignerType
|
||||||
pubkey*: ValidatorPubKey
|
pubkey*: ValidatorPubKey
|
||||||
remote*: Uri
|
remote*: HttpHostUri
|
||||||
flags*: set[RemoteKeystoreFlag]
|
flags*: set[RemoteKeystoreFlag]
|
||||||
|
|
||||||
KsResult*[T] = Result[T, string]
|
KsResult*[T] = Result[T, string]
|
||||||
|
@ -181,7 +183,7 @@ type
|
||||||
signingKey*: ValidatorPrivKey
|
signingKey*: ValidatorPrivKey
|
||||||
withdrawalKey*: ValidatorPrivKey
|
withdrawalKey*: ValidatorPrivKey
|
||||||
|
|
||||||
SimpleHexEncodedTypes = ScryptSalt|ChecksumBytes|CipherBytes
|
SimpleHexEncodedTypes* = ScryptSalt|ChecksumBytes|CipherBytes
|
||||||
|
|
||||||
const
|
const
|
||||||
keyLen = 32
|
keyLen = 32
|
||||||
|
@ -217,6 +219,15 @@ CipherFunctionKind.serializesAsTextInJson
|
||||||
PrfKind.serializesAsTextInJson
|
PrfKind.serializesAsTextInJson
|
||||||
KdfKind.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 =
|
template `$`*(m: Mnemonic): string =
|
||||||
string(m)
|
string(m)
|
||||||
|
|
||||||
|
@ -491,8 +502,8 @@ proc readValue*(r: var JsonReader, value: var Aes128CtrIv)
|
||||||
r.raiseUnexpectedValue(
|
r.raiseUnexpectedValue(
|
||||||
"The aes-128-ctr IV must be a valid hex string")
|
"The aes-128-ctr IV must be a valid hex string")
|
||||||
|
|
||||||
proc readValue*[T: SimpleHexEncodedTypes](r: var JsonReader, value: var T)
|
proc readValue*[T: SimpleHexEncodedTypes](r: var JsonReader, value: var T) {.
|
||||||
{.raises: [SerializationError, IOError, Defect].} =
|
raises: [SerializationError, IOError, Defect].} =
|
||||||
value = T ncrutils.fromHex(r.readValue(string))
|
value = T ncrutils.fromHex(r.readValue(string))
|
||||||
if len(seq[byte](value)) == 0:
|
if len(seq[byte](value)) == 0:
|
||||||
r.raiseUnexpectedValue("Valid hex string expected")
|
r.raiseUnexpectedValue("Valid hex string expected")
|
||||||
|
@ -531,77 +542,121 @@ proc readValue*(r: var JsonReader, value: var Kdf)
|
||||||
r.raiseUnexpectedValue(
|
r.raiseUnexpectedValue(
|
||||||
"The Kdf value should have sub-fields named 'function' and 'params'")
|
"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].} =
|
{.raises: [SerializationError, IOError, Defect].} =
|
||||||
var
|
var
|
||||||
versionWasPresent = false
|
version: Option[uint64]
|
||||||
description: Option[string]
|
description: Option[string]
|
||||||
remote: Option[Uri]
|
remote: Option[HttpHostUri]
|
||||||
remoteType: Option[string]
|
remoteType: Option[string]
|
||||||
ignoreSslVerification: Option[bool]
|
ignoreSslVerification: Option[bool]
|
||||||
pubkey: Option[ValidatorPubKey]
|
pubkey: Option[ValidatorPubKey]
|
||||||
for fieldName in readObjectFields(r):
|
|
||||||
|
for fieldName in readObjectFields(reader):
|
||||||
case fieldName:
|
case fieldName:
|
||||||
of "pubkey":
|
of "pubkey":
|
||||||
if pubkey.isSome():
|
if pubkey.isSome():
|
||||||
r.raiseUnexpectedField("Multiple `pubkey` fields found",
|
reader.raiseUnexpectedField("Multiple `pubkey` fields found",
|
||||||
"RemoteKeystore")
|
"RemoteKeystore")
|
||||||
let res = r.readValue(ValidatorPubKey)
|
pubkey = some(reader.readValue(ValidatorPubKey))
|
||||||
pubkey = some(res)
|
|
||||||
value.pubkey = res
|
|
||||||
of "remote":
|
of "remote":
|
||||||
if remote.isSome():
|
if remote.isSome():
|
||||||
r.raiseUnexpectedField("Multiple `remote` fields found",
|
reader.raiseUnexpectedField("Multiple `remote` fields found",
|
||||||
"RemoteKeystore")
|
"RemoteKeystore")
|
||||||
let res = r.readValue(Uri)
|
remote = some(reader.readValue(HttpHostUri))
|
||||||
remote = some(res)
|
|
||||||
value.remote = res
|
|
||||||
of "version":
|
of "version":
|
||||||
if versionWasPresent:
|
if version.isSome():
|
||||||
r.raiseUnexpectedField("Multiple `version` fields found",
|
reader.raiseUnexpectedField("Multiple `version` fields found",
|
||||||
"RemoteKeystore")
|
"RemoteKeystore")
|
||||||
value.version = r.readValue(uint64)
|
version = some(reader.readValue(uint64))
|
||||||
versionWasPresent = true
|
|
||||||
of "description":
|
of "description":
|
||||||
let res = r.readValue(string)
|
let res = reader.readValue(string)
|
||||||
if value.description.isSome():
|
if description.isSome():
|
||||||
value.description = some(value.description.get() & "\n" & res)
|
description = some(description.get() & "\n" & res)
|
||||||
else:
|
else:
|
||||||
value.description = some(res)
|
description = some(res)
|
||||||
of "ignore_ssl_verification":
|
of "ignore_ssl_verification":
|
||||||
if ignoreSslVerification.isSome():
|
if ignoreSslVerification.isSome():
|
||||||
r.raiseUnexpectedField("Multiple conflicting options found",
|
reader.raiseUnexpectedField("Multiple conflicting options found",
|
||||||
"RemoteKeystore")
|
"RemoteKeystore")
|
||||||
let res = r.readValue(bool)
|
ignoreSslVerification = some(reader.readValue(bool))
|
||||||
ignoreSslVerification = some(res)
|
|
||||||
if res:
|
|
||||||
value.flags.incl(RemoteKeystoreFlag.IgnoreSSLVerification)
|
|
||||||
else:
|
|
||||||
value.flags.excl(RemoteKeystoreFlag.IgnoreSSLVerification)
|
|
||||||
of "type":
|
of "type":
|
||||||
if remoteType.isSome():
|
if remoteType.isSome():
|
||||||
r.raiseUnexpectedField("Multiple `type` fields found",
|
reader.raiseUnexpectedField("Multiple `type` fields found",
|
||||||
"RemoteKeystore")
|
"RemoteKeystore")
|
||||||
let res = r.readValue(string)
|
remoteType = some(reader.readValue(string))
|
||||||
remoteType = some(res)
|
|
||||||
case res
|
|
||||||
of "web3signer":
|
|
||||||
value.remoteType = RemoteSignerType.Web3Signer
|
|
||||||
else:
|
|
||||||
r.raiseUnexpectedValue("Unsupported remote signer `type` value")
|
|
||||||
else:
|
else:
|
||||||
# Ignore unknown field names.
|
# Ignore unknown field names.
|
||||||
discard
|
discard
|
||||||
|
|
||||||
if not versionWasPresent:
|
if version.isNone():
|
||||||
r.raiseUnexpectedValue("Field version is missing")
|
reader.raiseUnexpectedValue("Field `version` is missing")
|
||||||
if remote.isNone():
|
if remote.isNone():
|
||||||
r.raiseUnexpectedValue("Field remote is missing")
|
reader.raiseUnexpectedValue("Field `remote` is missing")
|
||||||
if pubkey.isNone():
|
if pubkey.isNone():
|
||||||
r.raiseUnexpectedValue("Field pubkey is missing")
|
reader.raiseUnexpectedValue("Field `pubkey` is missing")
|
||||||
# Set default remote signer type to `Web3Signer`.
|
|
||||||
if remoteType.isNone():
|
let keystoreType =
|
||||||
value.remoteType = RemoteSignerType.Web3Signer
|
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,
|
template writeValue*(w: var JsonWriter,
|
||||||
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
|
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
|
||||||
|
@ -839,6 +894,20 @@ proc createKeystore*(kdfKind: KdfKind,
|
||||||
uuid: $uuid,
|
uuid: $uuid,
|
||||||
version: 4)
|
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,
|
proc createWallet*(kdfKind: KdfKind,
|
||||||
rng: var BrHmacDrbgContext,
|
rng: var BrHmacDrbgContext,
|
||||||
seed: KeySeed,
|
seed: KeySeed,
|
||||||
|
|
|
@ -34,6 +34,7 @@ const
|
||||||
DisableFileName* = ".disable"
|
DisableFileName* = ".disable"
|
||||||
DisableFileContent* = "Please do not remove this file manually. " &
|
DisableFileContent* = "Please do not remove this file manually. " &
|
||||||
"This can lead to slashing of this validator's key."
|
"This can lead to slashing of this validator's key."
|
||||||
|
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters.
|
||||||
|
|
||||||
type
|
type
|
||||||
WalletPathPair* = object
|
WalletPathPair* = object
|
||||||
|
@ -48,13 +49,19 @@ type
|
||||||
|
|
||||||
KmResult*[T] = Result[T, cstring]
|
KmResult*[T] = Result[T, cstring]
|
||||||
|
|
||||||
RemoveValidatorStatus* = enum
|
RemoveValidatorStatus* {.pure.} = enum
|
||||||
deleted = "Deleted"
|
deleted = "Deleted"
|
||||||
missingDir = "Could not find keystore directory to remove"
|
notFound = "Not found"
|
||||||
|
|
||||||
AddValidatorStatus* = enum
|
AddValidatorStatus* {.pure.} = enum
|
||||||
added = "Validator added"
|
|
||||||
existingArtifacts = "Keystore artifacts already exists"
|
existingArtifacts = "Keystore artifacts already exists"
|
||||||
|
failed = "Validator not added"
|
||||||
|
|
||||||
|
AddValidatorFailure* = object
|
||||||
|
status*: AddValidatorStatus
|
||||||
|
message*: string
|
||||||
|
|
||||||
|
ImportResult*[T] = Result[T, AddValidatorFailure]
|
||||||
|
|
||||||
const
|
const
|
||||||
minPasswordLen = 12
|
minPasswordLen = 12
|
||||||
|
@ -100,6 +107,26 @@ func init*(T: type KeystoreData,
|
||||||
remoteUrl: keystore.remote
|
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 =
|
proc checkAndCreateDataDir*(dataDir: string): bool =
|
||||||
when defined(posix):
|
when defined(posix):
|
||||||
let requiredPerms = 0o700
|
let requiredPerms = 0o700
|
||||||
|
@ -344,24 +371,34 @@ proc loadKeystoreUnsafe*(validatorsDir, secretsDir,
|
||||||
|
|
||||||
proc loadRemoteKeystoreImpl(validatorsDir,
|
proc loadRemoteKeystoreImpl(validatorsDir,
|
||||||
keyName: string): Option[KeystoreData] =
|
keyName: string): Option[KeystoreData] =
|
||||||
let remoteKeystorePath = validatorsDir / keyName / RemoteKeystoreFileName
|
let keystorePath = validatorsDir / keyName / RemoteKeystoreFileName
|
||||||
let privateItem =
|
|
||||||
|
if not(checkSensitiveFilePermissions(keystorePath)):
|
||||||
|
error "Remote keystorage file has insecure permissions",
|
||||||
|
key_path = keystorePath
|
||||||
|
return
|
||||||
|
|
||||||
|
let keyStore =
|
||||||
block:
|
block:
|
||||||
let keystore =
|
let remoteKeystore =
|
||||||
try:
|
try:
|
||||||
Json.decode(remoteKeystorePath, RemoteKeystore)
|
Json.loadFile(keystorePath, RemoteKeystore)
|
||||||
except SerializationError as e:
|
except IOError as err:
|
||||||
error "Failed to read remote keystore file",
|
error "Failed to read remote keystore file", err = err.msg,
|
||||||
keystore_path = remoteKeystorePath,
|
path = keystorePath
|
||||||
err_msg = e.formatMsg(remoteKeystorePath)
|
|
||||||
return
|
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():
|
if res.isErr():
|
||||||
error "Invalid validator's public key in keystore file",
|
error "Invalid remote keystore file",
|
||||||
keystore_path = remoteKeystorePath
|
path = keystorePath
|
||||||
return
|
return
|
||||||
res.get()
|
res.get()
|
||||||
some(privateItem)
|
some(keyStore)
|
||||||
|
|
||||||
proc loadKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
proc loadKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
nonInteractive: bool): Option[KeystoreData] =
|
nonInteractive: bool): Option[KeystoreData] =
|
||||||
|
@ -433,43 +470,108 @@ proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
||||||
error "Unable to find any keystore files", keystorePath
|
error "Unable to find any keystore files", keystorePath
|
||||||
none[KeystoreData]()
|
none[KeystoreData]()
|
||||||
|
|
||||||
proc removeValidatorFiles*(validatorsDir,
|
proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
|
||||||
secretsDir ,
|
kind: KeystoreKind
|
||||||
publicKeyDir: string): KmResult[RemoveValidatorStatus] {.
|
): KmResult[RemoveValidatorStatus] {.
|
||||||
raises: [Defect].} =
|
raises: [Defect].} =
|
||||||
let keystoreDir = validatorsDir / publicKeyDir
|
let
|
||||||
let keystoreFile = keystoreDir / KeystoreFileName
|
keystoreDir = validatorsDir / keyName
|
||||||
let secretFile = secretsDir / publicKeyDir
|
keystoreFile =
|
||||||
|
case kind
|
||||||
|
of KeystoreKind.Local:
|
||||||
|
keystoreDir / KeystoreFileName
|
||||||
|
of KeystoreKind.Remote:
|
||||||
|
keystoreDir / RemoteKeystoreFileName
|
||||||
|
secretFile = secretsDir / keyName
|
||||||
|
|
||||||
if not (dirExists(keystoreDir)):
|
if not(existsDir(keystoreDir)):
|
||||||
return ok(missingDir)
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
try:
|
|
||||||
removeDir(keystoreDir, false)
|
|
||||||
except OSError:
|
|
||||||
return err("Could not remove keystore directory")
|
|
||||||
|
|
||||||
let res = io2.removeFile(secretFile)
|
if not(existsFile(keystoreFile)):
|
||||||
if res.isErr():
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
return err("Could not remove password file")
|
|
||||||
|
|
||||||
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,
|
proc removeValidator*(pool: var ValidatorPool, conf: AnyConf,
|
||||||
publicKey: ValidatorPubKey): KmResult[RemoveValidatorStatus] {.
|
publicKey: ValidatorPubKey,
|
||||||
|
kind: KeystoreKind): KmResult[RemoveValidatorStatus] {.
|
||||||
raises: [Defect].} =
|
raises: [Defect].} =
|
||||||
let publicKeyHex: string = "0x" & publicKey.toHex()
|
let validator = pool.getValidator(publicKey)
|
||||||
let res = removeValidatorFiles(conf.validatorsDir(),
|
if isNil(validator):
|
||||||
conf.secretsDir(),
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
publicKeyHex)
|
if validator.kind.toKeystoreKind() != kind:
|
||||||
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
|
let publicKeyName: string = "0x" & publicKey.toHex()
|
||||||
|
let res = removeValidatorFiles(conf, publicKeyName, kind)
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return err(res.error())
|
return err(res.error())
|
||||||
|
|
||||||
pool.removeValidator(publicKey)
|
pool.removeValidator(publicKey)
|
||||||
ok res.value()
|
ok(res.value())
|
||||||
|
|
||||||
iterator listLoadableKeystores*(validatorsDir,
|
proc checkKeyName*(keyName: string): bool =
|
||||||
secretsDir: string,
|
const keyAlphabet = {'a'..'f', 'A'..'F', '0'..'9'}
|
||||||
nonInteractive: bool): KeystoreData =
|
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:
|
try:
|
||||||
for kind, file in walkDir(validatorsDir):
|
for kind, file in walkDir(validatorsDir):
|
||||||
if kind == pcDir:
|
if kind == pcDir:
|
||||||
|
@ -479,26 +581,22 @@ iterator listLoadableKeystores*(validatorsDir,
|
||||||
keystoreDir = validatorsDir / keyName
|
keystoreDir = validatorsDir / keyName
|
||||||
keystoreFile = keystoreDir / KeystoreFileName
|
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.
|
# Skip folders which do not have keystore file inside.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let
|
let
|
||||||
secretFile = secretsDir / keyName
|
secretFile = secretsDir / keyName
|
||||||
keystore = loadKeystore(validatorsDir, secretsDir, keyName, nonInteractive)
|
keystore = loadKeystore(validatorsDir, secretsDir, keyName,
|
||||||
|
nonInteractive)
|
||||||
if keystore.isSome():
|
if keystore.isSome():
|
||||||
let pubkey = keystore.get().privateKey.toPubKey().toPubKey()
|
yield keystore.get()
|
||||||
|
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
fatal "Unable to load keystore", keystore_file = keystoreFile
|
fatal "Unable to load keystore", keystore = file
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
|
@ -509,7 +607,8 @@ iterator listLoadableKeystores*(validatorsDir,
|
||||||
iterator listLoadableKeystores*(config: AnyConf): KeystoreData =
|
iterator listLoadableKeystores*(config: AnyConf): KeystoreData =
|
||||||
for el in listLoadableKeystores(config.validatorsDir(),
|
for el in listLoadableKeystores(config.validatorsDir(),
|
||||||
config.secretsDir(),
|
config.secretsDir(),
|
||||||
config.nonInteractive):
|
config.nonInteractive,
|
||||||
|
{KeystoreKind.Local, KeystoreKind.Remote}):
|
||||||
yield el
|
yield el
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -519,6 +618,8 @@ type
|
||||||
FailedToCreateSecretsDir
|
FailedToCreateSecretsDir
|
||||||
FailedToCreateSecretFile
|
FailedToCreateSecretFile
|
||||||
FailedToCreateKeystoreFile
|
FailedToCreateKeystoreFile
|
||||||
|
DuplicateKeystoreDir
|
||||||
|
DuplicateKeystoreFile
|
||||||
|
|
||||||
KeystoreGenerationError* = object
|
KeystoreGenerationError* = object
|
||||||
case kind*: KeystoreGenerationErrorKind
|
case kind*: KeystoreGenerationErrorKind
|
||||||
|
@ -526,7 +627,9 @@ type
|
||||||
FailedToCreateValidatorsDir,
|
FailedToCreateValidatorsDir,
|
||||||
FailedToCreateSecretsDir,
|
FailedToCreateSecretsDir,
|
||||||
FailedToCreateSecretFile,
|
FailedToCreateSecretFile,
|
||||||
FailedToCreateKeystoreFile:
|
FailedToCreateKeystoreFile,
|
||||||
|
DuplicateKeystoreDir,
|
||||||
|
DuplicateKeystoreFile:
|
||||||
error*: string
|
error*: string
|
||||||
|
|
||||||
proc mapErrTo*[T, E](r: Result[T, E], v: static KeystoreGenerationErrorKind):
|
proc mapErrTo*[T, E](r: Result[T, E], v: static KeystoreGenerationErrorKind):
|
||||||
|
@ -555,7 +658,8 @@ proc loadNetKeystore*(keyStorePath: string,
|
||||||
|
|
||||||
if insecurePwd.isSome():
|
if insecurePwd.isSome():
|
||||||
warn "Using insecure password to unlock networking key"
|
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:
|
if decrypted.isOk:
|
||||||
return some(decrypted.get())
|
return some(decrypted.get())
|
||||||
else:
|
else:
|
||||||
|
@ -607,11 +711,10 @@ proc saveNetKeystore*(rng: var BrHmacDrbgContext, keyStorePath: string,
|
||||||
key_path = keyStorePath
|
key_path = keyStorePath
|
||||||
res.mapErrTo(FailedToCreateKeystoreFile)
|
res.mapErrTo(FailedToCreateKeystoreFile)
|
||||||
|
|
||||||
proc createValidatorFiles*(
|
proc createValidatorFiles*(secretsDir, validatorsDir, keystoreDir, secretFile,
|
||||||
secretsDir, validatorsDir,
|
passwordAsString, keystoreFile,
|
||||||
keystoreDir, secretFile,
|
encodedStorage: string
|
||||||
passwordAsString, keystoreFile,
|
): Result[void, KeystoreGenerationError] =
|
||||||
encodedStorage: string): Result[void, KeystoreGenerationError] =
|
|
||||||
|
|
||||||
var
|
var
|
||||||
success = false # becomes true when everything is created successfully
|
success = false # becomes true when everything is created successfully
|
||||||
|
@ -641,17 +744,45 @@ proc createValidatorFiles*(
|
||||||
discard io2.removeDir(keystoreDir)
|
discard io2.removeDir(keystoreDir)
|
||||||
|
|
||||||
# secretFile:
|
# secretFile:
|
||||||
? secureWriteFile(secretFile, passwordAsString).mapErrTo(FailedToCreateSecretFile)
|
? secureWriteFile(secretFile,
|
||||||
|
passwordAsString).mapErrTo(FailedToCreateSecretFile)
|
||||||
defer:
|
defer:
|
||||||
if not success:
|
if not success:
|
||||||
discard io2.removeFile(secretFile)
|
discard io2.removeFile(secretFile)
|
||||||
|
|
||||||
# keystoreFile:
|
# keystoreFile:
|
||||||
? secureWriteFile(keystoreFile, encodedStorage).mapErrTo(FailedToCreateKeystoreFile)
|
? secureWriteFile(keystoreFile,
|
||||||
|
encodedStorage).mapErrTo(FailedToCreateKeystoreFile)
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
ok()
|
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,
|
proc saveKeystore*(rng: var BrHmacDrbgContext,
|
||||||
validatorsDir, secretsDir: string,
|
validatorsDir, secretsDir: string,
|
||||||
signingKey: ValidatorPrivKey,
|
signingKey: ValidatorPrivKey,
|
||||||
|
@ -663,35 +794,118 @@ proc saveKeystore*(rng: var BrHmacDrbgContext,
|
||||||
keypass = KeystorePass.init(password)
|
keypass = KeystorePass.init(password)
|
||||||
keyName = "0x" & signingPubKey.toHex()
|
keyName = "0x" & signingPubKey.toHex()
|
||||||
keystoreDir = validatorsDir / keyName
|
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
|
let keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
|
||||||
keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
|
|
||||||
keypass, signingKeyPath,
|
keypass, signingKeyPath,
|
||||||
mode = mode)
|
mode = mode)
|
||||||
keystoreFile = keystoreDir / KeystoreFileName
|
|
||||||
|
|
||||||
var encodedStorage: string
|
var encodedStorage: string
|
||||||
try:
|
try:
|
||||||
encodedStorage = Json.encode(keyStore)
|
encodedStorage = Json.encode(keyStore)
|
||||||
except SerializationError as e:
|
except SerializationError as e:
|
||||||
error "Could not serialize keystorage", key_path = keystoreFile
|
error "Could not serialize keystorage", key_path = keystoreFile
|
||||||
return err(KeystoreGenerationError(
|
return err(KeystoreGenerationError(
|
||||||
kind: FailedToCreateKeystoreFile, error: e.msg))
|
kind: FailedToCreateKeystoreFile, error: e.msg))
|
||||||
|
|
||||||
? createValidatorFiles(secretsDir, validatorsDir,
|
? createValidatorFiles(secretsDir, validatorsDir,
|
||||||
keystoreDir,
|
keystoreDir,
|
||||||
secretsDir / keyName, keypass.str,
|
secretsDir / keyName, keypass.str,
|
||||||
keystoreFile, encodedStorage)
|
keystoreFile, encodedStorage)
|
||||||
|
|
||||||
ok()
|
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,
|
proc importKeystore*(pool: var ValidatorPool,
|
||||||
rng: var BrHmacDrbgContext,
|
rng: var BrHmacDrbgContext,
|
||||||
conf: AnyConf, keystore: Keystore,
|
conf: AnyConf, keystore: Keystore,
|
||||||
password: string): KmResult[AddValidatorStatus]
|
password: string): ImportResult[KeystoreData] {.
|
||||||
{.raises: [Defect].} =
|
raises: [Defect].} =
|
||||||
let keypass = KeystorePass.init(password)
|
let keypass = KeystorePass.init(password)
|
||||||
let privateKey =
|
let privateKey =
|
||||||
block:
|
block:
|
||||||
|
@ -699,31 +913,33 @@ proc importKeystore*(pool: var ValidatorPool,
|
||||||
if res.isOk():
|
if res.isOk():
|
||||||
res.get()
|
res.get()
|
||||||
else:
|
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()
|
# We check `publicKey` in memory storage first.
|
||||||
let keyName = "0x" & publicKey.toHex()
|
if publicKey.toPubKey() in pool:
|
||||||
|
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
|
||||||
|
|
||||||
let validatorsDir = conf.validatorsDir()
|
# We check `publicKey` in filesystem.
|
||||||
let secretsDir = conf.secretsDir()
|
if existsKeystore(keystoreDir, {KeystoreKind.Local, KeystoreKind.Remote}):
|
||||||
|
return err(AddValidatorFailure.init(AddValidatorStatus.existingArtifacts))
|
||||||
|
|
||||||
let secretFile = secretsDir / keyName
|
let res = saveKeystore(rng, validatorsDir, secretsDir,
|
||||||
let keystoreDir = validatorsDir / keyName
|
privateKey, publicKey, keystore.path, password)
|
||||||
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)
|
|
||||||
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return err("Keystore Generation Error")
|
return err(AddValidatorFailure.init(AddValidatorStatus.failed,
|
||||||
|
$res.error()))
|
||||||
|
|
||||||
pool.addLocalValidator(KeystoreData.init(privateKey, keystore))
|
ok(KeystoreData.init(privateKey, keystore))
|
||||||
ok AddValidatorStatus.added
|
|
||||||
|
|
||||||
proc generateDeposits*(cfg: RuntimeConfig,
|
proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
rng: var BrHmacDrbgContext,
|
rng: var BrHmacDrbgContext,
|
||||||
|
@ -731,7 +947,8 @@ proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
firstValidatorIdx, totalNewValidators: int,
|
firstValidatorIdx, totalNewValidators: int,
|
||||||
validatorsDir: string,
|
validatorsDir: string,
|
||||||
secretsDir: string,
|
secretsDir: string,
|
||||||
mode = Secure): Result[seq[DepositData], KeystoreGenerationError] =
|
mode = Secure): Result[seq[DepositData],
|
||||||
|
KeystoreGenerationError] =
|
||||||
var deposits: seq[DepositData]
|
var deposits: seq[DepositData]
|
||||||
|
|
||||||
notice "Generating deposits", totalNewValidators, validatorsDir, secretsDir
|
notice "Generating deposits", totalNewValidators, validatorsDir, secretsDir
|
||||||
|
|
|
@ -84,15 +84,14 @@ proc findValidator(validators: auto, pubkey: ValidatorPubKey):
|
||||||
else:
|
else:
|
||||||
some(idx.ValidatorIndex)
|
some(idx.ValidatorIndex)
|
||||||
|
|
||||||
proc addLocalValidator(node: BeaconNode,
|
proc addLocalValidator(node: BeaconNode, validators: auto,
|
||||||
validators: auto,
|
|
||||||
item: KeystoreData) =
|
item: KeystoreData) =
|
||||||
let
|
let
|
||||||
pubkey = item.pubkey
|
pubkey = item.pubkey
|
||||||
index = findValidator(validators, pubkey)
|
index = findValidator(validators, pubkey)
|
||||||
node.attachedValidators[].addLocalValidator(item, index)
|
node.attachedValidators[].addLocalValidator(item, index)
|
||||||
|
|
||||||
proc addRemoteValidator(node: BeaconNode, validators: auto,
|
proc addRemoteValidator(pool: var ValidatorPool, validators: auto,
|
||||||
item: KeystoreData) =
|
item: KeystoreData) =
|
||||||
let httpFlags =
|
let httpFlags =
|
||||||
block:
|
block:
|
||||||
|
@ -108,7 +107,7 @@ proc addRemoteValidator(node: BeaconNode, validators: auto,
|
||||||
remote_url = $item.remoteUrl, validator = item.pubkey
|
remote_url = $item.remoteUrl, validator = item.pubkey
|
||||||
return
|
return
|
||||||
let index = findValidator(validators, item.pubkey)
|
let index = findValidator(validators, item.pubkey)
|
||||||
node.attachedValidators[].addRemoteValidator(item, client.get(), index)
|
pool.addRemoteValidator(item, client.get(), index)
|
||||||
|
|
||||||
proc addLocalValidators*(node: BeaconNode,
|
proc addLocalValidators*(node: BeaconNode,
|
||||||
validators: openArray[KeystoreData]) =
|
validators: openArray[KeystoreData]) =
|
||||||
|
@ -120,7 +119,8 @@ proc addRemoteValidators*(node: BeaconNode,
|
||||||
validators: openArray[KeystoreData]) =
|
validators: openArray[KeystoreData]) =
|
||||||
withState(node.dag.headState.data):
|
withState(node.dag.headState.data):
|
||||||
for item in validators:
|
for item in validators:
|
||||||
node.addRemoteValidator(state.data.validators.asSeq(), item)
|
node.attachedValidators[].addRemoteValidator(
|
||||||
|
state.data.validators.asSeq(), item)
|
||||||
|
|
||||||
proc addValidators*(node: BeaconNode) =
|
proc addValidators*(node: BeaconNode) =
|
||||||
let (localValidators, remoteValidators) =
|
let (localValidators, remoteValidators) =
|
||||||
|
|
|
@ -114,8 +114,12 @@ proc removeValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey) =
|
||||||
let validator = pool.validators.getOrDefault(pubkey)
|
let validator = pool.validators.getOrDefault(pubkey)
|
||||||
if not(isNil(validator)):
|
if not(isNil(validator)):
|
||||||
pool.validators.del(pubkey)
|
pool.validators.del(pubkey)
|
||||||
notice "Local or remote validator detached", pubkey,
|
case validator.kind
|
||||||
validator = shortLog(validator)
|
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)
|
validators.set(pool.count().int64)
|
||||||
|
|
||||||
proc updateValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey,
|
proc updateValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey,
|
||||||
|
|
|
@ -53,7 +53,8 @@ cli do(validatorsDir: string, secretsDir: string,
|
||||||
validators: Table[ValidatorIndex, ValidatorPrivKey]
|
validators: Table[ValidatorIndex, ValidatorPrivKey]
|
||||||
validatorKeys: Table[ValidatorPubKey, ValidatorPrivKey]
|
validatorKeys: Table[ValidatorPubKey, ValidatorPrivKey]
|
||||||
|
|
||||||
for item in listLoadableKeystores(validatorsDir, secretsDir, true):
|
for item in listLoadableKeystores(validatorsDir, secretsDir, true,
|
||||||
|
{KeystoreKind.Local}):
|
||||||
let
|
let
|
||||||
pubkey = item.privateKey.toPubKey().toPubKey()
|
pubkey = item.privateKey.toPubKey().toPubKey()
|
||||||
idx = findValidator(getStateField(state[], validators).toSeq, pubkey)
|
idx = findValidator(getStateField(state[], validators).toSeq, pubkey)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import
|
||||||
|
|
||||||
../beacon_chain/spec/[crypto, keystore, eth2_merkleization],
|
../beacon_chain/spec/[crypto, keystore, eth2_merkleization],
|
||||||
../beacon_chain/spec/datatypes/base,
|
../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/validators/[keystore_management, slashing_protection_common],
|
||||||
../beacon_chain/networking/network_metadata,
|
../beacon_chain/networking/network_metadata,
|
||||||
../beacon_chain/rpc/rest_key_management_api,
|
../beacon_chain/rpc/rest_key_management_api,
|
||||||
|
@ -28,6 +28,48 @@ const
|
||||||
tokenFilePath = dataDir / "keymanager-token.txt"
|
tokenFilePath = dataDir / "keymanager-token.txt"
|
||||||
keymanagerPort = 47000
|
keymanagerPort = 47000
|
||||||
correctTokenValue = "some secret token"
|
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 =
|
proc startSingleNodeNetwork =
|
||||||
let
|
let
|
||||||
|
@ -65,6 +107,13 @@ proc startSingleNodeNetwork =
|
||||||
Json.saveFile(depositsFile, launchPadDeposits)
|
Json.saveFile(depositsFile, launchPadDeposits)
|
||||||
notice "Deposit data written", filename = depositsFile
|
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)
|
let tokenFileRes = secureWriteFile(tokenFilePath, correctTokenValue)
|
||||||
if tokenFileRes.isErr:
|
if tokenFileRes.isErr:
|
||||||
fatal "Failed to create token file", err = deposits.error
|
fatal "Failed to create token file", err = deposits.error
|
||||||
|
@ -125,6 +174,40 @@ const
|
||||||
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
||||||
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
|
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.} =
|
proc runTests {.async.} =
|
||||||
while bnStatus != BeaconNodeStatus.Running:
|
while bnStatus != BeaconNodeStatus.Running:
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
|
@ -136,6 +219,8 @@ proc runTests {.async.} =
|
||||||
rng = keys.newRng()
|
rng = keys.newRng()
|
||||||
privateKey = ValidatorPrivKey.fromRaw(secretBytes).get
|
privateKey = ValidatorPrivKey.fromRaw(secretBytes).get
|
||||||
|
|
||||||
|
localList = listLocalValidators(validatorsDir, secretsDir)
|
||||||
|
|
||||||
newKeystore = createKeystore(
|
newKeystore = createKeystore(
|
||||||
kdfPbkdf2, rng[], privateKey,
|
kdfPbkdf2, rng[], privateKey,
|
||||||
KeystorePass.init password,
|
KeystorePass.init password,
|
||||||
|
@ -143,18 +228,102 @@ proc runTests {.async.} =
|
||||||
description = "This is a test keystore that uses PBKDF2 to secure the secret",
|
description = "This is a test keystore that uses PBKDF2 to secure the secret",
|
||||||
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
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(
|
importKeystoresBody = KeystoresAndSlashingProtection(
|
||||||
keystores: @[newKeystore],
|
keystores: @[newKeystore],
|
||||||
passwords: @[password],
|
passwords: @[password],
|
||||||
slashing_protection: SPDIR())
|
)
|
||||||
|
|
||||||
deleteKeysBody = DeleteKeystoresBody(
|
deleteKeysBody = DeleteKeystoresBody(
|
||||||
pubkeys: @[privateKey.toPubKey.toPubKey])
|
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():
|
suite "ListKeys requests" & preset():
|
||||||
asyncTest "Correct token provided" & preset():
|
asyncTest "Correct token provided" & preset():
|
||||||
let
|
let
|
||||||
filesystemKeystores = sorted(listValidators(validatorsDir, secretsDir))
|
filesystemKeystores = sorted(
|
||||||
|
listLocalValidators(validatorsDir, secretsDir))
|
||||||
apiKeystores = sorted((await client.listKeys(correctTokenValue)).data)
|
apiKeystores = sorted((await client.listKeys(correctTokenValue)).data)
|
||||||
|
|
||||||
check filesystemKeystores == apiKeystores
|
check filesystemKeystores == apiKeystores
|
||||||
|
@ -198,6 +367,75 @@ proc runTests {.async.} =
|
||||||
let keystores = await client.listKeys("Invalid Token")
|
let keystores = await client.listKeys("Invalid Token")
|
||||||
|
|
||||||
suite "ImportKeystores requests" & preset():
|
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():
|
asyncTest "Missing Authorization header" & preset():
|
||||||
let
|
let
|
||||||
response = await client.importKeystoresPlain(importKeystoresBody)
|
response = await client.importKeystoresPlain(importKeystoresBody)
|
||||||
|
@ -285,6 +523,245 @@ proc runTests {.async.} =
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
responseJson["message"].getStr() == InvalidAuthorization
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
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
|
bnStatus = BeaconNodeStatus.Stopping
|
||||||
|
|
||||||
proc main() {.async.} =
|
proc main() {.async.} =
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[os, options, json, typetraits],
|
std/[os, options, json, typetraits, uri, algorithm],
|
||||||
unittest2, chronos, chronicles, stint, json_serialization,
|
unittest2, chronos, chronicles, stint, json_serialization,
|
||||||
blscurve, eth/keys, nimcrypto/utils,
|
blscurve, eth/keys, nimcrypto/utils,
|
||||||
libp2p/crypto/crypto as lcrypto,
|
libp2p/crypto/crypto as lcrypto,
|
||||||
|
@ -24,9 +24,6 @@ proc directoryItemsCount(dir: string): int {.raises: [OSError].} =
|
||||||
for el in walkDir(dir):
|
for el in walkDir(dir):
|
||||||
result += 1
|
result += 1
|
||||||
|
|
||||||
proc isEmptyDir(dir: string): bool =
|
|
||||||
directoryItemsCount(dir) == 0
|
|
||||||
|
|
||||||
proc validatorPubKeysInDir(dir: string): seq[string] =
|
proc validatorPubKeysInDir(dir: string): seq[string] =
|
||||||
for kind, file in walkDir(dir):
|
for kind, file in walkDir(dir):
|
||||||
if kind == pcDir:
|
if kind == pcDir:
|
||||||
|
@ -47,6 +44,13 @@ let
|
||||||
cfg = defaultRuntimeConfig
|
cfg = defaultRuntimeConfig
|
||||||
validatorDirRes = secureCreatePath(testValidatorsDir)
|
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():
|
if validatorDirRes.isErr():
|
||||||
warn "Could not create validators folder",
|
warn "Could not create validators folder",
|
||||||
path = testValidatorsDir, err = ioErrorMsg(validatorDirRes.error)
|
path = testValidatorsDir, err = ioErrorMsg(validatorDirRes.error)
|
||||||
|
@ -70,14 +74,59 @@ if deposits.isErr:
|
||||||
|
|
||||||
let validatorPubKeys = validatorPubKeysInDir(testValidatorsDir)
|
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":
|
test "Remove validator files":
|
||||||
let
|
let
|
||||||
validatorsCountBefore = directoryItemsCount(testValidatorsDir)
|
validatorsCountBefore = directoryItemsCount(testValidatorsDir)
|
||||||
secretsCountBefore = directoryItemsCount(testSecretsDir)
|
secretsCountBefore = directoryItemsCount(testSecretsDir)
|
||||||
firstValidator = validatorPubKeys[0]
|
firstValidator = validatorPubKeys[0]
|
||||||
removeValidatorFilesRes = removeValidatorFiles(
|
removeValidatorFilesRes = removeValidatorFiles(
|
||||||
testValidatorsDir, testSecretsDir, firstValidator)
|
testValidatorsDir, testSecretsDir, firstValidator, KeystoreKind.Local)
|
||||||
validatorsCountAfter = directoryItemsCount(testValidatorsDir)
|
validatorsCountAfter = directoryItemsCount(testValidatorsDir)
|
||||||
secretsCountAfter = directoryItemsCount(testSecretsDir)
|
secretsCountAfter = directoryItemsCount(testSecretsDir)
|
||||||
|
|
||||||
|
@ -94,31 +143,263 @@ suite "removeValidatorFiles":
|
||||||
let
|
let
|
||||||
nonexistentValidator =
|
nonexistentValidator =
|
||||||
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
"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":
|
test "Remove validator files twice":
|
||||||
let
|
let
|
||||||
secondValidator = validatorPubKeys[1]
|
secondValidator = validatorPubKeys[1]
|
||||||
res1 = removeValidatorFiles(testValidatorsDir, testSecretsDir, secondValidator)
|
res1 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
|
||||||
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir, secondValidator)
|
secondValidator, KeystoreKind.Local)
|
||||||
|
res2 = removeValidatorFiles(testValidatorsDir, testSecretsDir,
|
||||||
|
secondValidator, KeystoreKind.Local)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
not fileExists(testValidatorsDir / secondValidator)
|
not fileExists(testValidatorsDir / secondValidator)
|
||||||
not fileExists(testSecretsDir / secondValidator)
|
not fileExists(testSecretsDir / secondValidator)
|
||||||
res1.isOk and res1.value() == RemoveValidatorStatus.deleted
|
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 testValidatorsDir
|
||||||
os.removeDir testSecretsDir
|
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:
|
setup:
|
||||||
const
|
const
|
||||||
password = string.fromBytes hexToSeqByte("7465737470617373776f7264f09f9491")
|
password = string.fromBytes hexToSeqByte("7465737470617373776f7264f09f9491")
|
||||||
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||||
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
|
|
||||||
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
||||||
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
||||||
|
|
||||||
|
@ -131,18 +412,19 @@ suite "createValidatorFiles":
|
||||||
salt=salt, iv=iv,
|
salt=salt, iv=iv,
|
||||||
description = "This is a test keystore that uses PBKDF2 to secure the secret.",
|
description = "This is a test keystore that uses PBKDF2 to secure the secret.",
|
||||||
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
||||||
keystoreJsonContents = Json.encode(keystore)
|
keystoreJsonContents {.used.} = Json.encode(keystore)
|
||||||
|
|
||||||
hexEncodedPubkey = "0x" & keystore.pubkey.toHex()
|
hexEncodedPubkey = "0x" & keystore.pubkey.toHex()
|
||||||
keystoreDir = testValidatorsDir / hexEncodedPubkey
|
keystoreDir {.used.} = testValidatorsDir / hexEncodedPubkey
|
||||||
secretFile = testSecretsDir / hexEncodedPubkey
|
secretFile {.used.} = testSecretsDir / hexEncodedPubkey
|
||||||
keystoreFile = testValidatorsDir / hexEncodedPubkey / KeystoreFileName
|
keystoreFile {.used.} = testValidatorsDir / hexEncodedPubkey /
|
||||||
|
KeystoreFileName
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
os.removeDir testValidatorsDir
|
os.removeDir testValidatorsDir
|
||||||
os.removeDir testSecretsDir
|
os.removeDir testSecretsDir
|
||||||
|
|
||||||
test "Add keystore files":
|
test "Add keystore files [LOCAL]":
|
||||||
let
|
let
|
||||||
res = createValidatorFiles(testSecretsDir, testValidatorsDir,
|
res = createValidatorFiles(testSecretsDir, testValidatorsDir,
|
||||||
keystoreDir,
|
keystoreDir,
|
||||||
|
@ -166,7 +448,9 @@ suite "createValidatorFiles":
|
||||||
secretFile.contentEquals password
|
secretFile.contentEquals password
|
||||||
keystoreFile.contentEquals keystoreJsonContents
|
keystoreFile.contentEquals keystoreJsonContents
|
||||||
|
|
||||||
test "Add keystore files twice":
|
namesEqual(validatorPubKeys, [hexEncodedPubkey])
|
||||||
|
|
||||||
|
test "Add keystore files twice [LOCAL]":
|
||||||
let
|
let
|
||||||
res1 = createValidatorFiles(testSecretsDir, testValidatorsDir,
|
res1 = createValidatorFiles(testSecretsDir, testValidatorsDir,
|
||||||
keystoreDir,
|
keystoreDir,
|
||||||
|
@ -194,6 +478,76 @@ suite "createValidatorFiles":
|
||||||
secretFile.contentEquals password
|
secretFile.contentEquals password
|
||||||
keystoreFile.contentEquals keystoreJsonContents
|
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
|
# TODO The following tests are disabled on Windows because the io2 module
|
||||||
# doesn't implement the permission/mode parameter at the moment:
|
# doesn't implement the permission/mode parameter at the moment:
|
||||||
when not defined(windows):
|
when not defined(windows):
|
||||||
|
@ -298,3 +652,263 @@ suite "createValidatorFiles":
|
||||||
secretsCountBefore == secretsCountAfter
|
secretsCountBefore == secretsCountAfter
|
||||||
|
|
||||||
os.removeDir testDataDir
|
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