mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-02 09:46:26 +00:00
[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
|
||||
```
|
||||
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
|
||||
|
@ -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])
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -48,6 +48,8 @@ proc validate(key: string, value: string): int =
|
||||
0
|
||||
of "{block_root}":
|
||||
0
|
||||
of "{pubkey}":
|
||||
int(value.len != 98)
|
||||
else:
|
||||
1
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user