mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-10 06:16:25 +00:00
8771e91d53
* Support for driving multiple EL nodes from a single Nimbus BN Full list of changes: * Eth1Monitor has been renamed to ELManager to match its current responsibilities better. * The ELManager is no longer optional in the code (it won't have a nil value under any circumstances). * The support for subscribing for headers was removed as it only worked with WebSockets and contributed significant complexity while bringing only a very minor advantage. * The `--web3-url` parameter has been deprecated in favor of a new `--el` parameter. The new parameter has a reasonable default value and supports specifying a different JWT for each connection. Each connection can also be configured with a different set of responsibilities (e.g. download deposits, validate blocks and/or produce blocks). On the command-line, these properties can be configured through URL properties stored in the #anchor part of the URL. In TOML files, they come with a very natural syntax (althrough the URL scheme is also supported). * The previously scattered EL-related state and logic is now moved to `eth1_monitor.nim` (this module will be renamed to `el_manager.nim` in a follow-up commit). State is assigned properly either to the `ELManager` or the to individual `ELConnection` objects where appropriate. The ELManager executes all Engine API requests against all attached EL nodes, in parallel. It compares their results and if there is a disagreement regarding the validity of a certain payload, this is detected and the beacon node is protected from publishing a block with a potential execution layer consensus bug in it. The BN provides metrics per EL node for the number of successful or failed requests for each type Engine API requests. If an EL node goes offline and connectivity is resoted later, we report the problem and the remedy in edge-triggered fashion. * More progress towards implementing Deneb block production in the VC and comparing the value of blocks produced by the EL and the builder API. * Adds a Makefile target for the zhejiang testnet
1496 lines
61 KiB
Nim
1496 lines
61 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,
|
|
../ncli/ncli_testnet,
|
|
./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"
|
|
runtimeConfigFile = dataDir / "config.yaml"
|
|
genesisFile = dataDir / "genesis.ssz"
|
|
depositTreeSnapshotFile = dataDir / "deposit_tree_snapshot.ssz"
|
|
bootstrapEnrFile = dataDir / "bootstrap_node.enr"
|
|
tokenFilePath = dataDir / "keymanager-token.txt"
|
|
defaultBasePort = 49000
|
|
correctTokenValue = "some secret token"
|
|
defaultFeeRecipient = Eth1Address.fromHex("0x000000000000000000000000000000000000DEAD")
|
|
defaultGasLimit = 30_000_000
|
|
|
|
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 runtimeConfigWritten = secureWriteFile(runtimeConfigFile, """
|
|
ALTAIR_FORK_EPOCH: 0
|
|
BELLATRIX_FORK_EPOCH: 0
|
|
""")
|
|
|
|
if runtimeConfigWritten.isOk:
|
|
notice "Run-time config written", filename = runtimeConfigFile
|
|
else:
|
|
fatal "Failed to write run-time config", filename = runtimeConfigFile
|
|
quit 1
|
|
|
|
let createTestnetConf = try: ncli_testnet.CliConfig.load(cmdLine = mapIt([
|
|
"createTestnet",
|
|
"--data-dir=" & dataDir,
|
|
"--total-validators=" & $simulationDepositsCount,
|
|
"--deposits-file=" & depositsFile,
|
|
"--output-genesis=" & genesisFile,
|
|
"--output-deposit-tree-snapshot=" & depositTreeSnapshotFile,
|
|
"--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),
|
|
"--no-el",
|
|
"--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)
|
|
node = BeaconNode.init(rng, runNodeConf, metadata)
|
|
|
|
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 "Gas limit management" & testFlavour:
|
|
asyncTest "Missing Authorization header" & testFlavour:
|
|
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
|
|
block:
|
|
let
|
|
response = await client.listGasLimitPlain(pubkey)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setGasLimitPlain(
|
|
pubkey,
|
|
default SetGasLimitRequest)
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteGasLimitPlain(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.listGasLimitPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setGasLimitPlain(
|
|
pubkey,
|
|
default SetGasLimitRequest,
|
|
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 401
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteGasLimitPlain(
|
|
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.listGasLimitPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.setGasLimitPlain(
|
|
pubkey,
|
|
default SetGasLimitRequest,
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
block:
|
|
let
|
|
response = await client.deleteGasLimitPlain(
|
|
pubkey,
|
|
EmptyBody(),
|
|
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
|
responseJson = Json.decode(response.data, JsonNode)
|
|
|
|
check:
|
|
response.status == 403
|
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
|
|
|
asyncTest "Obtaining the gas limit of a missing validator returns 404" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[0]).expect("valid key")
|
|
response = await client.listGasLimitPlain(
|
|
pubkey,
|
|
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
|
|
|
check:
|
|
response.status == 404
|
|
|
|
asyncTest "Setting the gas limit on a missing validator creates a record for it" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[1]).expect("valid key")
|
|
gasLimit = 20_000_000'u64
|
|
|
|
await client.setGasLimit(pubkey, gasLimit, correctTokenValue)
|
|
let resultFromApi = await client.listGasLimit(pubkey, correctTokenValue)
|
|
|
|
check:
|
|
resultFromApi == gasLimit
|
|
|
|
asyncTest "Obtaining the gas limit of an unconfigured validator returns the suggested default" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
|
resultFromApi = await client.listGasLimit(pubkey, correctTokenValue)
|
|
|
|
check:
|
|
resultFromApi == defaultGasLimit
|
|
|
|
asyncTest "Configuring the gas limit" & testFlavour:
|
|
let
|
|
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[1]).expect("valid key")
|
|
firstGasLimit = 40_000_000'u64
|
|
|
|
await client.setGasLimit(pubkey, firstGasLimit, correctTokenValue)
|
|
|
|
let firstResultFromApi = await client.listGasLimit(pubkey, correctTokenValue)
|
|
check:
|
|
firstResultFromApi == firstGasLimit
|
|
|
|
let secondGasLimit = 50_000_000'u64
|
|
await client.setGasLimit(pubkey, secondGasLimit, correctTokenValue)
|
|
|
|
let secondResultFromApi = await client.listGasLimit(pubkey, correctTokenValue)
|
|
check:
|
|
secondResultFromApi == secondGasLimit
|
|
|
|
await client.deleteGasLimit(pubkey, correctTokenValue)
|
|
let finalResultFromApi = await client.listGasLimit(pubkey, correctTokenValue)
|
|
check:
|
|
finalResultFromApi == defaultGasLimit
|
|
|
|
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)
|