[Keymanager API] Support for the feerecipient end-points (#3864)

Other changes:

* The Keymanager error responses differ from the Beacon API responses.
  'keymanagerApiError' replaces the former usages of 'jsonError'.

* Return status code 401 and 403 for authorization errors in accordance
  to the spec.

* Eliminate inconsistencies in the REST JSON parsing. Some of the code
  paths allowed missing fields.

* Added logging of serialization failure details at DEBUG level.
This commit is contained in:
zah 2022-07-13 17:45:04 +03:00 committed by GitHub
parent 456b5ebc48
commit 806536a040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 575 additions and 195 deletions

View File

@ -168,6 +168,17 @@ OK: 3/3 Fail: 0/3 Skip: 0/3
+ addExitMessage/getVoluntaryExitMessage OK
```
OK: 3/3 Fail: 0/3 Skip: 0/3
## Fee recipient management [Preset: mainnet]
```diff
+ Configuring the fee recpient [Preset: mainnet] OK
+ Invalid Authorization Header [Preset: mainnet] OK
+ Invalid Authorization Token [Preset: mainnet] OK
+ Missing Authorization header [Preset: mainnet] OK
+ Obtaining the fee recpient of a missing validator returns 404 [Preset: mainnet] OK
+ Obtaining the fee recpient of an unconfigured validator returns the suggested default [Pre OK
+ Setting the fee recipient on a missing validator creates a record for it [Preset: mainnet] OK
```
OK: 7/7 Fail: 0/7 Skip: 0/7
## FinalizedBlocks [Preset: mainnet]
```diff
+ Basic ops [Preset: mainnet] OK
@ -569,4 +580,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
OK: 9/9 Fail: 0/9 Skip: 0/9
---TOTAL---
OK: 314/319 Fail: 0/319 Skip: 5/319
OK: 321/326 Fail: 0/326 Skip: 5/326

View File

@ -345,7 +345,6 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
let vindex =
block:
let vid = validator_id.get()
case vid.kind
of ValidatorQueryKind.Key:
let optIndices = keysToIndices(node.restKeysCache, state, [vid.key])

View File

@ -50,6 +50,8 @@ const
"Proposer slashing object was broadcasted"
InvalidVoluntaryExitObjectError* =
"Unable to decode voluntary exit object(s)"
InvalidFeeRecipientRequestError* =
"Bad request. Request was malformed and could not be processed"
VoluntaryExitValidationError* =
"Invalid voluntary exit, it will never pass validation so it's rejected"
VoluntaryExitValidationSuccess* =
@ -195,7 +197,7 @@ const
"Invalid validator's public key(s) found"
BadRequestFormatError* =
"Bad request format"
InvalidAuthorization* =
InvalidAuthorizationError* =
"Invalid Authorization Header"
PrunedStateError* =
"Trying to access a pruned historical state"

View File

@ -50,6 +50,24 @@ proc listRemoteDistributedValidators*(node: BeaconNode): seq[DistributedKeystore
)
validators
proc keymanagerApiError(status: HttpCode, msg: string): RestApiResponse =
let data =
block:
var default: string
try:
var defstrings: seq[string]
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("message", msg)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
RestApiResponse.error(status, data, "application/json")
proc checkAuthorization*(request: HttpRequestRef,
node: BeaconNode): Result[void, AuthorizationError] =
let authorizations = request.headers.getList("authorization")
@ -65,6 +83,15 @@ proc checkAuthorization*(request: HttpRequestRef,
else:
return err noAuthorizationHeader
proc authErrorResponse(error: AuthorizationError): RestApiResponse =
let status = case error:
of missingBearerScheme, noAuthorizationHeader:
Http401
of incorrectToken:
Http403
keymanagerApiError(status, InvalidAuthorizationError)
proc validateUri*(url: string): Result[Uri, cstring] =
let surl = parseUri(url)
if surl.scheme notin ["http", "https"]:
@ -106,8 +133,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let response = GetKeystoresResponse(data: listLocalValidators(node))
return RestApiResponse.jsonResponsePlain(response)
@ -116,16 +142,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let request =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(KeystoresAndSlashingProtection, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
$dres.error())
return keymanagerApiError(Http400, InvalidKeystoreObjects)
dres.get()
if request.slashing_protection.isSome():
@ -133,13 +157,13 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
slashing_protection.metadata.genesis_validators_root.Eth2Digest:
return RestApiResponse.jsonError(Http400,
return keymanagerApiError(Http400,
"The slashing protection database and imported file refer to " &
"different blockchains.")
let res = inclSPDIR(node.attachedValidators.slashingProtection,
slashing_protection)
if res == siFailure:
return RestApiResponse.jsonError(Http500,
return keymanagerApiError(Http500,
"Internal server error; Failed to import slashing protection data")
var response: PostKeystoresResponse
@ -169,16 +193,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
$dres.error())
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
dres.get().pubkeys
var
@ -230,8 +252,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet, "/api/eth/v1/remotekeys") do () -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let response = GetRemoteKeystoresResponse(data: listRemoteValidators(node))
return RestApiResponse.jsonResponsePlain(response)
@ -240,16 +261,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(ImportRemoteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
$dres.error())
return keymanagerApiError(Http400, InvalidKeystoreObjects)
dres.get().remote_keys
var response: PostKeystoresResponse
@ -274,16 +293,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
$dres.error())
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
dres.get().pubkeys
var response: DeleteRemoteKeystoresResponse
@ -292,13 +309,78 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
response.data.add(status)
return RestApiResponse.jsonResponsePlain(response)
# https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/ListFeeRecipient
router.api(MethodGet, "/api/eth/v1/validator/{pubkey}/feerecipient") do (
pubkey: ValidatorPubKey) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return authErrorResponse authStatus.error
let
pubkey = pubkey.valueOr:
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
ethaddress = node.config.getSuggestedFeeRecipient(pubkey)
return if ethaddress.isOk:
RestApiResponse.jsonResponse(ListFeeRecipientResponse(
pubkey: pubkey,
ethaddress: ethaddress.get))
else:
case ethaddress.error
of noSuchValidator:
keymanagerApiError(Http404, "No matching validator found")
of invalidFeeRecipientFile:
keymanagerApiError(Http500, "Error reading fee recipient file")
# https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/SetFeeRecipient
router.api(MethodPost, "/api/eth/v1/validator/{pubkey}/feerecipient") do (
pubkey: ValidatorPubKey,
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return authErrorResponse authStatus.error
let
pubkey= pubkey.valueOr:
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
feeRecipientReq =
block:
if contentBody.isNone():
return keymanagerApiError(Http400, InvalidFeeRecipientRequestError)
let dres = decodeBody(SetFeeRecipientRequest, contentBody.get())
if dres.isErr():
return keymanagerApiError(Http400, InvalidFeeRecipientRequestError)
dres.get()
status = node.config.setFeeRecipient(pubkey, feeRecipientReq.ethaddress)
return if status.isOk:
RestApiResponse.response("", Http202, "text/plain")
else:
keymanagerApiError(
Http500, "Failed to set fee recipient: " & status.error)
# https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/DeleteFeeRecipient
router.api(MethodDelete, "/api/eth/v1/validator/{pubkey}/feerecipient") do (
pubkey: ValidatorPubKey) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return authErrorResponse authStatus.error
let
pubkey = pubkey.valueOr:
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
res = removeFeeRecipientFile(node.config, pubkey)
return if res.isOk:
RestApiResponse.response("", Http204, "text/plain")
else:
keymanagerApiError(
Http500, "Failed to remove fee recipient file: " & res.error)
# TODO: These URLs will be changed once we submit a proposal for
# /api/eth/v2/remotekeys that supports distributed keys.
router.api(MethodGet, "/api/eth/v1/remotekeys/distributed") do () -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let response = GetDistributedKeystoresResponse(data: listRemoteDistributedValidators(node))
return RestApiResponse.jsonResponsePlain(response)
@ -308,16 +390,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(ImportDistributedKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
$dres.error())
return keymanagerApiError(Http400, InvalidKeystoreObjects)
dres.get.remote_keys
var response: PostKeystoresResponse
@ -339,16 +419,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody: Option[ContentBody]) -> RestApiResponse:
let authStatus = checkAuthorization(request, node)
if authStatus.isErr():
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
$authStatus.error())
return authErrorResponse authStatus.error
let keys =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
return keymanagerApiError(Http404, EmptyRequestBodyError)
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
$dres.error())
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
dres.get.pubkeys
var response: DeleteRemoteKeystoresResponse
@ -388,6 +466,21 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v1/remotekeys",
"/api/eth/v1/remotekeys")
router.redirect(
MethodGet,
"/eth/v1/validator/{pubkey}/feerecipient",
"/api/eth/v1/validator/{pubkey}/feerecipient")
router.redirect(
MethodPost,
"/eth/v1/validator/{pubkey}/feerecipient",
"/api/eth/v1/validator/{pubkey}/feerecipient")
router.redirect(
MethodDelete,
"/eth/v1/validator/{pubkey}/feerecipient",
"/api/eth/v1/validator/{pubkey}/feerecipient")
router.redirect(
MethodGet,
"/eth/v1/remotekeys/distributed",

View File

@ -48,6 +48,8 @@ proc validate(key: string, value: string): int =
0
of "{block_root}":
0
of "{pubkey}":
int(value.len != 98)
else:
1

View File

@ -69,6 +69,8 @@ const
[byte('a'), byte('l'), byte('t'), byte('a'), byte('i'), byte('r')]
type
EmptyBody* = object
RestGenericError* = object
code*: uint64
message*: string
@ -81,29 +83,31 @@ type
EncodeTypes* =
AttesterSlashing |
DeleteKeystoresBody |
EmptyBody |
ImportDistributedKeystoresBody |
ImportRemoteKeystoresBody |
KeystoresAndSlashingProtection |
ProposerSlashing |
phase0.SignedBeaconBlock |
altair.SignedBeaconBlock |
bellatrix.SignedBeaconBlock |
SetFeeRecipientRequest |
SignedBlindedBeaconBlock |
SignedValidatorRegistrationV1 |
SignedVoluntaryExit |
Web3SignerRequest |
KeystoresAndSlashingProtection |
DeleteKeystoresBody |
ImportRemoteKeystoresBody |
ImportDistributedKeystoresBody
altair.SignedBeaconBlock |
bellatrix.SignedBeaconBlock |
phase0.SignedBeaconBlock
EncodeArrays* =
seq[ValidatorIndex] |
seq[Attestation] |
seq[RemoteKeystoreInfo] |
seq[RestCommitteeSubscription] |
seq[RestSignedContributionAndProof] |
seq[RestSyncCommitteeMessage] |
seq[RestSyncCommitteeSubscription] |
seq[SignedAggregateAndProof] |
seq[SignedValidatorRegistrationV1] |
seq[RestCommitteeSubscription] |
seq[RestSyncCommitteeSubscription] |
seq[RestSyncCommitteeMessage] |
seq[RestSignedContributionAndProof] |
seq[RemoteKeystoreInfo]
seq[ValidatorIndex]
DecodeTypes* =
DataEnclosedObject |
@ -111,20 +115,22 @@ type
DataRootEnclosedObject |
DataVersionEnclosedObject |
GetBlockV2Response |
GetDistributedKeystoresResponse |
GetKeystoresResponse |
GetRemoteKeystoresResponse |
GetDistributedKeystoresResponse |
GetStateV2Response |
GetStateForkResponse |
GetStateV2Response |
KeymanagerGenericError |
KeystoresAndSlashingProtection |
ListFeeRecipientResponse |
ProduceBlockResponseV2 |
RestDutyError |
RestValidator |
RestGenericError |
RestValidator |
Web3SignerErrorResponse |
Web3SignerKeysResponse |
Web3SignerSignatureResponse |
Web3SignerStatusResponse |
KeystoresAndSlashingProtection
Web3SignerStatusResponse
SszDecodeTypes* =
GetPhase0StateSszResponse |
@ -470,11 +476,10 @@ template hexOriginal(data: openArray[byte]): string =
to0xHex(data)
proc decodeJsonString*[T](t: typedesc[T],
data: JsonString,
requireAllFields = true): Result[T, cstring] =
data: JsonString): Result[T, cstring] =
try:
ok(RestJson.decode(string(data), T,
requireAllFields = requireAllFields,
requireAllFields = true,
allowUnknownFields = true))
except SerializationError:
err("Unable to deserialize data")
@ -1558,8 +1563,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerAggregationSlotData,
data.get(), true)
let res = decodeJsonString(Web3SignerAggregationSlotData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `aggregation_slot` format")
@ -1574,7 +1578,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(AggregateAndProof, data.get(), true)
let res = decodeJsonString(AggregateAndProof, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `aggregate_and_proof` format")
@ -1590,7 +1594,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(AttestationData, data.get(), true)
let res = decodeJsonString(AttestationData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `attestation` format")
@ -1606,7 +1610,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(phase0.BeaconBlock, data.get(), true)
let res = decodeJsonString(phase0.BeaconBlock, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `block` format")
@ -1622,7 +1626,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerForkedBeaconBlock, data.get(), true)
let res = decodeJsonString(Web3SignerForkedBeaconBlock, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `beacon_block` format")
@ -1636,7 +1640,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `deposit` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerDepositData, data.get(), true)
let res = decodeJsonString(Web3SignerDepositData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `deposit` format")
@ -1652,8 +1656,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerRandaoRevealData, data.get(),
true)
let res = decodeJsonString(Web3SignerRandaoRevealData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `randao_reveal` format")
@ -1669,7 +1672,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(VoluntaryExit, data.get(), true)
let res = decodeJsonString(VoluntaryExit, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `voluntary_exit` format")
@ -1686,8 +1689,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerSyncCommitteeMessageData,
data.get(), true)
let res = decodeJsonString(Web3SignerSyncCommitteeMessageData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `sync_committee_message` format")
@ -1705,8 +1707,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(SyncAggregatorSelectionData,
data.get(), true)
let res = decodeJsonString(SyncAggregatorSelectionData, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `sync_aggregator_selection_data` format")
@ -1724,8 +1725,7 @@ proc readValue*(reader: var JsonReader[RestJson],
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(ContributionAndProof,
data.get(), true)
let res = decodeJsonString(ContributionAndProof, data.get())
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `contribution_and_proof` format")
@ -2068,7 +2068,10 @@ proc readValue*(reader: var JsonReader[RestJson],
for item in strKeystores:
let key =
try:
RestJson.decode(item, Keystore, allowUnknownFields = true)
RestJson.decode(item,
Keystore,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
# TODO re-raise the exception by adjusting the column index, so the user
# will get an accurate syntax error within the larger message
@ -2080,7 +2083,10 @@ proc readValue*(reader: var JsonReader[RestJson],
if strSlashing.isSome():
let db =
try:
RestJson.decode(strSlashing.get(), SPDIR, allowUnknownFields = true)
RestJson.decode(strSlashing.get(),
SPDIR,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
reader.raiseUnexpectedValue("Invalid slashing protection format")
some(db)
@ -2172,8 +2178,12 @@ proc decodeBody*[T](t: typedesc[T],
return err("Unsupported content type")
let data =
try:
RestJson.decode(body.data, T, allowUnknownFields = true)
RestJson.decode(body.data, T,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>")
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
@ -2222,8 +2232,12 @@ proc decodeBytes*[T: DecodeTypes](t: typedesc[T], value: openArray[byte],
case contentType
of "application/json":
try:
ok RestJson.decode(value, T, allowUnknownFields = true)
except SerializationError:
ok RestJson.decode(value, T,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>")
err("Serialization error")
else:
err("Content-Type not supported")

View File

@ -20,6 +20,19 @@ UUID.serializesAsBaseIn RestJson
KeyPath.serializesAsBaseIn RestJson
WalletName.serializesAsBaseIn RestJson
proc raiseKeymanagerGenericError*(resp: RestPlainResponse) {.
noreturn, raises: [RestError, Defect].} =
let error =
block:
let res = decodeBytes(KeymanagerGenericError, resp.data, resp.contentType)
if res.isErr():
let msg = "Incorrect response error format (" & $resp.status &
") [" & $res.error() & "]"
raise newException(RestError, msg)
res.get()
let msg = "Error response (" & $resp.status & ") [" & error.message & "]"
raise newException(RestError, msg)
proc listKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/keystores",
meth: MethodGet.}
@ -49,7 +62,7 @@ proc listKeys*(client: RestClientRef,
raise newException(RestError, $keystoresRes.error)
return keystoresRes.get()
of 401, 403, 500:
raiseGenericError(resp)
raiseKeymanagerGenericError(resp)
else:
raiseUnknownStatusError(resp)
@ -69,6 +82,23 @@ proc deleteRemoteKeysPlain*(body: DeleteKeystoresBody): RestPlainResponse {.
meth: MethodDelete.}
## https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys
proc listFeeRecipientPlain*(pubkey: ValidatorPubKey): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/{pubkey}/feerecipient",
meth: MethodGet.}
## https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/ListFeeRecipient
proc setFeeRecipientPlain*(pubkey: ValidatorPubKey,
body: SetFeeRecipientRequest): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/{pubkey}/feerecipient",
meth: MethodPost.}
## https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/SetFeeRecipient
proc deleteFeeRecipientPlain*(pubkey: ValidatorPubKey,
body: EmptyBody): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/{pubkey}/feerecipient",
meth: MethodDelete.}
## https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/DeleteFeeRecipient
proc listRemoteDistributedKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/remotekeys/distributed",
meth: MethodGet.}
@ -82,7 +112,6 @@ proc deleteRemoteDistributedKeysPlain*(body: DeleteKeystoresBody): RestPlainResp
rest, endpoint: "/eth/v1/remotekeys/distributed",
meth: MethodDelete.}
proc listRemoteKeys*(client: RestClientRef,
token: string): Future[GetRemoteKeystoresResponse] {.
async.} =
@ -91,12 +120,66 @@ proc listRemoteKeys*(client: RestClientRef,
case resp.status:
of 200:
let res = decodeBytes(GetRemoteKeystoresResponse, resp.data,
let res = decodeBytes(GetRemoteKeystoresResponse,
resp.data,
resp.contentType)
if res.isErr():
raise newException(RestError, $res.error())
return res.get()
of 401, 403, 500:
raiseGenericError(resp)
raiseKeymanagerGenericError(resp)
else:
raiseUnknownStatusError(resp)
proc listFeeRecipient*(client: RestClientRef,
pubkey: ValidatorPubKey,
token: string): Future[Eth1Address] {.async.} =
let resp = await client.listFeeRecipientPlain(
pubkey,
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 200:
let res = decodeBytes(DataEnclosedObject[ListFeeRecipientResponse],
resp.data,
resp.contentType)
if res.isErr:
raise newException(RestError, $res.error)
return res.get.data.ethaddress
of 401, 403, 404, 500:
raiseKeymanagerGenericError(resp)
else:
raiseUnknownStatusError(resp)
proc setFeeRecipient*(client: RestClientRef,
pubkey: ValidatorPubKey,
feeRecipient: Eth1Address,
token: string) {.async.} =
let resp = await client.setFeeRecipientPlain(
pubkey,
SetFeeRecipientRequest(ethaddress: feeRecipient),
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 202:
discard
of 400, 401, 403, 404, 500:
raiseKeymanagerGenericError(resp)
else:
raiseUnknownStatusError(resp)
proc deleteFeeRecipient*(client: RestClientRef,
pubkey: ValidatorPubKey,
token: string) {.async.} =
let resp = await client.deleteFeeRecipientPlain(
pubkey,
EmptyBody(),
extraHeaders = @[("Authorization", "Bearer " & token)])
case resp.status:
of 204:
discard
of 401, 403, 404, 500:
raiseKeymanagerGenericError(resp)
else:
raiseUnknownStatusError(resp)

View File

@ -65,6 +65,13 @@ type
DeleteRemoteKeystoresResponse* = object
data*: seq[RemoteKeystoreStatus]
SetFeeRecipientRequest* = object
ethaddress*: Eth1Address
ListFeeRecipientResponse* = object
pubkey*: ValidatorPubKey
ethaddress*: Eth1Address
KeystoreStatus* = enum
error = "error"
notActive = "not_active"
@ -78,6 +85,9 @@ type
missingBearerScheme = "Bearer Authentication is not included in request"
incorrectToken = "Authentication token is incorrect"
KeymanagerGenericError* = object
message*: string
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
for a, b in fields(x, y):
var c = cmp(a, b)

View File

@ -33,9 +33,7 @@ const
KeystoreFileName* = "keystore.json"
RemoteKeystoreFileName* = "remote_keystore.json"
NetKeystoreFileName* = "network_keystore.json"
DisableFileName* = ".disable"
DisableFileContent* = "Please do not remove this file manually. " &
"This can lead to slashing of this validator's key."
FeeRecipientFilename* = "suggested_fee_recipient.hex"
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters.
type
@ -520,6 +518,9 @@ proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
ok(RemoveValidatorStatus.deleted)
func fsName(pubkey: ValidatorPubKey|CookedPubKey): string =
"0x" & pubkey.toHex()
proc removeValidatorFiles*(conf: AnyConf, keyName: string,
kind: KeystoreKind): KmResult[RemoveValidatorStatus]
{.raises: [Defect].} =
@ -534,8 +535,7 @@ proc removeValidator*(pool: var ValidatorPool, conf: AnyConf,
return ok(RemoveValidatorStatus.notFound)
if validator.kind.toKeystoreKind() != kind:
return ok(RemoveValidatorStatus.notFound)
let publicKeyName: string = "0x" & publicKey.toHex()
let res = removeValidatorFiles(conf, publicKeyName, kind)
let res = removeValidatorFiles(conf, publicKey.fsName, kind)
if res.isErr():
return err(res.error())
pool.removeValidator(publicKey)
@ -793,7 +793,7 @@ proc saveKeystore*(rng: var HmacDrbgContext,
mode = Secure): Result[void, KeystoreGenerationError] =
let
keypass = KeystorePass.init(password)
keyName = "0x" & signingPubKey.toHex()
keyName = signingPubKey.fsName
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / KeystoreFileName
@ -832,7 +832,7 @@ proc saveKeystore*(validatorsDir: string,
desc = ""): Result[void, KeystoreGenerationError]
{.raises: [Defect].} =
let
keyName = "0x" & publicKey.toHex()
keyName = publicKey.fsName
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / RemoteKeystoreFileName
keystoreDesc = if len(desc) == 0: none[string]() else: some(desc)
@ -888,7 +888,7 @@ proc importKeystore*(pool: var ValidatorPool, conf: AnyConf,
{.raises: [Defect].} =
let
publicKey = keystore.pubkey
keyName = "0x" & publicKey.toHex()
keyName = publicKey.fsName
validatorsDir = conf.validatorsDir()
keystoreDir = validatorsDir / keyName
keystoreFile = keystoreDir / RemoteKeystoreFileName
@ -933,7 +933,7 @@ proc importKeystore*(pool: var ValidatorPool,
AddValidatorFailure.init(AddValidatorStatus.failed, res.error()))
let
publicKey = privateKey.toPubKey()
keyName = "0x" & publicKey.toHex()
keyName = publicKey.fsName
validatorsDir = conf.validatorsDir()
secretsDir = conf.secretsDir()
secretFile = secretsDir / keyName
@ -987,6 +987,72 @@ proc generateDistirbutedStore*(rng: var HmacDrbgContext,
# actual validator
saveKeystore(remoteValidatorDir, pubKey, signers, threshold)
func validatorKeystoreDir(conf: AnyConf, pubkey: ValidatorPubKey): string =
conf.validatorsDir / pubkey.fsName
func feeRecipientPath*(conf: AnyConf, pubkey: ValidatorPubKey): string =
conf.validatorKeystoreDir(pubkey) / FeeRecipientFilename
proc removeFeeRecipientFile*(conf: AnyConf, pubkey: ValidatorPubKey): Result[void, string] =
let path = conf.feeRecipientPath(pubkey)
if fileExists(path):
let res = io2.removeFile(path)
if res.isErr:
return err res.error.ioErrorMsg
return ok()
proc setFeeRecipient*(conf: AnyConf, pubkey: ValidatorPubKey, feeRecipient: Eth1Address): Result[void, string] =
let validatorKeystoreDir = conf.validatorKeystoreDir(pubkey)
? secureCreatePath(validatorKeystoreDir).mapErr(proc(e: auto): string =
"Could not create wallet directory [" & validatorKeystoreDir & "]: " & $e)
io2.writeFile(validatorKeystoreDir / FeeRecipientFilename, $feeRecipient)
.mapErr(proc(e: auto): string = "Failed to write fee recipient file: " & $e)
func defaultFeeRecipient*(conf: AnyConf): Eth1Address =
if conf.suggestedFeeRecipient.isSome:
conf.suggestedFeeRecipient.get
else:
# https://github.com/nim-lang/Nim/issues/19802
(static(default(Eth1Address)))
type
FeeRecipientStatus* = enum
noSuchValidator
invalidFeeRecipientFile
proc getSuggestedFeeRecipient*(
conf: AnyConf,
pubkey: ValidatorPubKey): Result[Eth1Address, FeeRecipientStatus] =
let validatorDir = conf.validatorKeystoreDir(pubkey)
# In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that's a more urgent matter to fix. Many people
# people might prefer, however, not to override their default suggested fee
# recipients per validator, so don't warn very loudly, if at all.
if not dirExists(validatorDir):
return err noSuchValidator
let feeRecipientPath = validatorDir / FeeRecipientFilename
if not fileExists(feeRecipientPath):
return ok conf.defaultFeeRecipient
try:
# Avoid being overly flexible initially. Trailing whitespace is common
# enough it probably should be allowed, but it is reasonable to simply
# disallow the mostly-pointless flexibility of leading whitespace.
ok Eth1Address.fromHex(strutils.strip(
readFile(feeRecipientPath), leading = false, trailing = true))
except CatchableError as exc:
# Because the nonexistent validator case was already checked, any failure
# at this point is serious enough to alert the user.
warn "getSuggestedFeeRecipient: failed loading fee recipient file; falling back to default fee recipient",
feeRecipientPath,
err = exc.msg
err invalidFeeRecipientFile
proc generateDeposits*(cfg: RuntimeConfig,
rng: var HmacDrbgContext,
seed: KeySeed,

View File

@ -337,44 +337,6 @@ proc get_execution_payload(
asConsensusExecutionPayload(
await execution_engine.getPayload(payload_id.get))
proc getSuggestedFeeRecipient(node: BeaconNode, pubkey: ValidatorPubKey):
Eth1Address =
template defaultSuggestedFeeRecipient(): Eth1Address =
if node.config.suggestedFeeRecipient.isSome:
node.config.suggestedFeeRecipient.get
else:
# https://github.com/nim-lang/Nim/issues/19802
(static(default(Eth1Address)))
const feeRecipientFilename = "suggested_fee_recipient.hex"
let
keyName = "0x" & pubkey.toHex()
feeRecipientPath =
node.config.validatorsDir() / keyName / feeRecipientFilename
# In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that's a more urgent matter to fix. Many people
# people might prefer, however, not to override their default suggested fee
# recipients per validator, so don't warn very loudly, if at all.
if not fileExists(feeRecipientPath):
debug "getSuggestedFeeRecipient: did not find fee recipient file; using default fee recipient",
feeRecipientPath
return defaultSuggestedFeeRecipient()
try:
# Avoid being overly flexible initially. Trailing whitespace is common
# enough it probably should be allowed, but it is reasonable to simply
# disallow the mostly-pointless flexibility of leading whitespace.
Eth1Address.fromHex(strip(
readFile(feeRecipientPath), leading = false, trailing = true))
except CatchableError as exc:
# Because the nonexistent validator case was already checked, any failure
# at this point is serious enough to alert the user.
warn "getSuggestedFeeRecipient: failed loading fee recipient file; falling back to default fee recipient",
feeRecipientPath,
err = exc.msg
defaultSuggestedFeeRecipient()
proc getExecutionPayload(
node: BeaconNode, proposalState: auto, pubkey: ValidatorPubKey):
Future[ExecutionPayload] {.async.} =
@ -410,9 +372,11 @@ proc getExecutionPayload(
terminalBlockHash
latestFinalized =
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
feeRecipient = node.config.getSuggestedFeeRecipient(pubkey).valueOr:
node.config.defaultFeeRecipient
payload_id = (await forkchoice_updated(
proposalState.bellatrixData.data, latestHead, latestFinalized,
node.getSuggestedFeeRecipient(pubkey),
feeRecipient,
node.consensusManager.eth1Monitor))
payload = awaitWithTimeout(
get_execution_payload(payload_id, node.consensusManager.eth1Monitor),

View File

@ -35,6 +35,7 @@ const
tokenFilePath = dataDir / "keymanager-token.txt"
keymanagerPort = 47000
correctTokenValue = "some secret token"
defaultFeeRecipient = Eth1Address.fromHex("0x000000000000000000000000000000000000DEAD")
newPrivateKeys = [
"0x598c9b81749ba7bb8eb37781027359e3ffe87d0e1579e21c453ce22af0c05e35",
"0x14e4470a1d8913ec0602048af78addf0fd7a37f591dd3feda828d10a10c0f6ff",
@ -66,8 +67,16 @@ const
"0xa782e5161ba8e9ac135b0db3203a8c23aa61e19be6b9c198393d8b2b902bad8139863d9cf26bc2cbdc3b747bafc64606",
"0xb33f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7189e"
]
unusedPublicKeys = [
"0xc22f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7232d",
"0x0bbca63e35c7a159fc2f187d300cad9ef5f5e73e55f78c391e7bc2c2feabc2d9d63dfe99edd7058ad0ab9d7f14aade5f"
]
newPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/remote"))
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:
@ -159,6 +168,7 @@ proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} =
"--keymanager-address=127.0.0.1",
"--keymanager-port=" & $keymanagerPort,
"--keymanager-token-file=" & tokenFilePath,
"--suggested-fee-recipient=" & $defaultFeeRecipient,
"--light-client-enable=off",
"--light-client-data-serve=off",
"--light-client-data-import-mode=none",
@ -552,9 +562,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -564,9 +572,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -575,10 +581,8 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
expect RestError:
let keystores = await client.listKeys("Invalid Token")
@ -660,9 +664,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -673,9 +675,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -685,10 +685,8 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
suite "DeleteKeys requests" & preset():
asyncTest "Deleting not existing key" & preset():
@ -710,9 +708,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -723,9 +719,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -735,10 +729,8 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
suite "ListRemoteKeys requests" & preset():
asyncTest "Correct token provided" & preset():
@ -757,9 +749,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -769,9 +759,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -780,14 +768,174 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
expect RestError:
let keystores = await client.listKeys("Invalid Token")
suite "Fee recipient management" & preset():
asyncTest "Missing Authorization header" & preset():
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" & preset():
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" & preset():
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" & preset():
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" & preset():
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" & preset():
let
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
resultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)
check:
resultFromApi == defaultFeeRecipient
asyncTest "Configuring the fee recpient" & preset():
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" & preset():
asyncTest "Importing list of remote keys" & preset():
let
@ -866,9 +1014,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -879,9 +1025,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -891,10 +1035,8 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
suite "DeleteRemoteKeys requests" & preset():
asyncTest "Deleting not existing key" & preset():
@ -949,9 +1091,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Header" & preset():
let
@ -962,9 +1102,7 @@ proc runTests {.async.} =
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
responseJson["message"].getStr() == InvalidAuthorizationError
asyncTest "Invalid Authorization Token" & preset():
let
@ -974,10 +1112,8 @@ proc runTests {.async.} =
responseJson = Json.decode(response.data, JsonNode)
check:
response.status == 401
responseJson["code"].getStr() == "401"
responseJson["message"].getStr() == InvalidAuthorization
responseJson["stacktraces"][0].getStr() == $incorrectToken
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError
bnStatus = BeaconNodeStatus.Stopping