mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-26 13:15:16 +00:00
In Jenkins CI we run two instances of unit tests concurrently. This can trigger CI failure when the same port numbers are re-used by the different test instances. Fixed one more issue of this by allowing user configuration of the base port number.
1326 lines
55 KiB
Nim
1326 lines
55 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2021-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.
|
|
|
|
{.used.}
|
|
|
|
import
|
|
std/[typetraits, os, options, json, sequtils, uri, algorithm],
|
|
testutils/unittests, chronicles, stint, json_serialization, confutils,
|
|
chronos, eth/keys, blscurve, libp2p/crypto/crypto as lcrypto,
|
|
stew/[byteutils, io2], stew/shims/net,
|
|
|
|
../beacon_chain/spec/[crypto, keystore, eth2_merkleization],
|
|
../beacon_chain/spec/datatypes/base,
|
|
../beacon_chain/spec/eth2_apis/[rest_keymanager_calls, rest_keymanager_types],
|
|
../beacon_chain/validators/[keystore_management, slashing_protection_common],
|
|
../beacon_chain/networking/network_metadata,
|
|
../beacon_chain/rpc/rest_key_management_api,
|
|
../beacon_chain/[conf, filepath, beacon_node,
|
|
nimbus_beacon_node, beacon_node_status,
|
|
nimbus_validator_client],
|
|
../beacon_chain/validator_client/common,
|
|
|
|
./testutil
|
|
|
|
type
|
|
KeymanagerToTest = object
|
|
ident: string
|
|
port: int
|
|
validatorsDir: string
|
|
secretsDir: string
|
|
|
|
# Individual port numbers derived by adding `ord` to configurable base port
|
|
PortKind {.pure.} = enum
|
|
PeerToPeer,
|
|
Metrics,
|
|
KeymanagerBN,
|
|
KeymanagerVC
|
|
|
|
const
|
|
simulationDepositsCount = 128
|
|
dataDir = "./test_keymanager_api"
|
|
validatorsDir = dataDir / "validators"
|
|
secretsDir = dataDir / "secrets"
|
|
depositsFile = dataDir / "deposits.json"
|
|
genesisFile = dataDir / "genesis.ssz"
|
|
bootstrapEnrFile = dataDir / "bootstrap_node.enr"
|
|
tokenFilePath = dataDir / "keymanager-token.txt"
|
|
defaultBasePort = 49000
|
|
correctTokenValue = "some secret token"
|
|
defaultFeeRecipient = Eth1Address.fromHex("0x000000000000000000000000000000000000DEAD")
|
|
|
|
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"
|
|
]
|
|
|
|
unusedPublicKeys = [
|
|
"0xc22f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7232d",
|
|
"0x0bbca63e35c7a159fc2f187d300cad9ef5f5e73e55f78c391e7bc2c2feabc2d9d63dfe99edd7058ad0ab9d7f14aade5f"
|
|
]
|
|
|
|
newPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/remote"))
|
|
|
|
nodeDataDir = dataDir / "node-0"
|
|
nodeValidatorsDir = nodeDataDir / "validators"
|
|
nodeSecretsDir = nodeDataDir / "secrets"
|
|
|
|
vcDataDir = dataDir / "validator-0"
|
|
vcValidatorsDir = vcDataDir / "validators"
|
|
vcSecretsDir = vcDataDir / "secrets"
|
|
|
|
func specifiedFeeRecipient(x: int): Eth1Address =
|
|
copyMem(addr result, unsafeAddr x, sizeof x)
|
|
|
|
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 prepareNetwork =
|
|
let
|
|
rng = keys.newRng()
|
|
mnemonic = generateMnemonic(rng[])
|
|
seed = getSeed(mnemonic, KeystorePass.init "")
|
|
cfg = defaultRuntimeConfig
|
|
|
|
let vres = secureCreatePath(validatorsDir)
|
|
if vres.isErr():
|
|
warn "Could not create validators folder",
|
|
path = validatorsDir, err = ioErrorMsg(vres.error)
|
|
|
|
let sres = secureCreatePath(secretsDir)
|
|
if sres.isErr():
|
|
warn "Could not create secrets folder",
|
|
path = secretsDir, err = ioErrorMsg(sres.error)
|
|
|
|
let deposits = generateDeposits(
|
|
cfg,
|
|
rng[],
|
|
seed,
|
|
0, simulationDepositsCount,
|
|
validatorsDir,
|
|
secretsDir,
|
|
@[],
|
|
0,
|
|
0,
|
|
KeystoreMode.Fast)
|
|
|
|
if deposits.isErr:
|
|
fatal "Failed to generate deposits", err = deposits.error
|
|
quit 1
|
|
|
|
let launchPadDeposits =
|
|
mapIt(deposits.value, LaunchPadDeposit.init(cfg, it))
|
|
|
|
Json.saveFile(depositsFile, launchPadDeposits)
|
|
notice "Deposit data written", filename = depositsFile
|
|
|
|
let createTestnetConf = try: BeaconNodeConf.load(cmdLine = mapIt([
|
|
"--data-dir=" & dataDir,
|
|
"createTestnet",
|
|
"--total-validators=" & $simulationDepositsCount,
|
|
"--deposits-file=" & depositsFile,
|
|
"--output-genesis=" & genesisFile,
|
|
"--output-bootstrap-file=" & bootstrapEnrFile,
|
|
"--netkey-file=network_key.json",
|
|
"--insecure-netkey-password=true",
|
|
"--genesis-offset=0"], it))
|
|
except Exception as exc: # TODO Fix confutils exceptions
|
|
raiseAssert exc.msg
|
|
|
|
doCreateTestnet(createTestnetConf, rng[])
|
|
|
|
let tokenFileRes = secureWriteFile(tokenFilePath, correctTokenValue)
|
|
if tokenFileRes.isErr:
|
|
fatal "Failed to create token file", err = deposits.error
|
|
quit 1
|
|
|
|
proc copyHalfValidators(dstDataDir: string, firstHalf: bool) =
|
|
let dstValidatorsDir = dstDataDir / "validators"
|
|
|
|
block:
|
|
let status = secureCreatePath(dstValidatorsDir)
|
|
if status.isErr():
|
|
fatal "Could not create node validators folder",
|
|
path = dstValidatorsDir, err = ioErrorMsg(status.error)
|
|
quit 1
|
|
|
|
let dstSecretsDir = dstDataDir / "secrets"
|
|
|
|
block:
|
|
let status = secureCreatePath(dstSecretsDir)
|
|
if status.isErr():
|
|
fatal "Could not create node secrets folder",
|
|
path = dstSecretsDir, err = ioErrorMsg(status.error)
|
|
quit 1
|
|
|
|
var validatorIdx = 0
|
|
for validator in walkDir(validatorsDir):
|
|
if (validatorIdx < simulationDepositsCount div 2) == firstHalf:
|
|
let
|
|
currValidator = os.splitPath(validator.path).tail
|
|
secretFile = secretsDir / currValidator
|
|
secretRes = readAllChars(secretFile)
|
|
|
|
if secretRes.isErr:
|
|
fatal "Failed to read secret file",
|
|
path = secretFile, err = $secretRes.error
|
|
quit 1
|
|
|
|
let
|
|
dstSecretFile = dstSecretsDir / currValidator
|
|
secretFileStatus = secureWriteFile(dstSecretFile, secretRes.get)
|
|
|
|
if secretFileStatus.isErr:
|
|
fatal "Failed to write secret file",
|
|
path = dstSecretFile, err = $secretFileStatus.error
|
|
quit 1
|
|
|
|
let
|
|
dstValidatorDir = dstDataDir / "validators" / currValidator
|
|
validatorDirRes = secureCreatePath(dstValidatorDir)
|
|
|
|
if validatorDirRes.isErr:
|
|
fatal "Failed to create validator dir",
|
|
path = dstValidatorDir, err = $validatorDirRes.error
|
|
quit 1
|
|
|
|
let
|
|
keystoreFile = validatorsDir / currValidator / "keystore.json"
|
|
readKeystoreRes = readAllChars(keystoreFile)
|
|
|
|
if readKeystoreRes.isErr:
|
|
fatal "Failed to read keystore file",
|
|
path = keystoreFile, err = $readKeystoreRes.error
|
|
quit 1
|
|
|
|
let
|
|
dstKeystore = dstValidatorDir / "keystore.json"
|
|
writeKeystoreRes = secureWriteFile(dstKeystore, readKeystoreRes.get)
|
|
|
|
if writeKeystoreRes.isErr:
|
|
fatal "Failed to write keystore file",
|
|
path = dstKeystore, err = $writeKeystoreRes.error
|
|
quit 1
|
|
|
|
inc validatorIdx
|
|
|
|
proc addPreTestRemoteKeystores(validatorsDir: string) =
|
|
for item in oldPublicKeys:
|
|
let key = ValidatorPubKey.fromHex(item).tryGet()
|
|
let res = saveKeystore(validatorsDir, key, oldPublicKeysUrl)
|
|
if res.isErr():
|
|
fatal "Failed to create remote keystore file",
|
|
validatorsDir = nodeValidatorsDir, key,
|
|
err = res.error
|
|
quit 1
|
|
|
|
proc startBeaconNode(basePort: int) {.raises: [Defect, CatchableError].} =
|
|
let rng = keys.newRng()
|
|
|
|
copyHalfValidators(nodeDataDir, true)
|
|
addPreTestRemoteKeystores(nodeValidatorsDir)
|
|
|
|
let runNodeConf = try: BeaconNodeConf.load(cmdLine = mapIt([
|
|
"--tcp-port=" & $(basePort + PortKind.PeerToPeer.ord),
|
|
"--udp-port=" & $(basePort + PortKind.PeerToPeer.ord),
|
|
"--discv5=off",
|
|
"--network=" & dataDir,
|
|
"--data-dir=" & nodeDataDir,
|
|
"--validators-dir=" & nodeValidatorsDir,
|
|
"--secrets-dir=" & nodeSecretsDir,
|
|
"--metrics-address=127.0.0.1",
|
|
"--metrics-port=" & $(basePort + PortKind.Metrics.ord),
|
|
"--rest=true",
|
|
"--rest-address=127.0.0.1",
|
|
"--rest-port=" & $(basePort + PortKind.KeymanagerBN.ord),
|
|
"--keymanager=true",
|
|
"--keymanager-address=127.0.0.1",
|
|
"--keymanager-port=" & $(basePort + PortKind.KeymanagerBN.ord),
|
|
"--keymanager-token-file=" & tokenFilePath,
|
|
"--suggested-fee-recipient=" & $defaultFeeRecipient,
|
|
"--doppelganger-detection=off"], it))
|
|
except Exception as exc: # TODO fix confutils exceptions
|
|
raiseAssert exc.msg
|
|
|
|
let metadata = loadEth2NetworkMetadata(dataDir)
|
|
|
|
let node = BeaconNode.init(
|
|
metadata.cfg,
|
|
rng,
|
|
runNodeConf,
|
|
metadata.depositContractDeployedAt,
|
|
metadata.eth1Network,
|
|
metadata.genesisData,
|
|
metadata.genesisDepositsSnapshot
|
|
)
|
|
|
|
node.start() # This will run until the node is terminated by
|
|
# setting its `bnStatus` to `Stopping`.
|
|
|
|
# os.removeDir dataDir
|
|
|
|
proc startValidatorClient(basePort: int) {.async, thread.} =
|
|
let rng = keys.newRng()
|
|
|
|
copyHalfValidators(vcDataDir, false)
|
|
addPreTestRemoteKeystores(vcValidatorsDir)
|
|
|
|
let runValidatorClientConf = try: ValidatorClientConf.load(cmdLine = mapIt([
|
|
"--beacon-node=http://127.0.0.1:" & $(basePort + PortKind.KeymanagerBN.ord),
|
|
"--data-dir=" & vcDataDir,
|
|
"--validators-dir=" & vcValidatorsDir,
|
|
"--secrets-dir=" & vcSecretsDir,
|
|
"--suggested-fee-recipient=" & $defaultFeeRecipient,
|
|
"--keymanager=true",
|
|
"--keymanager-address=127.0.0.1",
|
|
"--keymanager-port=" & $(basePort + PortKind.KeymanagerVC.ord),
|
|
"--keymanager-token-file=" & tokenFilePath], it))
|
|
except:
|
|
quit 1
|
|
|
|
await runValidatorClient(runValidatorClientConf, rng)
|
|
|
|
const
|
|
password = "7465737470617373776f7264f09f9491"
|
|
# This is taken from the offical test vectors in test_keystores.nim
|
|
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
|
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
|
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
|
|
secretNetBytes = hexToSeqByte "08021220fe442379443d6e2d7d75d3a58f96fbb35f0a9c7217796825fc9040e3b89c5736"
|
|
|
|
proc listLocalValidators(validatorsDir,
|
|
secretsDir: string): seq[ValidatorPubKey] {.
|
|
raises: [Defect].} =
|
|
var validators: seq[ValidatorPubKey]
|
|
try:
|
|
for el in listLoadableKeys(validatorsDir, secretsDir,
|
|
{KeystoreKind.Local}):
|
|
validators.add el.toPubKey()
|
|
except OSError as err:
|
|
error "Failure to list the validator directories",
|
|
validatorsDir, secretsDir, err = err.msg
|
|
validators
|
|
|
|
proc listRemoteValidators(validatorsDir,
|
|
secretsDir: string): seq[ValidatorPubKey] {.
|
|
raises: [Defect].} =
|
|
var validators: seq[ValidatorPubKey]
|
|
try:
|
|
for el in listLoadableKeys(validatorsDir, secretsDir,
|
|
{KeystoreKind.Remote}):
|
|
validators.add el.toPubKey()
|
|
except OSError as err:
|
|
error "Failure to list the validator directories",
|
|
validatorsDir, secretsDir, err = err.msg
|
|
validators
|
|
|
|
proc `==`(a: seq[ValidatorPubKey],
|
|
b: seq[KeystoreInfo | RemoteKeystoreInfo]): bool =
|
|
if len(a) != len(b):
|
|
return false
|
|
var indices: seq[int]
|
|
for publicKey in a:
|
|
let index =
|
|
block:
|
|
var res = -1
|
|
for k, v in b.pairs():
|
|
let key =
|
|
when b is seq[KeystoreInfo]:
|
|
v.validating_pubkey
|
|
else:
|
|
v.pubkey
|
|
if key == publicKey:
|
|
res = k
|
|
break
|
|
res
|
|
if (index == -1) or (index in indices):
|
|
return false
|
|
indices.add(index)
|
|
true
|
|
|
|
proc runTests(keymanager: KeymanagerToTest) {.async.} =
|
|
let
|
|
client = RestClientRef.new(initTAddress("127.0.0.1", keymanager.port))
|
|
rng = keys.newRng()
|
|
privateKey = ValidatorPrivKey.fromRaw(secretBytes).get
|
|
|
|
allValidators = listLocalValidators(
|
|
keymanager.validatorsDir, keymanager.secretsDir)
|
|
|
|
let
|
|
newKeystore = createKeystore(
|
|
kdfPbkdf2, rng[], privateKey,
|
|
KeystorePass.init password,
|
|
salt = salt, iv = iv,
|
|
description = "This is a test keystore that uses PBKDF2 to secure the secret",
|
|
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
|
|
|
importKeystoresBody1 =
|
|
block:
|
|
var
|
|
res1: seq[Keystore]
|
|
res2: seq[string]
|
|
for key in newPrivateKeys:
|
|
let privateKey = ValidatorPrivKey.fromHex(key).tryGet()
|
|
let store = createKeystore(kdfPbkdf2, rng[], privateKey,
|
|
KeystorePass.init password, salt = salt, iv = iv,
|
|
description = "Test keystore",
|
|
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
|
res1.add(store)
|
|
res2.add(password)
|
|
KeystoresAndSlashingProtection(
|
|
keystores: res1,
|
|
passwords: res2,
|
|
)
|
|
|
|
deleteKeysBody1 =
|
|
block:
|
|
var res: seq[ValidatorPubKey]
|
|
for item in newPrivateKeys:
|
|
let privateKey = ValidatorPrivKey.fromHex(item).tryGet()
|
|
let publicKey = privateKey.toPubKey().toPubKey()
|
|
res.add(publicKey)
|
|
DeleteKeystoresBody(
|
|
pubkeys: res
|
|
)
|
|
|
|
importKeystoresBody = KeystoresAndSlashingProtection(
|
|
keystores: @[newKeystore],
|
|
passwords: @[password],
|
|
)
|
|
|
|
deleteKeysBody = DeleteKeystoresBody(
|
|
pubkeys: @[privateKey.toPubKey.toPubKey])
|
|
|
|
importRemoteKeystoresBody =
|
|
block:
|
|
var res: seq[RemoteKeystoreInfo]
|
|
# Adding keys which are already present in filesystem
|
|
for item in oldPublicKeys:
|
|
let key = ValidatorPubKey.fromHex(item).tryGet()
|
|
res.add(RemoteKeystoreInfo(pubkey: key, url: newPublicKeysUrl))
|
|
# Adding keys which are new
|
|
for item in newPublicKeys:
|
|
let key = ValidatorPubKey.fromHex(item).tryGet()
|
|
res.add(RemoteKeystoreInfo(pubkey: key, url: newPublicKeysUrl))
|
|
# Adding non-remote keys which are already present in filesystem
|
|
res.add(RemoteKeystoreInfo(pubkey: allValidators[0],
|
|
url: newPublicKeysUrl))
|
|
res.add(RemoteKeystoreInfo(pubkey: allValidators[1],
|
|
url: newPublicKeysUrl))
|
|
ImportRemoteKeystoresBody(remote_keys: res)
|
|
|
|
template expectedImportStatus(i: int): string =
|
|
if i < 8:
|
|
"duplicate"
|
|
elif i == 16 or i == 17:
|
|
"duplicate"
|
|
else:
|
|
"imported"
|
|
|
|
let
|
|
deleteRemoteKeystoresBody1 =
|
|
block:
|
|
var res: seq[ValidatorPubKey]
|
|
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(),
|
|
allValidators[0],
|
|
allValidators[1]
|
|
]
|
|
)
|
|
|
|
testFlavour = " [" & keymanager.ident & "]" & preset()
|
|
|
|
suite "Serialization/deserialization" & testFlavour:
|
|
proc `==`(a, b: Kdf): bool =
|
|
if (a.function != b.function) or (a.message != b.message):
|
|
return false
|
|
case a.function
|
|
of KdfKind.kdfPbkdf2:
|
|
(a.pbkdf2Params.dklen == b.pbkdf2Params.dklen) and
|
|
(a.pbkdf2Params.c == b.pbkdf2Params.c) and
|
|
(a.pbkdf2Params.prf == b.pbkdf2Params.prf) and
|
|
(seq[byte](a.pbkdf2Params.salt) == seq[byte](b.pbkdf2Params.salt))
|
|
of KdfKind.kdfScrypt:
|
|
(a.scryptParams.dklen == b.scryptParams.dklen) and
|
|
(a.scryptParams.n == b.scryptParams.n) and
|
|
(a.scryptParams.p == b.scryptParams.p) and
|
|
(a.scryptParams.r == b.scryptParams.r) and
|
|
(seq[byte](a.scryptParams.salt) == seq[byte](b.scryptParams.salt))
|
|
|
|
proc `==`(a, b: Checksum): bool =
|
|
if a.function != b.function:
|
|
return false
|
|
case a.function
|
|
of ChecksumFunctionKind.sha256Checksum:
|
|
a.message.data == b.message.data
|
|
|
|
proc `==`(a, b: Cipher): bool =
|
|
if (a.function != b.function) or
|
|
(seq[byte](a.message) != seq[byte](b.message)):
|
|
return false
|
|
case a.function
|
|
of CipherFunctionKind.aes128CtrCipher:
|
|
seq[byte](a.params.iv) == seq[byte](b.params.iv)
|
|
|
|
proc `==`(a, b: Crypto): bool =
|
|
(a.kdf == b.kdf) and (a.checksum == b.checksum) and
|
|
(a.cipher == b.cipher)
|
|
|
|
proc `==`(a, b: Keystore): bool =
|
|
(a.crypto == b.crypto) and (a.pubkey == b.pubkey) and
|
|
(string(a.path) == string(b.path)) and
|
|
(a.description == b.description) and (a.uuid == b.uuid) and
|
|
(a.version == b.version)
|
|
|
|
test "Deserialization test vectors":
|
|
let
|
|
kdf1 = Kdf(
|
|
function: KdfKind.kdfPbkdf2,
|
|
pbkdf2Params: Pbkdf2Params(
|
|
dklen: 32'u64,
|
|
c: 262144'u64,
|
|
prf: PrfKind.HmacSha256,
|
|
salt: Pbkdf2Salt(hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"))
|
|
),
|
|
message: ""
|
|
)
|
|
kdf2 = Kdf(
|
|
function: KdfKind.kdfScrypt,
|
|
scryptParams: ScryptParams(
|
|
dklen: 32'u64, n: 262144, p: 1, r: 8,
|
|
salt: ScryptSalt(hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"))),
|
|
message: ""
|
|
)
|
|
checksum1 = Checksum(
|
|
function: ChecksumFunctionKind.sha256Checksum,
|
|
params: Sha256Params(),
|
|
message: Sha256Digest(MDigest[256].fromHex("0x88c0059314a3db1b2e86d4b0d37ac7ade7c6e56e3d3e34af298254f35c8b501e"))
|
|
)
|
|
checksum2 = Checksum(
|
|
function: ChecksumFunctionKind.sha256Checksum,
|
|
params: Sha256Params(),
|
|
message: Sha256Digest(MDigest[256].fromHex("0xadb59d10d2436c12f2fe229f27ec598739da92686485e9fed5255d3ed9bb1c1f"))
|
|
)
|
|
checksum3 = Checksum(
|
|
function: ChecksumFunctionKind.sha256Checksum,
|
|
params: Sha256Params(),
|
|
message: Sha256Digest(MDigest[256].fromHex("0xea4d7f495ac74bbf431ef340f15ee1aea75811bd1bab8dd64b3c2dfc041d5d90"))
|
|
)
|
|
checksum4 = Checksum(
|
|
function: ChecksumFunctionKind.sha256Checksum,
|
|
params: Sha256Params(),
|
|
message: Sha256Digest(MDigest[256].fromHex("0x71ed99dab563f1e9f1190b0de9d92d3266df2223036e7dc3ca9d9599478fe5a4"))
|
|
)
|
|
cipher1 = Cipher(
|
|
function: CipherFunctionKind.aes128CtrCipher,
|
|
params: Aes128CtrParams(iv: Aes128CtrIv(hexToSeqByte("264daa3f303d7259501c93d997d84fe6"))),
|
|
message: CipherBytes(hexToSeqByte("c071f12ec97eb449422de643e737924e02eec266f3b56cde476eae4fad5c6e64"))
|
|
)
|
|
cipher2 = Cipher(
|
|
function: CipherFunctionKind.aes128CtrCipher,
|
|
params: Aes128CtrParams(iv: Aes128CtrIv(hexToSeqByte("264daa3f303d7259501c93d997d84fe6"))),
|
|
message: CipherBytes(hexToSeqByte("8d192da5a06c001eca9c954812ce165d007c889d7711b12faa7a9d6f4d5cc6ae"))
|
|
)
|
|
cipher3 = Cipher(
|
|
function: CipherFunctionKind.aes128CtrCipher,
|
|
params: Aes128CtrParams(iv: Aes128CtrIv(hexToSeqByte("264daa3f303d7259501c93d997d84fe6"))),
|
|
message: CipherBytes(hexToSeqByte("c40a44096120e406a011ec5a22d7cbb24126436c471e21b10f078c722c6d0c3f"))
|
|
)
|
|
cipher4 = Cipher(
|
|
function: CipherFunctionKind.aes128CtrCipher,
|
|
params: Aes128CtrParams(iv: Aes128CtrIv(hexToSeqByte("264daa3f303d7259501c93d997d84fe6"))),
|
|
message: CipherBytes(hexToSeqByte("896298820832505128a09f51d72e4fa143b40997c3bafc40e213bf52cc6da4f5"))
|
|
)
|
|
keystore1 = Keystore(
|
|
crypto: Crypto(kdf: kdf1, checksum: checksum1, cipher: cipher1),
|
|
pubkey: ValidatorPubKey.fromHex("0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798").get(),
|
|
path: KeyPath("m/12381/60/0/0"),
|
|
description: some "Test keystore",
|
|
uuid: "a3331c0c-a013-4754-a122-9988b3381fec",
|
|
version: 4
|
|
)
|
|
keystore2 = Keystore(
|
|
crypto: Crypto(kdf: kdf1, checksum: checksum2, cipher: cipher2),
|
|
pubkey: ValidatorPubKey.fromHex("0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228").get(),
|
|
path: KeyPath("m/12381/60/0/0"),
|
|
description: some "Test keystore",
|
|
uuid: "905dd873-48af-416a-8c80-4283d5af84f9",
|
|
version: 4
|
|
)
|
|
keystore3 = Keystore(
|
|
crypto: Crypto(kdf: kdf2, checksum: checksum3, cipher: cipher3),
|
|
pubkey: ValidatorPubKey.fromHex("0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798").get(),
|
|
path: KeyPath("m/12381/60/0/0"),
|
|
description: some "Test keystore",
|
|
uuid: "ad1bf334-faaa-4257-8e28-81a45722e87b",
|
|
version: 4
|
|
)
|
|
keystore4 = Keystore(
|
|
crypto: Crypto(kdf: kdf2, checksum: checksum4, cipher: cipher4),
|
|
pubkey: ValidatorPubKey.fromHex("0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228").get(),
|
|
path: KeyPath("m/12381/60/0/0"),
|
|
description: some "Test keystore",
|
|
uuid: "d91bcde8-8bf5-45c6-b04d-c10d99ae9b6b",
|
|
version: 4
|
|
)
|
|
|
|
const
|
|
Vector1 = r"{""keystores"":[""{\""crypto\"":{\""kdf\"":{\""function\"":\""pbkdf2\"",\""params\"":{\""dklen\"":32,\""c\"":262144,\""prf\"":\""hmac-sha256\"",\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0x88c0059314a3db1b2e86d4b0d37ac7ade7c6e56e3d3e34af298254f35c8b501e\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c071f12ec97eb449422de643e737924e02eec266f3b56cde476eae4fad5c6e64\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""a3331c0c-a013-4754-a122-9988b3381fec\"",\""name\"":\""named-a3331c0c-a013-4754-a122-9988b3381fec\"",\""version\"":4}""],""passwords"":[""7465737470617373776f7264f09f9491""]}"
|
|
Vector2 = r"{""keystores"":[""{\""crypto\"":{\""kdf\"":{\""function\"":\""pbkdf2\"",\""params\"":{\""dklen\"":32,\""c\"":262144,\""prf\"":\""hmac-sha256\"",\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0x88c0059314a3db1b2e86d4b0d37ac7ade7c6e56e3d3e34af298254f35c8b501e\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c071f12ec97eb449422de643e737924e02eec266f3b56cde476eae4fad5c6e64\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""a3331c0c-a013-4754-a122-9988b3381fec\"",\""version\"":4}"",""{\""crypto\"":{\""kdf\"":{\""function\"":\""pbkdf2\"",\""params\"":{\""dklen\"":32,\""c\"":262144,\""prf\"":\""hmac-sha256\"",\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0xadb59d10d2436c12f2fe229f27ec598739da92686485e9fed5255d3ed9bb1c1f\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""8d192da5a06c001eca9c954812ce165d007c889d7711b12faa7a9d6f4d5cc6ae\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""905dd873-48af-416a-8c80-4283d5af84f9\"",\""version\"":4}""],""passwords"":[""7465737470617373776f7264f09f9491"",""7465737470617373776f7264f09f9491""]}"
|
|
Vector3 = r"{""keystores"":[""{\""crypto\"":{\""kdf\"":{\""function\"":\""scrypt\"",\""params\"":{\""dklen\"":32,\""n\"":262144,\""p\"":1,\""r\"":8,\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0xea4d7f495ac74bbf431ef340f15ee1aea75811bd1bab8dd64b3c2dfc041d5d90\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c40a44096120e406a011ec5a22d7cbb24126436c471e21b10f078c722c6d0c3f\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""ad1bf334-faaa-4257-8e28-81a45722e87b\"",\""version\"":4}""],""passwords"":[""7465737470617373776f7264f09f9491""]}"
|
|
Vector4 = r"{""keystores"":[""{\""crypto\"":{\""kdf\"":{\""function\"":\""scrypt\"",\""params\"":{\""dklen\"":32,\""n\"":262144,\""p\"":1,\""r\"":8,\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0xea4d7f495ac74bbf431ef340f15ee1aea75811bd1bab8dd64b3c2dfc041d5d90\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c40a44096120e406a011ec5a22d7cbb24126436c471e21b10f078c722c6d0c3f\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""ad1bf334-faaa-4257-8e28-81a45722e87b\"",\""version\"":4}"",""{\""crypto\"":{\""kdf\"":{\""function\"":\""scrypt\"",\""params\"":{\""dklen\"":32,\""n\"":262144,\""p\"":1,\""r\"":8,\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0x71ed99dab563f1e9f1190b0de9d92d3266df2223036e7dc3ca9d9599478fe5a4\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""896298820832505128a09f51d72e4fa143b40997c3bafc40e213bf52cc6da4f5\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xa00d2954717425ce047e0928e5f4ec7c0e3bbe1058db511303fd659770ddace686ee2e22ac180422e516f4c503eb2228\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""d91bcde8-8bf5-45c6-b04d-c10d99ae9b6b\"",\""version\"":4}""],""passwords"":[""7465737470617373776f7264f09f9491"",""7465737470617373776f7264f09f9491""]}"
|
|
Vector5 = r"{""keystores"":[""{\""crypto\"":{\""kdf\"":{\""function\"":\""pbkdf2\"",\""params\"":{\""dklen\"":32,\""c\"":262144,\""prf\"":\""hmac-sha256\"",\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0x88c0059314a3db1b2e86d4b0d37ac7ade7c6e56e3d3e34af298254f35c8b501e\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c071f12ec97eb449422de643e737924e02eec266f3b56cde476eae4fad5c6e64\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""a3331c0c-a013-4754-a122-9988b3381fec\"",\""version\"":4}"",""{\""crypto\"":{\""kdf\"":{\""function\"":\""scrypt\"",\""params\"":{\""dklen\"":32,\""n\"":262144,\""p\"":1,\""r\"":8,\""salt\"":\""d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\""},\""message\"":\""\""},\""checksum\"":{\""function\"":\""sha256\"",\""params\"":{},\""message\"":\""0xea4d7f495ac74bbf431ef340f15ee1aea75811bd1bab8dd64b3c2dfc041d5d90\""},\""cipher\"":{\""function\"":\""aes-128-ctr\"",\""params\"":{\""iv\"":\""264daa3f303d7259501c93d997d84fe6\""},\""message\"":\""c40a44096120e406a011ec5a22d7cbb24126436c471e21b10f078c722c6d0c3f\""}},\""description\"":\""Test keystore\"",\""pubkey\"":\""0xb4102a1f6c80e5c596a974ebd930c9f809c3587dc4d1d3634b77ff66db71e376dbc86c3252c6d140ce031f4ec6167798\"",\""path\"":\""m/12381/60/0/0\"",\""uuid\"":\""ad1bf334-faaa-4257-8e28-81a45722e87b\"",\""version\"":4}""],""passwords"":[""7465737470617373776f7264f09f9491"", ""7465737470617373776f7264f09f9491""]}"
|
|
|
|
let
|
|
r1 = decodeBytes(KeystoresAndSlashingProtection,
|
|
Vector1.toOpenArrayByte(0, len(Vector1) - 1),
|
|
Opt.some(getContentType("application/json").get()))
|
|
r2 = decodeBytes(KeystoresAndSlashingProtection,
|
|
Vector2.toOpenArrayByte(0, len(Vector2) - 1),
|
|
Opt.some(getContentType("application/json").get()))
|
|
r3 = decodeBytes(KeystoresAndSlashingProtection,
|
|
Vector3.toOpenArrayByte(0, len(Vector3) - 1),
|
|
Opt.some(getContentType("application/json").get()))
|
|
r4 = decodeBytes(KeystoresAndSlashingProtection,
|
|
Vector4.toOpenArrayByte(0, len(Vector4) - 1),
|
|
Opt.some(getContentType("application/json").get()))
|
|
r5 = decodeBytes(KeystoresAndSlashingProtection,
|
|
Vector5.toOpenArrayByte(0, len(Vector5) - 1),
|
|
Opt.some(getContentType("application/json").get()))
|
|
|
|
check:
|
|
r1.isOk() == true
|
|
r2.isOk() == true
|
|
r3.isOk() == true
|
|
r4.isOk() == true
|
|
r5.isOk() == true
|
|
|
|
let
|
|
d1 = r1.get()
|
|
d2 = r2.get()
|
|
d3 = r3.get()
|
|
d4 = r4.get()
|
|
d5 = r5.get()
|
|
|
|
check:
|
|
len(d1.keystores) == 1
|
|
len(d2.keystores) == 2
|
|
len(d3.keystores) == 1
|
|
len(d4.keystores) == 2
|
|
len(d5.keystores) == 2
|
|
d1.keystores[0] == keystore1
|
|
d2.keystores[0] == keystore1
|
|
d2.keystores[1] == keystore2
|
|
d3.keystores[0] == keystore3
|
|
d4.keystores[0] == keystore3
|
|
d4.keystores[1] == keystore4
|
|
d5.keystores[0] == keystore1
|
|
d5.keystores[1] == keystore3
|
|
len(d1.passwords) == 1
|
|
len(d2.passwords) == 2
|
|
len(d3.passwords) == 1
|
|
len(d4.passwords) == 2
|
|
len(d5.passwords) == 2
|
|
d1.passwords == @["7465737470617373776f7264f09f9491"]
|
|
d2.passwords == @["7465737470617373776f7264f09f9491",
|
|
"7465737470617373776f7264f09f9491"]
|
|
d3.passwords == @["7465737470617373776f7264f09f9491"]
|
|
d4.passwords == @["7465737470617373776f7264f09f9491",
|
|
"7465737470617373776f7264f09f9491"]
|
|
d5.passwords == @["7465737470617373776f7264f09f9491",
|
|
"7465737470617373776f7264f09f9491"]
|
|
|
|
suite "ListKeys requests" & testFlavour:
|
|
asyncTest "Correct token provided" & testFlavour:
|
|
let
|
|
filesystemKeys = sorted listLocalValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir)
|
|
apiKeys = sorted (await client.listKeys(correctTokenValue)).data
|
|
|
|
check filesystemKeys == apiKeys
|
|
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let
|
|
response = await client.listKeysPlain()
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.listKeysPlain(
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.listKeysPlain(
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
expect RestError:
|
|
let keystores = await client.listKeys("Invalid Token")
|
|
|
|
suite "ImportKeystores requests" & testFlavour:
|
|
asyncTest "ImportKeystores/ListKeystores/DeleteKeystores" & testFlavour:
|
|
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
|
|
filesystemKeys1 = sorted(
|
|
listLocalValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir))
|
|
apiKeystores1 = sorted((await client.listKeys(correctTokenValue)).data)
|
|
|
|
check:
|
|
filesystemKeys1 == apiKeystores1
|
|
importKeystoresBody1.keystores[0].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[1].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[2].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[3].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[4].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[5].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[6].pubkey in filesystemKeys1
|
|
importKeystoresBody1.keystores[7].pubkey in filesystemKeys1
|
|
|
|
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
|
|
filesystemKeys2 = sorted(
|
|
listLocalValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir))
|
|
apiKeystores2 = sorted((await client.listKeys(correctTokenValue)).data)
|
|
|
|
check:
|
|
filesystemKeys2 == apiKeystores2
|
|
deleteKeysBody1.pubkeys[0] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[1] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[2] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[3] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[4] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[5] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[6] notin filesystemKeys2
|
|
deleteKeysBody1.pubkeys[7] notin filesystemKeys2
|
|
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let
|
|
response = await client.importKeystoresPlain(importKeystoresBody)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.importKeystoresPlain(
|
|
importKeystoresBody,
|
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.importKeystoresPlain(
|
|
importKeystoresBody,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
suite "DeleteKeys requests" & testFlavour:
|
|
asyncTest "Deleting not existing key" & testFlavour:
|
|
let
|
|
response = await client.deleteKeysPlain(
|
|
deleteKeysBody,
|
|
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 200
|
|
responseJson["data"][0]["status"].getStr() == "not_found"
|
|
responseJson["data"][0]["message"].getStr() == ""
|
|
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let
|
|
response = await client.deleteKeysPlain(deleteKeysBody)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.deleteKeysPlain(
|
|
deleteKeysBody,
|
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.deleteKeysPlain(
|
|
deleteKeysBody,
|
|
extraHeaders = @[("Authorization", "Bearer XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
suite "ListRemoteKeys requests" & testFlavour:
|
|
asyncTest "Correct token provided" & testFlavour:
|
|
let
|
|
filesystemKeys = sorted(
|
|
listRemoteValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir))
|
|
apiKeystores = sorted((
|
|
await client.listRemoteKeys(correctTokenValue)).data)
|
|
|
|
check filesystemKeys == apiKeystores
|
|
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let
|
|
response = await client.listRemoteKeysPlain()
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.listRemoteKeysPlain(
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.listRemoteKeysPlain(
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
expect RestError:
|
|
let keystores = await client.listKeys("Invalid Token")
|
|
|
|
suite "Fee recipient management" & testFlavour:
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
|
|
block:
|
|
let
|
|
response = await client.listFeeRecipientPlain(pubkey)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setFeeRecipientPlain(
|
|
pubkey,
|
|
default SetFeeRecipientRequest)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteFeeRecipientPlain(pubkey, EmptyBody())
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
|
|
block:
|
|
let
|
|
response = await client.listFeeRecipientPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setFeeRecipientPlain(
|
|
pubkey,
|
|
default SetFeeRecipientRequest,
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteFeeRecipientPlain(
|
|
pubkey,
|
|
EmptyBody(),
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
|
|
block:
|
|
let
|
|
response = await client.listFeeRecipientPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setFeeRecipientPlain(
|
|
pubkey,
|
|
default SetFeeRecipientRequest,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteFeeRecipientPlain(
|
|
pubkey,
|
|
EmptyBody(),
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Obtaining the fee recpient of a missing validator returns 404" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[0]).expect("valid key")
|
|
response = await client.listFeeRecipientPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
|
|
|
check:
|
|
response.status == 404
|
|
|
|
asyncTest "Setting the fee recipient on a missing validator creates a record for it" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[1]).expect("valid key")
|
|
feeRecipient = specifiedFeeRecipient(1)
|
|
|
|
await client.setFeeRecipient(pubkey, feeRecipient, correctTokenValue)
|
|
let resultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
|
|
|
|
check:
|
|
resultFromApi == feeRecipient
|
|
|
|
asyncTest "Obtaining the fee recpient of an unconfigured validator returns the suggested default" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
resultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
|
|
|
|
check:
|
|
resultFromApi == defaultFeeRecipient
|
|
|
|
asyncTest "Configuring the fee recpient" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[1]).expect("valid key")
|
|
firstFeeRecipient = specifiedFeeRecipient(2)
|
|
|
|
await client.setFeeRecipient(pubkey, firstFeeRecipient, correctTokenValue)
|
|
|
|
let firstResultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
|
|
check:
|
|
firstResultFromApi == firstFeeRecipient
|
|
|
|
let secondFeeRecipient = specifiedFeeRecipient(3)
|
|
await client.setFeeRecipient(pubkey, secondFeeRecipient, correctTokenValue)
|
|
|
|
let secondResultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
|
|
check:
|
|
secondResultFromApi == secondFeeRecipient
|
|
|
|
await client.deleteFeeRecipient(pubkey, correctTokenValue)
|
|
let finalResultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
|
|
check:
|
|
finalResultFromApi == defaultFeeRecipient
|
|
|
|
suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & testFlavour:
|
|
asyncTest "Importing list of remote keys" & testFlavour:
|
|
let
|
|
response1 = await client.importRemoteKeysPlain(
|
|
importRemoteKeystoresBody,
|
|
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
|
responseJson1 = Json.decode(response1.data, JsonNode)
|
|
|
|
check:
|
|
response1.status == 200
|
|
|
|
for i in 0 ..< 18:
|
|
check:
|
|
responseJson1["data"][i]["status"].getStr() == expectedImportStatus(i)
|
|
responseJson1["data"][i]["message"].getStr() == ""
|
|
|
|
let
|
|
filesystemKeys1 = sorted(
|
|
listRemoteValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir))
|
|
apiKeystores1 = sorted((
|
|
await client.listRemoteKeys(correctTokenValue)).data)
|
|
|
|
check:
|
|
filesystemKeys1 == apiKeystores1
|
|
|
|
for item in newPublicKeys:
|
|
let key = ValidatorPubKey.fromHex(item).tryGet()
|
|
let found =
|
|
block:
|
|
var res = false
|
|
for keystore in filesystemKeys1:
|
|
if keystore == 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
|
|
filesystemKeys2 = sorted(
|
|
listRemoteValidators(keymanager.validatorsDir,
|
|
keymanager.secretsDir))
|
|
apiKeystores2 = sorted((
|
|
await client.listRemoteKeys(correctTokenValue)).data)
|
|
|
|
check:
|
|
filesystemKeys2 == apiKeystores2
|
|
|
|
for keystore in filesystemKeys2:
|
|
let key = "0x" & keystore.toHex()
|
|
check:
|
|
key notin newPublicKeys
|
|
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let
|
|
response = await client.importRemoteKeysPlain(importRemoteKeystoresBody)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.importRemoteKeysPlain(
|
|
importRemoteKeystoresBody,
|
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.importRemoteKeysPlain(
|
|
importRemoteKeystoresBody,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
suite "DeleteRemoteKeys requests" & testFlavour:
|
|
asyncTest "Deleting not existing key" & testFlavour:
|
|
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" & testFlavour:
|
|
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(nodeValidatorsDir, nodeSecretsDir))
|
|
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" & testFlavour:
|
|
let
|
|
response = await client.deleteRemoteKeysPlain(
|
|
deleteRemoteKeystoresBody1)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Header" & testFlavour:
|
|
let
|
|
response = await client.deleteRemoteKeysPlain(
|
|
deleteRemoteKeystoresBody1,
|
|
extraHeaders = @[("Authorization", "Basic XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Invalid Authorization Token" & testFlavour:
|
|
let
|
|
response = await client.deleteRemoteKeysPlain(
|
|
deleteRemoteKeystoresBody1,
|
|
extraHeaders = @[("Authorization", "Bearer XYZ")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
proc delayedTests(basePort: int) {.async.} =
|
|
let
|
|
beaconNodeKeymanager = KeymanagerToTest(
|
|
ident: "Beacon Node",
|
|
port: basePort + PortKind.KeymanagerBN.ord,
|
|
validatorsDir: nodeValidatorsDir,
|
|
secretsDir: nodeSecretsDir)
|
|
|
|
validatorClientKeymanager = KeymanagerToTest(
|
|
ident: "Validator Client",
|
|
port: basePort + PortKind.KeymanagerVC.ord,
|
|
validatorsDir: vcValidatorsDir,
|
|
secretsDir: vcSecretsDir)
|
|
|
|
while bnStatus != BeaconNodeStatus.Running:
|
|
await sleepAsync(1.seconds)
|
|
|
|
asyncSpawn startValidatorClient(basePort)
|
|
|
|
await sleepAsync(2.seconds)
|
|
|
|
let deadline = sleepAsync(10.minutes)
|
|
await runTests(beaconNodeKeymanager) or deadline
|
|
|
|
# TODO
|
|
# This tests showed flaky behavior on a single Windows CI host
|
|
# Re-enable it in a follow-up PR
|
|
# await runTests(validatorClientKeymanager)
|
|
|
|
bnStatus = BeaconNodeStatus.Stopping
|
|
|
|
proc main(basePort: int) {.async.} =
|
|
if dirExists(dataDir):
|
|
os.removeDir dataDir
|
|
|
|
asyncSpawn delayedTests(basePort)
|
|
|
|
prepareNetwork()
|
|
startBeaconNode(basePort)
|
|
|
|
let
|
|
basePortStr = os.getEnv("NIMBUS_TEST_KEYMANAGER_BASE_PORT", $defaultBasePort)
|
|
basePort =
|
|
try:
|
|
let val = parseInt(basePortStr)
|
|
if val < 0 or val > (uint16.high.int - PortKind.high.ord):
|
|
fatal "Invalid base port arg", basePort = basePortStr
|
|
quit 1
|
|
val
|
|
except ValueError as exc:
|
|
fatal "Invalid base port arg", basePort = basePortStr, exc = exc.msg
|
|
quit 1
|
|
|
|
waitFor main(basePort)
|