[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:
parent
456b5ebc48
commit
806536a040
|
@ -168,6 +168,17 @@ OK: 3/3 Fail: 0/3 Skip: 0/3
|
||||||
+ addExitMessage/getVoluntaryExitMessage OK
|
+ addExitMessage/getVoluntaryExitMessage OK
|
||||||
```
|
```
|
||||||
OK: 3/3 Fail: 0/3 Skip: 0/3
|
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]
|
## FinalizedBlocks [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Basic ops [Preset: mainnet] OK
|
+ 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
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 314/319 Fail: 0/319 Skip: 5/319
|
OK: 321/326 Fail: 0/326 Skip: 5/326
|
||||||
|
|
|
@ -345,7 +345,6 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
let vindex =
|
let vindex =
|
||||||
block:
|
block:
|
||||||
let vid = validator_id.get()
|
|
||||||
case vid.kind
|
case vid.kind
|
||||||
of ValidatorQueryKind.Key:
|
of ValidatorQueryKind.Key:
|
||||||
let optIndices = keysToIndices(node.restKeysCache, state, [vid.key])
|
let optIndices = keysToIndices(node.restKeysCache, state, [vid.key])
|
||||||
|
|
|
@ -50,6 +50,8 @@ const
|
||||||
"Proposer slashing object was broadcasted"
|
"Proposer slashing object was broadcasted"
|
||||||
InvalidVoluntaryExitObjectError* =
|
InvalidVoluntaryExitObjectError* =
|
||||||
"Unable to decode voluntary exit object(s)"
|
"Unable to decode voluntary exit object(s)"
|
||||||
|
InvalidFeeRecipientRequestError* =
|
||||||
|
"Bad request. Request was malformed and could not be processed"
|
||||||
VoluntaryExitValidationError* =
|
VoluntaryExitValidationError* =
|
||||||
"Invalid voluntary exit, it will never pass validation so it's rejected"
|
"Invalid voluntary exit, it will never pass validation so it's rejected"
|
||||||
VoluntaryExitValidationSuccess* =
|
VoluntaryExitValidationSuccess* =
|
||||||
|
@ -195,7 +197,7 @@ const
|
||||||
"Invalid validator's public key(s) found"
|
"Invalid validator's public key(s) found"
|
||||||
BadRequestFormatError* =
|
BadRequestFormatError* =
|
||||||
"Bad request format"
|
"Bad request format"
|
||||||
InvalidAuthorization* =
|
InvalidAuthorizationError* =
|
||||||
"Invalid Authorization Header"
|
"Invalid Authorization Header"
|
||||||
PrunedStateError* =
|
PrunedStateError* =
|
||||||
"Trying to access a pruned historical state"
|
"Trying to access a pruned historical state"
|
||||||
|
|
|
@ -50,6 +50,24 @@ proc listRemoteDistributedValidators*(node: BeaconNode): seq[DistributedKeystore
|
||||||
)
|
)
|
||||||
validators
|
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,
|
proc checkAuthorization*(request: HttpRequestRef,
|
||||||
node: BeaconNode): Result[void, AuthorizationError] =
|
node: BeaconNode): Result[void, AuthorizationError] =
|
||||||
let authorizations = request.headers.getList("authorization")
|
let authorizations = request.headers.getList("authorization")
|
||||||
|
@ -65,6 +83,15 @@ proc checkAuthorization*(request: HttpRequestRef,
|
||||||
else:
|
else:
|
||||||
return err noAuthorizationHeader
|
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] =
|
proc validateUri*(url: string): Result[Uri, cstring] =
|
||||||
let surl = parseUri(url)
|
let surl = parseUri(url)
|
||||||
if surl.scheme notin ["http", "https"]:
|
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:
|
router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let response = GetKeystoresResponse(data: listLocalValidators(node))
|
let response = GetKeystoresResponse(data: listLocalValidators(node))
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
return RestApiResponse.jsonResponsePlain(response)
|
||||||
|
|
||||||
|
@ -116,16 +142,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let request =
|
let request =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(KeystoresAndSlashingProtection, contentBody.get())
|
let dres = decodeBody(KeystoresAndSlashingProtection, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
|
return keymanagerApiError(Http400, InvalidKeystoreObjects)
|
||||||
$dres.error())
|
|
||||||
dres.get()
|
dres.get()
|
||||||
|
|
||||||
if request.slashing_protection.isSome():
|
if request.slashing_protection.isSome():
|
||||||
|
@ -133,13 +157,13 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
|
let nodeSPDIR = toSPDIR(node.attachedValidators.slashingProtection)
|
||||||
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
|
if nodeSPDIR.metadata.genesis_validators_root.Eth2Digest !=
|
||||||
slashing_protection.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 " &
|
"The slashing protection database and imported file refer to " &
|
||||||
"different blockchains.")
|
"different blockchains.")
|
||||||
let res = inclSPDIR(node.attachedValidators.slashingProtection,
|
let res = inclSPDIR(node.attachedValidators.slashingProtection,
|
||||||
slashing_protection)
|
slashing_protection)
|
||||||
if res == siFailure:
|
if res == siFailure:
|
||||||
return RestApiResponse.jsonError(Http500,
|
return keymanagerApiError(Http500,
|
||||||
"Internal server error; Failed to import slashing protection data")
|
"Internal server error; Failed to import slashing protection data")
|
||||||
|
|
||||||
var response: PostKeystoresResponse
|
var response: PostKeystoresResponse
|
||||||
|
@ -169,16 +193,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let keys =
|
let keys =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||||
$dres.error())
|
|
||||||
dres.get().pubkeys
|
dres.get().pubkeys
|
||||||
|
|
||||||
var
|
var
|
||||||
|
@ -230,8 +252,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
router.api(MethodGet, "/api/eth/v1/remotekeys") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/remotekeys") do () -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let response = GetRemoteKeystoresResponse(data: listRemoteValidators(node))
|
let response = GetRemoteKeystoresResponse(data: listRemoteValidators(node))
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
return RestApiResponse.jsonResponsePlain(response)
|
||||||
|
|
||||||
|
@ -240,16 +261,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let keys =
|
let keys =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(ImportRemoteKeystoresBody, contentBody.get())
|
let dres = decodeBody(ImportRemoteKeystoresBody, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
|
return keymanagerApiError(Http400, InvalidKeystoreObjects)
|
||||||
$dres.error())
|
|
||||||
dres.get().remote_keys
|
dres.get().remote_keys
|
||||||
|
|
||||||
var response: PostKeystoresResponse
|
var response: PostKeystoresResponse
|
||||||
|
@ -274,16 +293,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let keys =
|
let keys =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||||
$dres.error())
|
|
||||||
dres.get().pubkeys
|
dres.get().pubkeys
|
||||||
|
|
||||||
var response: DeleteRemoteKeystoresResponse
|
var response: DeleteRemoteKeystoresResponse
|
||||||
|
@ -292,13 +309,78 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
response.data.add(status)
|
response.data.add(status)
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
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
|
# TODO: These URLs will be changed once we submit a proposal for
|
||||||
# /api/eth/v2/remotekeys that supports distributed keys.
|
# /api/eth/v2/remotekeys that supports distributed keys.
|
||||||
router.api(MethodGet, "/api/eth/v1/remotekeys/distributed") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/remotekeys/distributed") do () -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let response = GetDistributedKeystoresResponse(data: listRemoteDistributedValidators(node))
|
let response = GetDistributedKeystoresResponse(data: listRemoteDistributedValidators(node))
|
||||||
return RestApiResponse.jsonResponsePlain(response)
|
return RestApiResponse.jsonResponsePlain(response)
|
||||||
|
|
||||||
|
@ -308,16 +390,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let keys =
|
let keys =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(ImportDistributedKeystoresBody, contentBody.get())
|
let dres = decodeBody(ImportDistributedKeystoresBody, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
|
return keymanagerApiError(Http400, InvalidKeystoreObjects)
|
||||||
$dres.error())
|
|
||||||
dres.get.remote_keys
|
dres.get.remote_keys
|
||||||
|
|
||||||
var response: PostKeystoresResponse
|
var response: PostKeystoresResponse
|
||||||
|
@ -339,16 +419,14 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let authStatus = checkAuthorization(request, node)
|
let authStatus = checkAuthorization(request, node)
|
||||||
if authStatus.isErr():
|
if authStatus.isErr():
|
||||||
return RestApiResponse.jsonError(Http401, InvalidAuthorization,
|
return authErrorResponse authStatus.error
|
||||||
$authStatus.error())
|
|
||||||
let keys =
|
let keys =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
return keymanagerApiError(Http404, EmptyRequestBodyError)
|
||||||
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
let dres = decodeBody(DeleteKeystoresBody, contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||||
$dres.error())
|
|
||||||
dres.get.pubkeys
|
dres.get.pubkeys
|
||||||
|
|
||||||
var response: DeleteRemoteKeystoresResponse
|
var response: DeleteRemoteKeystoresResponse
|
||||||
|
@ -388,6 +466,21 @@ proc installKeymanagerHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/eth/v1/remotekeys",
|
"/eth/v1/remotekeys",
|
||||||
"/api/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(
|
router.redirect(
|
||||||
MethodGet,
|
MethodGet,
|
||||||
"/eth/v1/remotekeys/distributed",
|
"/eth/v1/remotekeys/distributed",
|
||||||
|
|
|
@ -48,6 +48,8 @@ proc validate(key: string, value: string): int =
|
||||||
0
|
0
|
||||||
of "{block_root}":
|
of "{block_root}":
|
||||||
0
|
0
|
||||||
|
of "{pubkey}":
|
||||||
|
int(value.len != 98)
|
||||||
else:
|
else:
|
||||||
1
|
1
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,8 @@ const
|
||||||
[byte('a'), byte('l'), byte('t'), byte('a'), byte('i'), byte('r')]
|
[byte('a'), byte('l'), byte('t'), byte('a'), byte('i'), byte('r')]
|
||||||
|
|
||||||
type
|
type
|
||||||
|
EmptyBody* = object
|
||||||
|
|
||||||
RestGenericError* = object
|
RestGenericError* = object
|
||||||
code*: uint64
|
code*: uint64
|
||||||
message*: string
|
message*: string
|
||||||
|
@ -81,29 +83,31 @@ type
|
||||||
|
|
||||||
EncodeTypes* =
|
EncodeTypes* =
|
||||||
AttesterSlashing |
|
AttesterSlashing |
|
||||||
|
DeleteKeystoresBody |
|
||||||
|
EmptyBody |
|
||||||
|
ImportDistributedKeystoresBody |
|
||||||
|
ImportRemoteKeystoresBody |
|
||||||
|
KeystoresAndSlashingProtection |
|
||||||
ProposerSlashing |
|
ProposerSlashing |
|
||||||
phase0.SignedBeaconBlock |
|
SetFeeRecipientRequest |
|
||||||
altair.SignedBeaconBlock |
|
|
||||||
bellatrix.SignedBeaconBlock |
|
|
||||||
SignedBlindedBeaconBlock |
|
SignedBlindedBeaconBlock |
|
||||||
SignedValidatorRegistrationV1 |
|
SignedValidatorRegistrationV1 |
|
||||||
SignedVoluntaryExit |
|
SignedVoluntaryExit |
|
||||||
Web3SignerRequest |
|
Web3SignerRequest |
|
||||||
KeystoresAndSlashingProtection |
|
altair.SignedBeaconBlock |
|
||||||
DeleteKeystoresBody |
|
bellatrix.SignedBeaconBlock |
|
||||||
ImportRemoteKeystoresBody |
|
phase0.SignedBeaconBlock
|
||||||
ImportDistributedKeystoresBody
|
|
||||||
|
|
||||||
EncodeArrays* =
|
EncodeArrays* =
|
||||||
seq[ValidatorIndex] |
|
|
||||||
seq[Attestation] |
|
seq[Attestation] |
|
||||||
|
seq[RemoteKeystoreInfo] |
|
||||||
|
seq[RestCommitteeSubscription] |
|
||||||
|
seq[RestSignedContributionAndProof] |
|
||||||
|
seq[RestSyncCommitteeMessage] |
|
||||||
|
seq[RestSyncCommitteeSubscription] |
|
||||||
seq[SignedAggregateAndProof] |
|
seq[SignedAggregateAndProof] |
|
||||||
seq[SignedValidatorRegistrationV1] |
|
seq[SignedValidatorRegistrationV1] |
|
||||||
seq[RestCommitteeSubscription] |
|
seq[ValidatorIndex]
|
||||||
seq[RestSyncCommitteeSubscription] |
|
|
||||||
seq[RestSyncCommitteeMessage] |
|
|
||||||
seq[RestSignedContributionAndProof] |
|
|
||||||
seq[RemoteKeystoreInfo]
|
|
||||||
|
|
||||||
DecodeTypes* =
|
DecodeTypes* =
|
||||||
DataEnclosedObject |
|
DataEnclosedObject |
|
||||||
|
@ -111,20 +115,22 @@ type
|
||||||
DataRootEnclosedObject |
|
DataRootEnclosedObject |
|
||||||
DataVersionEnclosedObject |
|
DataVersionEnclosedObject |
|
||||||
GetBlockV2Response |
|
GetBlockV2Response |
|
||||||
|
GetDistributedKeystoresResponse |
|
||||||
GetKeystoresResponse |
|
GetKeystoresResponse |
|
||||||
GetRemoteKeystoresResponse |
|
GetRemoteKeystoresResponse |
|
||||||
GetDistributedKeystoresResponse |
|
|
||||||
GetStateV2Response |
|
|
||||||
GetStateForkResponse |
|
GetStateForkResponse |
|
||||||
|
GetStateV2Response |
|
||||||
|
KeymanagerGenericError |
|
||||||
|
KeystoresAndSlashingProtection |
|
||||||
|
ListFeeRecipientResponse |
|
||||||
ProduceBlockResponseV2 |
|
ProduceBlockResponseV2 |
|
||||||
RestDutyError |
|
RestDutyError |
|
||||||
RestValidator |
|
|
||||||
RestGenericError |
|
RestGenericError |
|
||||||
|
RestValidator |
|
||||||
Web3SignerErrorResponse |
|
Web3SignerErrorResponse |
|
||||||
Web3SignerKeysResponse |
|
Web3SignerKeysResponse |
|
||||||
Web3SignerSignatureResponse |
|
Web3SignerSignatureResponse |
|
||||||
Web3SignerStatusResponse |
|
Web3SignerStatusResponse
|
||||||
KeystoresAndSlashingProtection
|
|
||||||
|
|
||||||
SszDecodeTypes* =
|
SszDecodeTypes* =
|
||||||
GetPhase0StateSszResponse |
|
GetPhase0StateSszResponse |
|
||||||
|
@ -470,11 +476,10 @@ template hexOriginal(data: openArray[byte]): string =
|
||||||
to0xHex(data)
|
to0xHex(data)
|
||||||
|
|
||||||
proc decodeJsonString*[T](t: typedesc[T],
|
proc decodeJsonString*[T](t: typedesc[T],
|
||||||
data: JsonString,
|
data: JsonString): Result[T, cstring] =
|
||||||
requireAllFields = true): Result[T, cstring] =
|
|
||||||
try:
|
try:
|
||||||
ok(RestJson.decode(string(data), T,
|
ok(RestJson.decode(string(data), T,
|
||||||
requireAllFields = requireAllFields,
|
requireAllFields = true,
|
||||||
allowUnknownFields = true))
|
allowUnknownFields = true))
|
||||||
except SerializationError:
|
except SerializationError:
|
||||||
err("Unable to deserialize data")
|
err("Unable to deserialize data")
|
||||||
|
@ -1558,8 +1563,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(Web3SignerAggregationSlotData,
|
let res = decodeJsonString(Web3SignerAggregationSlotData, data.get())
|
||||||
data.get(), true)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `aggregation_slot` format")
|
"Incorrect field `aggregation_slot` format")
|
||||||
|
@ -1574,7 +1578,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(AggregateAndProof, data.get(), true)
|
let res = decodeJsonString(AggregateAndProof, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `aggregate_and_proof` format")
|
"Incorrect field `aggregate_and_proof` format")
|
||||||
|
@ -1590,7 +1594,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(AttestationData, data.get(), true)
|
let res = decodeJsonString(AttestationData, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `attestation` format")
|
"Incorrect field `attestation` format")
|
||||||
|
@ -1606,7 +1610,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(phase0.BeaconBlock, data.get(), true)
|
let res = decodeJsonString(phase0.BeaconBlock, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `block` format")
|
"Incorrect field `block` format")
|
||||||
|
@ -1622,7 +1626,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(Web3SignerForkedBeaconBlock, data.get(), true)
|
let res = decodeJsonString(Web3SignerForkedBeaconBlock, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `beacon_block` format")
|
"Incorrect field `beacon_block` format")
|
||||||
|
@ -1636,7 +1640,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `deposit` is missing")
|
reader.raiseUnexpectedValue("Field `deposit` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(Web3SignerDepositData, data.get(), true)
|
let res = decodeJsonString(Web3SignerDepositData, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `deposit` format")
|
"Incorrect field `deposit` format")
|
||||||
|
@ -1652,8 +1656,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(Web3SignerRandaoRevealData, data.get(),
|
let res = decodeJsonString(Web3SignerRandaoRevealData, data.get())
|
||||||
true)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `randao_reveal` format")
|
"Incorrect field `randao_reveal` format")
|
||||||
|
@ -1669,7 +1672,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(VoluntaryExit, data.get(), true)
|
let res = decodeJsonString(VoluntaryExit, data.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `voluntary_exit` format")
|
"Incorrect field `voluntary_exit` format")
|
||||||
|
@ -1686,8 +1689,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(Web3SignerSyncCommitteeMessageData,
|
let res = decodeJsonString(Web3SignerSyncCommitteeMessageData, data.get())
|
||||||
data.get(), true)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `sync_committee_message` format")
|
"Incorrect field `sync_committee_message` format")
|
||||||
|
@ -1705,8 +1707,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(SyncAggregatorSelectionData,
|
let res = decodeJsonString(SyncAggregatorSelectionData, data.get())
|
||||||
data.get(), true)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `sync_aggregator_selection_data` format")
|
"Incorrect field `sync_aggregator_selection_data` format")
|
||||||
|
@ -1724,8 +1725,7 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
let res = decodeJsonString(ContributionAndProof,
|
let res = decodeJsonString(ContributionAndProof, data.get())
|
||||||
data.get(), true)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
reader.raiseUnexpectedValue(
|
reader.raiseUnexpectedValue(
|
||||||
"Incorrect field `contribution_and_proof` format")
|
"Incorrect field `contribution_and_proof` format")
|
||||||
|
@ -2068,7 +2068,10 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
for item in strKeystores:
|
for item in strKeystores:
|
||||||
let key =
|
let key =
|
||||||
try:
|
try:
|
||||||
RestJson.decode(item, Keystore, allowUnknownFields = true)
|
RestJson.decode(item,
|
||||||
|
Keystore,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true)
|
||||||
except SerializationError as exc:
|
except SerializationError as exc:
|
||||||
# TODO re-raise the exception by adjusting the column index, so the user
|
# TODO re-raise the exception by adjusting the column index, so the user
|
||||||
# will get an accurate syntax error within the larger message
|
# will get an accurate syntax error within the larger message
|
||||||
|
@ -2080,7 +2083,10 @@ proc readValue*(reader: var JsonReader[RestJson],
|
||||||
if strSlashing.isSome():
|
if strSlashing.isSome():
|
||||||
let db =
|
let db =
|
||||||
try:
|
try:
|
||||||
RestJson.decode(strSlashing.get(), SPDIR, allowUnknownFields = true)
|
RestJson.decode(strSlashing.get(),
|
||||||
|
SPDIR,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true)
|
||||||
except SerializationError as exc:
|
except SerializationError as exc:
|
||||||
reader.raiseUnexpectedValue("Invalid slashing protection format")
|
reader.raiseUnexpectedValue("Invalid slashing protection format")
|
||||||
some(db)
|
some(db)
|
||||||
|
@ -2172,8 +2178,12 @@ proc decodeBody*[T](t: typedesc[T],
|
||||||
return err("Unsupported content type")
|
return err("Unsupported content type")
|
||||||
let data =
|
let data =
|
||||||
try:
|
try:
|
||||||
RestJson.decode(body.data, T, allowUnknownFields = true)
|
RestJson.decode(body.data, T,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true)
|
||||||
except SerializationError as exc:
|
except SerializationError as exc:
|
||||||
|
debug "Failed to deserialize REST JSON data",
|
||||||
|
err = exc.formatMsg("<data>")
|
||||||
return err("Unable to deserialize data")
|
return err("Unable to deserialize data")
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return err("Unexpected deserialization error")
|
return err("Unexpected deserialization error")
|
||||||
|
@ -2222,8 +2232,12 @@ proc decodeBytes*[T: DecodeTypes](t: typedesc[T], value: openArray[byte],
|
||||||
case contentType
|
case contentType
|
||||||
of "application/json":
|
of "application/json":
|
||||||
try:
|
try:
|
||||||
ok RestJson.decode(value, T, allowUnknownFields = true)
|
ok RestJson.decode(value, T,
|
||||||
except SerializationError:
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true)
|
||||||
|
except SerializationError as exc:
|
||||||
|
debug "Failed to deserialize REST JSON data",
|
||||||
|
err = exc.formatMsg("<data>")
|
||||||
err("Serialization error")
|
err("Serialization error")
|
||||||
else:
|
else:
|
||||||
err("Content-Type not supported")
|
err("Content-Type not supported")
|
||||||
|
|
|
@ -20,6 +20,19 @@ UUID.serializesAsBaseIn RestJson
|
||||||
KeyPath.serializesAsBaseIn RestJson
|
KeyPath.serializesAsBaseIn RestJson
|
||||||
WalletName.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 {.
|
proc listKeysPlain*(): RestPlainResponse {.
|
||||||
rest, endpoint: "/eth/v1/keystores",
|
rest, endpoint: "/eth/v1/keystores",
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
|
@ -49,7 +62,7 @@ proc listKeys*(client: RestClientRef,
|
||||||
raise newException(RestError, $keystoresRes.error)
|
raise newException(RestError, $keystoresRes.error)
|
||||||
return keystoresRes.get()
|
return keystoresRes.get()
|
||||||
of 401, 403, 500:
|
of 401, 403, 500:
|
||||||
raiseGenericError(resp)
|
raiseKeymanagerGenericError(resp)
|
||||||
else:
|
else:
|
||||||
raiseUnknownStatusError(resp)
|
raiseUnknownStatusError(resp)
|
||||||
|
|
||||||
|
@ -69,6 +82,23 @@ proc deleteRemoteKeysPlain*(body: DeleteKeystoresBody): RestPlainResponse {.
|
||||||
meth: MethodDelete.}
|
meth: MethodDelete.}
|
||||||
## https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys
|
## 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 {.
|
proc listRemoteDistributedKeysPlain*(): RestPlainResponse {.
|
||||||
rest, endpoint: "/eth/v1/remotekeys/distributed",
|
rest, endpoint: "/eth/v1/remotekeys/distributed",
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
|
@ -82,7 +112,6 @@ proc deleteRemoteDistributedKeysPlain*(body: DeleteKeystoresBody): RestPlainResp
|
||||||
rest, endpoint: "/eth/v1/remotekeys/distributed",
|
rest, endpoint: "/eth/v1/remotekeys/distributed",
|
||||||
meth: MethodDelete.}
|
meth: MethodDelete.}
|
||||||
|
|
||||||
|
|
||||||
proc listRemoteKeys*(client: RestClientRef,
|
proc listRemoteKeys*(client: RestClientRef,
|
||||||
token: string): Future[GetRemoteKeystoresResponse] {.
|
token: string): Future[GetRemoteKeystoresResponse] {.
|
||||||
async.} =
|
async.} =
|
||||||
|
@ -91,12 +120,66 @@ proc listRemoteKeys*(client: RestClientRef,
|
||||||
|
|
||||||
case resp.status:
|
case resp.status:
|
||||||
of 200:
|
of 200:
|
||||||
let res = decodeBytes(GetRemoteKeystoresResponse, resp.data,
|
let res = decodeBytes(GetRemoteKeystoresResponse,
|
||||||
|
resp.data,
|
||||||
resp.contentType)
|
resp.contentType)
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
raise newException(RestError, $res.error())
|
raise newException(RestError, $res.error())
|
||||||
return res.get()
|
return res.get()
|
||||||
of 401, 403, 500:
|
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:
|
else:
|
||||||
raiseUnknownStatusError(resp)
|
raiseUnknownStatusError(resp)
|
||||||
|
|
|
@ -65,6 +65,13 @@ type
|
||||||
DeleteRemoteKeystoresResponse* = object
|
DeleteRemoteKeystoresResponse* = object
|
||||||
data*: seq[RemoteKeystoreStatus]
|
data*: seq[RemoteKeystoreStatus]
|
||||||
|
|
||||||
|
SetFeeRecipientRequest* = object
|
||||||
|
ethaddress*: Eth1Address
|
||||||
|
|
||||||
|
ListFeeRecipientResponse* = object
|
||||||
|
pubkey*: ValidatorPubKey
|
||||||
|
ethaddress*: Eth1Address
|
||||||
|
|
||||||
KeystoreStatus* = enum
|
KeystoreStatus* = enum
|
||||||
error = "error"
|
error = "error"
|
||||||
notActive = "not_active"
|
notActive = "not_active"
|
||||||
|
@ -78,6 +85,9 @@ type
|
||||||
missingBearerScheme = "Bearer Authentication is not included in request"
|
missingBearerScheme = "Bearer Authentication is not included in request"
|
||||||
incorrectToken = "Authentication token is incorrect"
|
incorrectToken = "Authentication token is incorrect"
|
||||||
|
|
||||||
|
KeymanagerGenericError* = object
|
||||||
|
message*: string
|
||||||
|
|
||||||
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
|
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
|
||||||
for a, b in fields(x, y):
|
for a, b in fields(x, y):
|
||||||
var c = cmp(a, b)
|
var c = cmp(a, b)
|
||||||
|
|
|
@ -33,9 +33,7 @@ const
|
||||||
KeystoreFileName* = "keystore.json"
|
KeystoreFileName* = "keystore.json"
|
||||||
RemoteKeystoreFileName* = "remote_keystore.json"
|
RemoteKeystoreFileName* = "remote_keystore.json"
|
||||||
NetKeystoreFileName* = "network_keystore.json"
|
NetKeystoreFileName* = "network_keystore.json"
|
||||||
DisableFileName* = ".disable"
|
FeeRecipientFilename* = "suggested_fee_recipient.hex"
|
||||||
DisableFileContent* = "Please do not remove this file manually. " &
|
|
||||||
"This can lead to slashing of this validator's key."
|
|
||||||
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters.
|
KeyNameSize* = 98 # 0x + hexadecimal key representation 96 characters.
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -520,6 +518,9 @@ proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
|
||||||
|
|
||||||
ok(RemoveValidatorStatus.deleted)
|
ok(RemoveValidatorStatus.deleted)
|
||||||
|
|
||||||
|
func fsName(pubkey: ValidatorPubKey|CookedPubKey): string =
|
||||||
|
"0x" & pubkey.toHex()
|
||||||
|
|
||||||
proc removeValidatorFiles*(conf: AnyConf, keyName: string,
|
proc removeValidatorFiles*(conf: AnyConf, keyName: string,
|
||||||
kind: KeystoreKind): KmResult[RemoveValidatorStatus]
|
kind: KeystoreKind): KmResult[RemoveValidatorStatus]
|
||||||
{.raises: [Defect].} =
|
{.raises: [Defect].} =
|
||||||
|
@ -534,8 +535,7 @@ proc removeValidator*(pool: var ValidatorPool, conf: AnyConf,
|
||||||
return ok(RemoveValidatorStatus.notFound)
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
if validator.kind.toKeystoreKind() != kind:
|
if validator.kind.toKeystoreKind() != kind:
|
||||||
return ok(RemoveValidatorStatus.notFound)
|
return ok(RemoveValidatorStatus.notFound)
|
||||||
let publicKeyName: string = "0x" & publicKey.toHex()
|
let res = removeValidatorFiles(conf, publicKey.fsName, kind)
|
||||||
let res = removeValidatorFiles(conf, publicKeyName, kind)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return err(res.error())
|
return err(res.error())
|
||||||
pool.removeValidator(publicKey)
|
pool.removeValidator(publicKey)
|
||||||
|
@ -793,7 +793,7 @@ proc saveKeystore*(rng: var HmacDrbgContext,
|
||||||
mode = Secure): Result[void, KeystoreGenerationError] =
|
mode = Secure): Result[void, KeystoreGenerationError] =
|
||||||
let
|
let
|
||||||
keypass = KeystorePass.init(password)
|
keypass = KeystorePass.init(password)
|
||||||
keyName = "0x" & signingPubKey.toHex()
|
keyName = signingPubKey.fsName
|
||||||
keystoreDir = validatorsDir / keyName
|
keystoreDir = validatorsDir / keyName
|
||||||
keystoreFile = keystoreDir / KeystoreFileName
|
keystoreFile = keystoreDir / KeystoreFileName
|
||||||
|
|
||||||
|
@ -832,7 +832,7 @@ proc saveKeystore*(validatorsDir: string,
|
||||||
desc = ""): Result[void, KeystoreGenerationError]
|
desc = ""): Result[void, KeystoreGenerationError]
|
||||||
{.raises: [Defect].} =
|
{.raises: [Defect].} =
|
||||||
let
|
let
|
||||||
keyName = "0x" & publicKey.toHex()
|
keyName = publicKey.fsName
|
||||||
keystoreDir = validatorsDir / keyName
|
keystoreDir = validatorsDir / keyName
|
||||||
keystoreFile = keystoreDir / RemoteKeystoreFileName
|
keystoreFile = keystoreDir / RemoteKeystoreFileName
|
||||||
keystoreDesc = if len(desc) == 0: none[string]() else: some(desc)
|
keystoreDesc = if len(desc) == 0: none[string]() else: some(desc)
|
||||||
|
@ -888,7 +888,7 @@ proc importKeystore*(pool: var ValidatorPool, conf: AnyConf,
|
||||||
{.raises: [Defect].} =
|
{.raises: [Defect].} =
|
||||||
let
|
let
|
||||||
publicKey = keystore.pubkey
|
publicKey = keystore.pubkey
|
||||||
keyName = "0x" & publicKey.toHex()
|
keyName = publicKey.fsName
|
||||||
validatorsDir = conf.validatorsDir()
|
validatorsDir = conf.validatorsDir()
|
||||||
keystoreDir = validatorsDir / keyName
|
keystoreDir = validatorsDir / keyName
|
||||||
keystoreFile = keystoreDir / RemoteKeystoreFileName
|
keystoreFile = keystoreDir / RemoteKeystoreFileName
|
||||||
|
@ -933,7 +933,7 @@ proc importKeystore*(pool: var ValidatorPool,
|
||||||
AddValidatorFailure.init(AddValidatorStatus.failed, res.error()))
|
AddValidatorFailure.init(AddValidatorStatus.failed, res.error()))
|
||||||
let
|
let
|
||||||
publicKey = privateKey.toPubKey()
|
publicKey = privateKey.toPubKey()
|
||||||
keyName = "0x" & publicKey.toHex()
|
keyName = publicKey.fsName
|
||||||
validatorsDir = conf.validatorsDir()
|
validatorsDir = conf.validatorsDir()
|
||||||
secretsDir = conf.secretsDir()
|
secretsDir = conf.secretsDir()
|
||||||
secretFile = secretsDir / keyName
|
secretFile = secretsDir / keyName
|
||||||
|
@ -987,6 +987,72 @@ proc generateDistirbutedStore*(rng: var HmacDrbgContext,
|
||||||
# actual validator
|
# actual validator
|
||||||
saveKeystore(remoteValidatorDir, pubKey, signers, threshold)
|
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,
|
proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
rng: var HmacDrbgContext,
|
rng: var HmacDrbgContext,
|
||||||
seed: KeySeed,
|
seed: KeySeed,
|
||||||
|
|
|
@ -337,44 +337,6 @@ proc get_execution_payload(
|
||||||
asConsensusExecutionPayload(
|
asConsensusExecutionPayload(
|
||||||
await execution_engine.getPayload(payload_id.get))
|
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(
|
proc getExecutionPayload(
|
||||||
node: BeaconNode, proposalState: auto, pubkey: ValidatorPubKey):
|
node: BeaconNode, proposalState: auto, pubkey: ValidatorPubKey):
|
||||||
Future[ExecutionPayload] {.async.} =
|
Future[ExecutionPayload] {.async.} =
|
||||||
|
@ -410,9 +372,11 @@ proc getExecutionPayload(
|
||||||
terminalBlockHash
|
terminalBlockHash
|
||||||
latestFinalized =
|
latestFinalized =
|
||||||
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
|
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
|
||||||
|
feeRecipient = node.config.getSuggestedFeeRecipient(pubkey).valueOr:
|
||||||
|
node.config.defaultFeeRecipient
|
||||||
payload_id = (await forkchoice_updated(
|
payload_id = (await forkchoice_updated(
|
||||||
proposalState.bellatrixData.data, latestHead, latestFinalized,
|
proposalState.bellatrixData.data, latestHead, latestFinalized,
|
||||||
node.getSuggestedFeeRecipient(pubkey),
|
feeRecipient,
|
||||||
node.consensusManager.eth1Monitor))
|
node.consensusManager.eth1Monitor))
|
||||||
payload = awaitWithTimeout(
|
payload = awaitWithTimeout(
|
||||||
get_execution_payload(payload_id, node.consensusManager.eth1Monitor),
|
get_execution_payload(payload_id, node.consensusManager.eth1Monitor),
|
||||||
|
|
|
@ -35,6 +35,7 @@ const
|
||||||
tokenFilePath = dataDir / "keymanager-token.txt"
|
tokenFilePath = dataDir / "keymanager-token.txt"
|
||||||
keymanagerPort = 47000
|
keymanagerPort = 47000
|
||||||
correctTokenValue = "some secret token"
|
correctTokenValue = "some secret token"
|
||||||
|
defaultFeeRecipient = Eth1Address.fromHex("0x000000000000000000000000000000000000DEAD")
|
||||||
newPrivateKeys = [
|
newPrivateKeys = [
|
||||||
"0x598c9b81749ba7bb8eb37781027359e3ffe87d0e1579e21c453ce22af0c05e35",
|
"0x598c9b81749ba7bb8eb37781027359e3ffe87d0e1579e21c453ce22af0c05e35",
|
||||||
"0x14e4470a1d8913ec0602048af78addf0fd7a37f591dd3feda828d10a10c0f6ff",
|
"0x14e4470a1d8913ec0602048af78addf0fd7a37f591dd3feda828d10a10c0f6ff",
|
||||||
|
@ -66,8 +67,16 @@ const
|
||||||
"0xa782e5161ba8e9ac135b0db3203a8c23aa61e19be6b9c198393d8b2b902bad8139863d9cf26bc2cbdc3b747bafc64606",
|
"0xa782e5161ba8e9ac135b0db3203a8c23aa61e19be6b9c198393d8b2b902bad8139863d9cf26bc2cbdc3b747bafc64606",
|
||||||
"0xb33f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7189e"
|
"0xb33f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7189e"
|
||||||
]
|
]
|
||||||
|
unusedPublicKeys = [
|
||||||
|
"0xc22f17216dda29dba1a9257e75b3dd8446c9ea217b563c20950c43f64300f7bd3d5f0dfa02274cab988e594552b7232d",
|
||||||
|
"0x0bbca63e35c7a159fc2f187d300cad9ef5f5e73e55f78c391e7bc2c2feabc2d9d63dfe99edd7058ad0ab9d7f14aade5f"
|
||||||
|
]
|
||||||
|
|
||||||
newPublicKeysUrl = HttpHostUri(parseUri("http://127.0.0.1/remote"))
|
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 =
|
proc contains*(keylist: openArray[KeystoreInfo], key: ValidatorPubKey): bool =
|
||||||
for item in keylist:
|
for item in keylist:
|
||||||
if item.validating_pubkey == key:
|
if item.validating_pubkey == key:
|
||||||
|
@ -159,6 +168,7 @@ proc startSingleNodeNetwork {.raises: [CatchableError, Defect].} =
|
||||||
"--keymanager-address=127.0.0.1",
|
"--keymanager-address=127.0.0.1",
|
||||||
"--keymanager-port=" & $keymanagerPort,
|
"--keymanager-port=" & $keymanagerPort,
|
||||||
"--keymanager-token-file=" & tokenFilePath,
|
"--keymanager-token-file=" & tokenFilePath,
|
||||||
|
"--suggested-fee-recipient=" & $defaultFeeRecipient,
|
||||||
"--light-client-enable=off",
|
"--light-client-enable=off",
|
||||||
"--light-client-data-serve=off",
|
"--light-client-data-serve=off",
|
||||||
"--light-client-data-import-mode=none",
|
"--light-client-data-import-mode=none",
|
||||||
|
@ -552,9 +562,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -564,9 +572,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -575,10 +581,8 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
expect RestError:
|
expect RestError:
|
||||||
let keystores = await client.listKeys("Invalid Token")
|
let keystores = await client.listKeys("Invalid Token")
|
||||||
|
@ -660,9 +664,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -673,9 +675,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -685,10 +685,8 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
suite "DeleteKeys requests" & preset():
|
suite "DeleteKeys requests" & preset():
|
||||||
asyncTest "Deleting not existing key" & preset():
|
asyncTest "Deleting not existing key" & preset():
|
||||||
|
@ -710,9 +708,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -723,9 +719,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -735,10 +729,8 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
suite "ListRemoteKeys requests" & preset():
|
suite "ListRemoteKeys requests" & preset():
|
||||||
asyncTest "Correct token provided" & preset():
|
asyncTest "Correct token provided" & preset():
|
||||||
|
@ -757,9 +749,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -769,9 +759,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -780,14 +768,174 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
expect RestError:
|
expect RestError:
|
||||||
let keystores = await client.listKeys("Invalid Token")
|
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():
|
suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & preset():
|
||||||
asyncTest "Importing list of remote keys" & preset():
|
asyncTest "Importing list of remote keys" & preset():
|
||||||
let
|
let
|
||||||
|
@ -866,9 +1014,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -879,9 +1025,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -891,10 +1035,8 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
suite "DeleteRemoteKeys requests" & preset():
|
suite "DeleteRemoteKeys requests" & preset():
|
||||||
asyncTest "Deleting not existing key" & preset():
|
asyncTest "Deleting not existing key" & preset():
|
||||||
|
@ -949,9 +1091,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $noAuthorizationHeader
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Header" & preset():
|
asyncTest "Invalid Authorization Header" & preset():
|
||||||
let
|
let
|
||||||
|
@ -962,9 +1102,7 @@ proc runTests {.async.} =
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 401
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $missingBearerScheme
|
|
||||||
|
|
||||||
asyncTest "Invalid Authorization Token" & preset():
|
asyncTest "Invalid Authorization Token" & preset():
|
||||||
let
|
let
|
||||||
|
@ -974,10 +1112,8 @@ proc runTests {.async.} =
|
||||||
responseJson = Json.decode(response.data, JsonNode)
|
responseJson = Json.decode(response.data, JsonNode)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
response.status == 401
|
response.status == 403
|
||||||
responseJson["code"].getStr() == "401"
|
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||||
responseJson["message"].getStr() == InvalidAuthorization
|
|
||||||
responseJson["stacktraces"][0].getStr() == $incorrectToken
|
|
||||||
|
|
||||||
bnStatus = BeaconNodeStatus.Stopping
|
bnStatus = BeaconNodeStatus.Stopping
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue