mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-11 14:54:12 +00:00
Add Keymanager API graffiti endpoints. (#6054)
* Initial commit. * Add more tests. * Fix API mistypes. * Fix mistypes in tests. * Fix one more mistype. * Fix affected tests because of error code 401. * Add GetGraffitiResponse object. * Add more tests. * Fix compilation errors. * Recover old behavior. * Recover old behavior. * Fix mistype. * Test could not know default graffiti value. * Make VC use adopted graffiti settings. * Make BN use adopted graffiti settings. * Update Alltests. * Fix test. * Revert "Fix test." This reverts commit c735f855d3cb9c4a1c8e8af29d3f4438d068e31f. * Workaround {.push raises.} requirement. * Fix comment. * Update Alltests.
This commit is contained in:
parent
c3016a9bc5
commit
72c844534f
@ -533,6 +533,17 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||
+ validateSyncCommitteeMessage - Duplicate pubkey OK
|
||||
```
|
||||
OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||
## Graffiti management [Beacon Node] [Preset: mainnet]
|
||||
```diff
|
||||
+ Configuring the graffiti [Beacon Node] [Preset: mainnet] OK
|
||||
+ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK
|
||||
+ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK
|
||||
+ Missing Authorization header [Beacon Node] [Preset: mainnet] OK
|
||||
+ Obtaining the graffiti of a missing validator returns 404 [Beacon Node] [Preset: mainnet] OK
|
||||
+ Obtaining the graffiti of an unconfigured validator returns the suggested default [Beacon OK
|
||||
+ Setting the graffiti on a missing validator creates a record for it [Beacon Node] [Preset: OK
|
||||
```
|
||||
OK: 7/7 Fail: 0/7 Skip: 0/7
|
||||
## Honest validator
|
||||
```diff
|
||||
+ General pubsub topics OK
|
||||
@ -1006,4 +1017,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||
|
||||
---TOTAL---
|
||||
OK: 675/680 Fail: 0/680 Skip: 5/680
|
||||
OK: 682/687 Fail: 0/687 Skip: 5/687
|
||||
|
@ -1435,6 +1435,12 @@ func defaultFeeRecipient*(conf: AnyConf): Opt[Eth1Address] =
|
||||
# https://github.com/nim-lang/Nim/issues/19802
|
||||
(static(Opt.none Eth1Address))
|
||||
|
||||
func defaultGraffitiBytes*(conf: AnyConf): GraffitiBytes =
|
||||
if conf.graffiti.isSome:
|
||||
conf.graffiti.get
|
||||
else:
|
||||
defaultGraffitiBytes()
|
||||
|
||||
proc loadJwtSecret(
|
||||
rng: var HmacDrbgContext,
|
||||
dataDir: string,
|
||||
|
@ -16,7 +16,7 @@ from ../spec/eth2_apis/dynamic_fee_recipients import
|
||||
DynamicFeeRecipientsStore, getDynamicFeeRecipient
|
||||
from ../validators/keystore_management import
|
||||
getPerValidatorDefaultFeeRecipient, getSuggestedGasLimit,
|
||||
getSuggestedFeeRecipient
|
||||
getSuggestedFeeRecipient, getSuggestedGraffiti
|
||||
from ../spec/beaconstate import has_eth1_withdrawal_credential
|
||||
from ../spec/presets import Eth1Address
|
||||
|
||||
@ -64,3 +64,9 @@ proc getGasLimit*(configValidatorsDir: string,
|
||||
pubkey: ValidatorPubKey): uint64 =
|
||||
getSuggestedGasLimit(configValidatorsDir, pubkey, configGasLimit).valueOr:
|
||||
configGasLimit
|
||||
|
||||
proc getGraffiti*(configValidatorsDir: string,
|
||||
configGraffiti: GraffitiBytes,
|
||||
pubkey: ValidatorPubKey): GraffitiBytes =
|
||||
getSuggestedGraffiti(configValidatorsDir, pubkey, configGraffiti).valueOr:
|
||||
configGraffiti
|
||||
|
@ -823,6 +823,7 @@ proc init*(T: type BeaconNode,
|
||||
config.secretsDir,
|
||||
config.defaultFeeRecipient,
|
||||
config.suggestedGasLimit,
|
||||
config.defaultGraffitiBytes,
|
||||
config.getPayloadBuilderAddress,
|
||||
getValidatorAndIdx,
|
||||
getBeaconTime,
|
||||
|
@ -391,6 +391,7 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
|
||||
vc.config.secretsDir,
|
||||
vc.config.defaultFeeRecipient,
|
||||
vc.config.suggestedGasLimit,
|
||||
vc.config.defaultGraffitiBytes,
|
||||
Opt.none(string),
|
||||
nil,
|
||||
vc.beaconClock.getBeaconTimeFn,
|
||||
|
@ -54,6 +54,8 @@ const
|
||||
"Bad request. Request was malformed and could not be processed"
|
||||
InvalidGasLimitRequestError* =
|
||||
"Bad request. Request was malformed and could not be processed"
|
||||
InvalidGraffitiRequestError* =
|
||||
"Bad request. Request was malformed and could not be processed"
|
||||
VoluntaryExitValidationError* =
|
||||
"Invalid voluntary exit, it will never pass validation so it's rejected"
|
||||
VoluntaryExitValidationSuccess* =
|
||||
@ -253,3 +255,7 @@ const
|
||||
"Invalid blob index"
|
||||
InvalidBroadcastValidationType* =
|
||||
"Invalid broadcast_validation type value"
|
||||
PathNotFoundError* =
|
||||
"Path not found"
|
||||
FileReadError* =
|
||||
"Error reading file"
|
||||
|
@ -11,7 +11,7 @@
|
||||
# please keep imports clear of `rest_utils` or any other module which imports
|
||||
# beacon node's specific networking code.
|
||||
|
||||
import std/[tables, strutils, uri]
|
||||
import std/[tables, strutils, uri,]
|
||||
import chronos, chronicles, confutils,
|
||||
results, stew/[base10, io2], blscurve, presto
|
||||
import ".."/spec/[keystore, crypto]
|
||||
@ -375,10 +375,12 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
ethaddress: ethaddress.get))
|
||||
else:
|
||||
case ethaddress.error
|
||||
of noConfigFile:
|
||||
keymanagerApiError(Http404, PathNotFoundError)
|
||||
of noSuchValidator:
|
||||
keymanagerApiError(Http404, "No matching validator found")
|
||||
keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
of malformedConfigFile:
|
||||
keymanagerApiError(Http500, "Error reading fee recipient file")
|
||||
keymanagerApiError(Http500, FileReadError)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/SetFeeRecipient
|
||||
router.api2(MethodPost, "/eth/v1/validator/{pubkey}/feerecipient") do (
|
||||
@ -402,7 +404,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
status = host.setFeeRecipient(pubkey, feeRecipientReq.ethaddress)
|
||||
|
||||
if status.isOk:
|
||||
RestApiResponse.response("", Http202, "text/plain")
|
||||
RestApiResponse.response(Http202)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http500, "Failed to set fee recipient: " & status.error)
|
||||
@ -412,17 +414,22 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
pubkey: ValidatorPubKey) -> RestApiResponse:
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return authErrorResponse authStatus.error
|
||||
let
|
||||
pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
res = host.removeFeeRecipientFile(pubkey)
|
||||
return keymanagerApiError(Http401, InvalidAuthorizationError)
|
||||
|
||||
let pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
|
||||
if not(host.checkValidatorKeystoreDir(pubkey)):
|
||||
return keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
if not(host.checkConfigFile(ConfigFileKind.FeeRecipientFile, pubkey)):
|
||||
return keymanagerApiError(Http404, PathNotFoundError)
|
||||
|
||||
let res = host.removeFeeRecipientFile(pubkey)
|
||||
if res.isOk:
|
||||
RestApiResponse.response("", Http204, "text/plain")
|
||||
RestApiResponse.response(Http204)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http500, "Failed to remove fee recipient file: " & res.error)
|
||||
Http403, "Failed to remove fee recipient file: " & res.error)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/#/Gas%20Limit/getGasLimit
|
||||
router.api2(MethodGet, "/eth/v1/validator/{pubkey}/gas_limit") do (
|
||||
@ -442,10 +449,12 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
gas_limit: gasLimit.get))
|
||||
else:
|
||||
case gasLimit.error
|
||||
of noConfigFile:
|
||||
keymanagerApiError(Http404, PathNotFoundError)
|
||||
of noSuchValidator:
|
||||
keymanagerApiError(Http404, "No matching validator found")
|
||||
keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
of malformedConfigFile:
|
||||
keymanagerApiError(Http500, "Error reading gas limit file")
|
||||
keymanagerApiError(Http500, FileReadError)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/#/Gas%20Limit/setGasLimit
|
||||
router.api2(MethodPost, "/eth/v1/validator/{pubkey}/gas_limit") do (
|
||||
@ -469,7 +478,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
status = host.setGasLimit(pubkey, gasLimitReq.gas_limit)
|
||||
|
||||
if status.isOk:
|
||||
RestApiResponse.response("", Http202, "text/plain")
|
||||
RestApiResponse.response(Http202)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http500, "Failed to set gas limit: " & status.error)
|
||||
@ -479,17 +488,22 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
pubkey: ValidatorPubKey) -> RestApiResponse:
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return authErrorResponse authStatus.error
|
||||
let
|
||||
pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
res = host.removeGasLimitFile(pubkey)
|
||||
return keymanagerApiError(Http401, InvalidAuthorizationError)
|
||||
|
||||
let pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
|
||||
if not(host.checkValidatorKeystoreDir(pubkey)):
|
||||
return keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
if not(host.checkConfigFile(ConfigFileKind.GasLimitFile, pubkey)):
|
||||
return keymanagerApiError(Http404, PathNotFoundError)
|
||||
|
||||
let res = host.removeGasLimitFile(pubkey)
|
||||
if res.isOk:
|
||||
RestApiResponse.response("", Http204, "text/plain")
|
||||
RestApiResponse.response(Http204)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http500, "Failed to remove gas limit file: " & res.error)
|
||||
Http403, "Failed to remove gas limit file: " & res.error)
|
||||
|
||||
# TODO: These URLs will be changed once we submit a proposal for
|
||||
# /eth/v2/remotekeys that supports distributed keys.
|
||||
@ -609,3 +623,78 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
||||
signature: signature
|
||||
)
|
||||
RestApiResponse.jsonResponse(response)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/getGraffiti
|
||||
router.api2(MethodGet, "/eth/v1/validator/{pubkey}/graffiti") do (
|
||||
pubkey: ValidatorPubKey) -> RestApiResponse:
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return authErrorResponse authStatus.error
|
||||
|
||||
let
|
||||
pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
graffiti = host.getSuggestedGraffiti(pubkey)
|
||||
|
||||
if graffiti.isOk:
|
||||
RestApiResponse.jsonResponse(
|
||||
GraffitiResponse(pubkey: pubkey,
|
||||
graffiti: GraffitiString.init(graffiti.get)))
|
||||
else:
|
||||
case graffiti.error
|
||||
of noConfigFile:
|
||||
keymanagerApiError(Http404, PathNotFoundError)
|
||||
of noSuchValidator:
|
||||
keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
of malformedConfigFile:
|
||||
keymanagerApiError(Http500, FileReadError)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/setGraffiti
|
||||
router.api2(MethodPost, "/eth/v1/validator/{pubkey}/graffiti") do (
|
||||
pubkey: ValidatorPubKey,
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return authErrorResponse authStatus.error
|
||||
|
||||
let
|
||||
pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
req =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return keymanagerApiError(Http400, InvalidGraffitiRequestError)
|
||||
decodeBody(SetGraffitiRequest, contentBody.get()).valueOr:
|
||||
return keymanagerApiError(Http400, InvalidGraffitiRequestError)
|
||||
|
||||
if not(host.checkValidatorKeystoreDir(pubkey)):
|
||||
return keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
|
||||
let status = host.setGraffiti(pubkey, GraffitiBytes.init(req.graffiti))
|
||||
if status.isOk:
|
||||
RestApiResponse.response(Http202)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http500, "Failed to set graffiti: " & status.error)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/deleteGraffiti
|
||||
router.api2(MethodDelete, "/eth/v1/validator/{pubkey}/graffiti") do (
|
||||
pubkey: ValidatorPubKey) -> RestApiResponse:
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return keymanagerApiError(Http401, InvalidAuthorizationError)
|
||||
|
||||
let pubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
|
||||
if not(host.checkValidatorKeystoreDir(pubkey)):
|
||||
return keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
if not(host.checkConfigFile(ConfigFileKind.GraffitiFile, pubkey)):
|
||||
return keymanagerApiError(Http404, PathNotFoundError)
|
||||
|
||||
let res = host.removeGraffitiFile(pubkey)
|
||||
if res.isOk:
|
||||
RestApiResponse.response(Http204)
|
||||
else:
|
||||
keymanagerApiError(
|
||||
Http403, "Failed to remove grafiti file: " & res.error)
|
||||
|
@ -27,7 +27,7 @@ from ".."/datatypes/deneb import BeaconState
|
||||
export
|
||||
eth2_ssz_serialization, results, peerid, common, serialization, chronicles,
|
||||
json_serialization, net, sets, rest_types, slashing_protection_common,
|
||||
jsonSerializationResults
|
||||
jsonSerializationResults, rest_keymanager_types
|
||||
|
||||
from web3/primitives import BlockHash
|
||||
export primitives.BlockHash
|
||||
@ -109,6 +109,8 @@ RestJson.useDefaultSerializationFor(
|
||||
KeystoreInfo,
|
||||
ListFeeRecipientResponse,
|
||||
ListGasLimitResponse,
|
||||
GetGraffitiResponse,
|
||||
GraffitiResponse,
|
||||
PendingAttestation,
|
||||
PostKeystoresResponse,
|
||||
PrepareBeaconProposer,
|
||||
@ -161,6 +163,7 @@ RestJson.useDefaultSerializationFor(
|
||||
SPDIR_Validator,
|
||||
SetFeeRecipientRequest,
|
||||
SetGasLimitRequest,
|
||||
SetGraffitiRequest,
|
||||
SignedAggregateAndProof,
|
||||
SignedBLSToExecutionChange,
|
||||
SignedBeaconBlockHeader,
|
||||
@ -314,7 +317,8 @@ type
|
||||
SignedValidatorRegistrationV1 |
|
||||
SignedVoluntaryExit |
|
||||
Web3SignerRequest |
|
||||
RestNimbusTimestamp1
|
||||
RestNimbusTimestamp1 |
|
||||
SetGraffitiRequest
|
||||
|
||||
EncodeOctetTypes* =
|
||||
altair.SignedBeaconBlock |
|
||||
@ -367,7 +371,8 @@ type
|
||||
SomeForkedLightClientObject |
|
||||
seq[SomeForkedLightClientObject] |
|
||||
RestNimbusTimestamp1 |
|
||||
RestNimbusTimestamp2
|
||||
RestNimbusTimestamp2 |
|
||||
GetGraffitiResponse
|
||||
|
||||
DecodeConsensysTypes* =
|
||||
ProduceBlockResponseV2 | ProduceBlindedBlockResponse
|
||||
@ -3407,6 +3412,18 @@ proc parseRoot(value: string): Result[Eth2Digest, cstring] =
|
||||
except ValueError:
|
||||
err("Unable to decode root value")
|
||||
|
||||
## GraffitiString
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: GraffitiString) {.
|
||||
raises: [IOError].} =
|
||||
writeValue(writer, $value)
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], T: type GraffitiString): T {.
|
||||
raises: [IOError, SerializationError].} =
|
||||
let res = init(GraffitiString, reader.readValue(string))
|
||||
if res.isErr():
|
||||
reader.raiseUnexpectedValue res.error
|
||||
res.get
|
||||
|
||||
proc decodeBody*(
|
||||
t: typedesc[RestPublishedSignedBeaconBlock],
|
||||
body: ContentBody,
|
||||
|
@ -117,6 +117,22 @@ proc deleteGasLimitPlain *(pubkey: ValidatorPubKey,
|
||||
meth: MethodDelete.}
|
||||
## https://ethereum.github.io/keymanager-APIs/#/Gas%20Limit/deleteGasLimit
|
||||
|
||||
proc getGraffitiPlain*(pubkey: ValidatorPubKey): RestPlainResponse {.
|
||||
rest, endpoint: "/eth/v1/validator/{pubkey}/graffiti",
|
||||
meth: MethodGet.}
|
||||
## https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/getGraffiti
|
||||
|
||||
proc setGraffitiPlain*(pubkey: ValidatorPubKey,
|
||||
body: SetGraffitiRequest): RestPlainResponse {.
|
||||
rest, endpoint: "/eth/v1/validator/{pubkey}/graffiti",
|
||||
meth: MethodPost.}
|
||||
## https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/setGraffiti
|
||||
|
||||
proc deleteGraffitiPlain*(pubkey: ValidatorPubKey): RestPlainResponse {.
|
||||
rest, endpoint: "/eth/v1/validator/{pubkey}/graffiti",
|
||||
meth: MethodDelete.}
|
||||
## https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti/deleteGraffiti
|
||||
|
||||
proc listRemoteDistributedKeysPlain*(): RestPlainResponse {.
|
||||
rest, endpoint: "/eth/v1/remotekeys/distributed",
|
||||
meth: MethodGet.}
|
||||
|
@ -5,7 +5,11 @@
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/typetraits,
|
||||
stew/byteutils,
|
||||
".."/[crypto, keystore],
|
||||
../../validators/slashing_protection_common
|
||||
|
||||
@ -99,9 +103,48 @@ type
|
||||
KeymanagerGenericError* = object
|
||||
message*: string
|
||||
|
||||
GraffitiString* = distinct array[MAX_GRAFFITI_SIZE, byte]
|
||||
|
||||
GraffitiResponse* = object
|
||||
pubkey*: ValidatorPubKey
|
||||
graffiti*: GraffitiString
|
||||
|
||||
GetGraffitiResponse* = object
|
||||
data*: GraffitiResponse
|
||||
|
||||
SetGraffitiRequest* = object
|
||||
graffiti*: GraffitiString
|
||||
|
||||
proc `<`*(x, y: KeystoreInfo | RemoteKeystoreInfo): bool =
|
||||
for a, b in fields(x, y):
|
||||
let c = cmp(a, b)
|
||||
if c < 0: return true
|
||||
if c > 0: return false
|
||||
return false
|
||||
|
||||
func init*(T: type GraffitiString,
|
||||
input: string): Result[GraffitiString, string] =
|
||||
var res: GraffitiString
|
||||
if len(input) > MAX_GRAFFITI_SIZE:
|
||||
return err("The graffiti value should be 32 characters or less")
|
||||
distinctBase(res)[0 ..< len(input)] = toBytes(input)
|
||||
ok(res)
|
||||
|
||||
func init*(T: type GraffitiBytes, input: GraffitiString): GraffitiBytes =
|
||||
var res: GraffitiBytes
|
||||
distinctBase(res)[0 ..< MAX_GRAFFITI_SIZE] =
|
||||
distinctBase(input)[0 ..< MAX_GRAFFITI_SIZE]
|
||||
res
|
||||
|
||||
func init*(T: type GraffitiString, input: GraffitiBytes): GraffitiString =
|
||||
var res: GraffitiString
|
||||
distinctBase(res)[0 ..< MAX_GRAFFITI_SIZE] =
|
||||
distinctBase(input)[0 ..< MAX_GRAFFITI_SIZE]
|
||||
res
|
||||
|
||||
func `$`*(input: GraffitiString): string =
|
||||
var res = newStringOfCap(MAX_GRAFFITI_SIZE)
|
||||
for ch in distinctBase(input):
|
||||
if ch != byte(0):
|
||||
res.add(char(ch))
|
||||
res
|
||||
|
@ -411,11 +411,7 @@ proc publishBlockV2(vc: ValidatorClientRef, currentSlot, slot: Slot,
|
||||
validator: AttachedValidator) {.async.} =
|
||||
let
|
||||
genesisRoot = vc.beaconGenesis.genesis_validators_root
|
||||
graffiti =
|
||||
if vc.config.graffiti.isSome():
|
||||
vc.config.graffiti.get()
|
||||
else:
|
||||
defaultGraffitiBytes()
|
||||
graffiti = vc.getGraffitiBytes(validator)
|
||||
vindex = validator.index.get()
|
||||
|
||||
logScope:
|
||||
@ -633,11 +629,7 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
|
||||
validator: AttachedValidator) {.async.} =
|
||||
let
|
||||
genesisRoot = vc.beaconGenesis.genesis_validators_root
|
||||
graffiti =
|
||||
if vc.config.graffiti.isSome():
|
||||
vc.config.graffiti.get()
|
||||
else:
|
||||
defaultGraffitiBytes()
|
||||
graffiti = vc.getGraffitiBytes(validator)
|
||||
fork = vc.forkAtEpoch(slot.epoch)
|
||||
vindex = validator.index.get()
|
||||
|
||||
|
@ -1513,3 +1513,8 @@ proc `+`*(slot: Slot, epochs: Epoch): Slot =
|
||||
func finish_slot*(epoch: Epoch): Slot =
|
||||
## Return the last slot of ``epoch``.
|
||||
Slot((epoch + 1).start_slot() - 1)
|
||||
|
||||
proc getGraffitiBytes*(vc: ValidatorClientRef,
|
||||
validator: AttachedValidator): GraffitiBytes =
|
||||
getGraffiti(vc.config.validatorsDir, vc.config.defaultGraffitiBytes(),
|
||||
validator.pubkey)
|
||||
|
@ -33,7 +33,8 @@ import
|
||||
validator],
|
||||
../consensus_object_pools/[
|
||||
spec_cache, blockchain_dag, block_clearance, attestation_pool,
|
||||
sync_committee_msg_pool, validator_change_pool, consensus_manager],
|
||||
sync_committee_msg_pool, validator_change_pool, consensus_manager,
|
||||
common_tools],
|
||||
../el/el_manager,
|
||||
../networking/eth2_network,
|
||||
../sszdump, ../sync/sync_manager,
|
||||
@ -221,6 +222,11 @@ proc getValidatorForDuties*(
|
||||
node.attachedValidators[].getValidatorForDuties(
|
||||
key.toPubKey(), slot, slashingSafe)
|
||||
|
||||
proc getGraffitiBytes*(
|
||||
node: BeaconNode, validator: AttachedValidator): GraffitiBytes =
|
||||
getGraffiti(node.config.validatorsDir, node.config.defaultGraffitiBytes(),
|
||||
validator.pubkey)
|
||||
|
||||
proc isSynced*(node: BeaconNode, head: BlockRef): bool =
|
||||
## TODO This function is here as a placeholder for some better heurestics to
|
||||
## determine if we're in sync and should be producing blocks and
|
||||
@ -797,7 +803,7 @@ proc getBlindedBlockParts[
|
||||
proc getBuilderBid[SBBB: deneb_mev.SignedBlindedBeaconBlock](
|
||||
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
|
||||
validator_pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig,
|
||||
validator_index: ValidatorIndex):
|
||||
graffitiBytes: GraffitiBytes, validator_index: ValidatorIndex):
|
||||
Future[BlindedBlockResult[SBBB]] {.async: (raises: [CancelledError]).} =
|
||||
## Returns the unsigned blinded block obtained from the Builder API.
|
||||
## Used by the BN's own validators, but not the REST server
|
||||
@ -808,7 +814,7 @@ proc getBuilderBid[SBBB: deneb_mev.SignedBlindedBeaconBlock](
|
||||
|
||||
let blindedBlockParts = await getBlindedBlockParts[EPH](
|
||||
node, payloadBuilderClient, head, validator_pubkey, slot, randao,
|
||||
validator_index, node.graffitiBytes)
|
||||
validator_index, graffitiBytes)
|
||||
if blindedBlockParts.isErr:
|
||||
# Not signed yet, fine to try to fall back on EL
|
||||
beacon_block_builder_missed_with_fallback.inc()
|
||||
@ -949,7 +955,8 @@ proc collectBids(
|
||||
if usePayloadBuilder:
|
||||
when not (EPS is bellatrix.ExecutionPayloadForSigning):
|
||||
getBuilderBid[SBBB](node, payloadBuilderClient, head,
|
||||
validator_pubkey, slot, randao, validator_index)
|
||||
validator_pubkey, slot, randao, graffitiBytes,
|
||||
validator_index)
|
||||
else:
|
||||
let fut = newFuture[BlindedBlockResult[SBBB]]("builder-bid")
|
||||
fut.complete(BlindedBlockResult[SBBB].err(
|
||||
@ -1027,12 +1034,13 @@ proc proposeBlockAux(
|
||||
genesis_validators_root: Eth2Digest,
|
||||
localBlockValueBoost: uint8): Future[BlockRef] {.async: (raises: [CancelledError]).} =
|
||||
let
|
||||
graffitiBytes = node.getGraffitiBytes(validator)
|
||||
payloadBuilderClient =
|
||||
node.getPayloadBuilderClient(validator_index.distinctBase).valueOr(nil)
|
||||
|
||||
collectedBids = await collectBids(
|
||||
SBBB, EPS, node, payloadBuilderClient, validator.pubkey, validator_index,
|
||||
node.graffitiBytes, head, slot, randao)
|
||||
graffitiBytes, head, slot, randao)
|
||||
|
||||
useBuilderBlock =
|
||||
if collectedBids.builderBid.isSome():
|
||||
|
@ -15,7 +15,7 @@ import
|
||||
nimbus_security_resources,
|
||||
".."/spec/[eth2_merkleization, keystore, crypto],
|
||||
".."/spec/datatypes/base,
|
||||
stew/io2, libp2p/crypto/crypto as lcrypto,
|
||||
stew/[io2, byteutils], libp2p/crypto/crypto as lcrypto,
|
||||
nimcrypto/utils as ncrutils,
|
||||
".."/[conf, filepath, beacon_clock],
|
||||
".."/networking/network_metadata,
|
||||
@ -40,6 +40,7 @@ const
|
||||
RemoteKeystoreFileName* = "remote_keystore.json"
|
||||
FeeRecipientFilename = "suggested_fee_recipient.hex"
|
||||
GasLimitFilename = "suggested_gas_limit.json"
|
||||
GraffitiBytesFilename = "graffiti.hex"
|
||||
BuilderConfigPath = "payload_builder.json"
|
||||
KeyNameSize = 98 # 0x + hexadecimal key representation 96 characters.
|
||||
MaxKeystoreFileSize = 65536
|
||||
@ -91,6 +92,7 @@ type
|
||||
secretsDir*: string
|
||||
defaultFeeRecipient*: Opt[Eth1Address]
|
||||
defaultGasLimit*: uint64
|
||||
defaultGraffiti*: GraffitiBytes
|
||||
defaultBuilderAddress*: Opt[string]
|
||||
getValidatorAndIdxFn*: ValidatorPubKeyToDataFn
|
||||
getBeaconTimeFn*: GetBeaconTimeFn
|
||||
@ -104,6 +106,10 @@ type
|
||||
|
||||
QueryResult = Result[seq[KeystoreData], string]
|
||||
|
||||
ConfigFileKind* {.pure.} = enum
|
||||
KeystoreFile, RemoteKeystoreFile, FeeRecipientFile, GasLimitFile,
|
||||
BuilderConfigFile, GraffitiFile
|
||||
|
||||
const
|
||||
minPasswordLen = 12
|
||||
minPasswordEntropy = 60.0
|
||||
@ -125,6 +131,7 @@ func init*(T: type KeymanagerHost,
|
||||
secretsDir: string,
|
||||
defaultFeeRecipient: Opt[Eth1Address],
|
||||
defaultGasLimit: uint64,
|
||||
defaultGraffiti: GraffitiBytes,
|
||||
defaultBuilderAddress: Opt[string],
|
||||
getValidatorAndIdxFn: ValidatorPubKeyToDataFn,
|
||||
getBeaconTimeFn: GetBeaconTimeFn,
|
||||
@ -140,6 +147,7 @@ func init*(T: type KeymanagerHost,
|
||||
secretsDir: secretsDir,
|
||||
defaultFeeRecipient: defaultFeeRecipient,
|
||||
defaultGasLimit: defaultGasLimit,
|
||||
defaultGraffiti: defaultGraffiti,
|
||||
defaultBuilderAddress: defaultBuilderAddress,
|
||||
getValidatorAndIdxFn: getValidatorAndIdxFn,
|
||||
getBeaconTimeFn: getBeaconTimeFn,
|
||||
@ -805,23 +813,36 @@ iterator listLoadableKeystores*(config: AnyConf,
|
||||
type
|
||||
ValidatorConfigFileStatus* = enum
|
||||
noSuchValidator
|
||||
noConfigFile
|
||||
malformedConfigFile
|
||||
|
||||
func validatorKeystoreDir(
|
||||
validatorsDir: string, pubkey: ValidatorPubKey): string =
|
||||
validatorsDir / pubkey.fsName
|
||||
|
||||
func feeRecipientPath(validatorsDir: string,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / FeeRecipientFilename
|
||||
proc checkValidatorKeystoreDir(validatorsDir: string,
|
||||
pubkey: ValidatorPubKey): bool =
|
||||
dirExists(validatorsDir.validatorKeystoreDir(pubkey))
|
||||
|
||||
func gasLimitPath(validatorsDir: string,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename
|
||||
func configFilePath*(validatorsDir: string, kind: ConfigFileKind,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
case kind
|
||||
of ConfigFileKind.KeystoreFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / KeystoreFileName
|
||||
of ConfigFileKind.RemoteKeystoreFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / RemoteKeystoreFileName
|
||||
of ConfigFileKind.FeeRecipientFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / FeeRecipientFilename
|
||||
of ConfigFileKind.GasLimitFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename
|
||||
of ConfigFileKind.BuilderConfigFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / BuilderConfigPath
|
||||
of ConfigFileKind.GraffitiFile:
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / GraffitiBytesFilename
|
||||
|
||||
func builderConfigPath(validatorsDir: string,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
validatorsDir.validatorKeystoreDir(pubkey) / BuilderConfigPath
|
||||
proc checkConfigFile*(validatorsDir: string, kind: ConfigFileKind,
|
||||
pubkey: ValidatorPubKey): bool =
|
||||
fileExists(validatorsDir.configFilePath(kind, pubkey))
|
||||
|
||||
proc getSuggestedFeeRecipient*(
|
||||
validatorsDir: string, pubkey: ValidatorPubKey,
|
||||
@ -833,7 +854,8 @@ proc getSuggestedFeeRecipient*(
|
||||
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
|
||||
return err noSuchValidator
|
||||
|
||||
let feeRecipientPath = validatorsDir.feeRecipientPath(pubkey)
|
||||
let feeRecipientPath =
|
||||
validatorsDir.configFilePath(ConfigFileKind.FeeRecipientFile, pubkey)
|
||||
if not fileExists(feeRecipientPath):
|
||||
return ok defaultFeeRecipient
|
||||
|
||||
@ -861,7 +883,8 @@ proc getSuggestedGasLimit*(
|
||||
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
|
||||
return err noSuchValidator
|
||||
|
||||
let gasLimitPath = validatorsDir.gasLimitPath(pubkey)
|
||||
let gasLimitPath =
|
||||
validatorsDir.configFilePath(ConfigFileKind.GasLimitFile, pubkey)
|
||||
if not fileExists(gasLimitPath):
|
||||
return ok defaultGasLimit
|
||||
try:
|
||||
@ -877,6 +900,34 @@ proc getSuggestedGasLimit*(
|
||||
err = exc.msg
|
||||
err malformedConfigFile
|
||||
|
||||
proc getSuggestedGraffiti*(
|
||||
validatorsDir: string,
|
||||
pubkey: ValidatorPubKey,
|
||||
defaultGraffitiBytes: GraffitiBytes
|
||||
): Result[GraffitiBytes, ValidatorConfigFileStatus] =
|
||||
# In this particular case, an error might be by design. If the file exists,
|
||||
# but doesn't load or parse that is more urgent. People might prefer not to
|
||||
# override their default suggested gas limit per validator, so don't warn.
|
||||
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
|
||||
return err noSuchValidator
|
||||
|
||||
let graffitiPath =
|
||||
validatorsDir.configFilePath(ConfigFileKind.GraffitiFile, pubkey)
|
||||
if not fileExists(graffitiPath):
|
||||
return ok defaultGraffitiBytes
|
||||
|
||||
let data = readAllChars(graffitiPath).valueOr:
|
||||
warn "Failed to load graffiti file; falling back to default graffiti",
|
||||
reason = ioErrorMsg(error), error = int(error)
|
||||
return err malformedConfigFile
|
||||
|
||||
try:
|
||||
ok GraffitiBytes.init(data)
|
||||
except ValueError as exc:
|
||||
warn "Invalid local graffiti file", graffitiPath,
|
||||
reason = exc.msg
|
||||
return err malformedConfigFile
|
||||
|
||||
type
|
||||
BuilderConfig = object
|
||||
payloadBuilderEnable: bool
|
||||
@ -892,7 +943,8 @@ proc getBuilderConfig*(
|
||||
if not dirExists(validatorsDir.validatorKeystoreDir(pubkey)):
|
||||
return err noSuchValidator
|
||||
|
||||
let builderConfigPath = validatorsDir.builderConfigPath(pubkey)
|
||||
let builderConfigPath =
|
||||
validatorsDir.configFilePath(ConfigFileKind.BuilderConfigFile, pubkey)
|
||||
if not fileExists(builderConfigPath):
|
||||
return ok defaultBuilderAddress
|
||||
|
||||
@ -1403,33 +1455,49 @@ func validatorKeystoreDir(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
host.validatorsDir.validatorKeystoreDir(pubkey)
|
||||
|
||||
proc checkValidatorKeystoreDir*(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): bool =
|
||||
host.validatorsDir.checkValidatorKeystoreDir(pubkey)
|
||||
|
||||
proc checkConfigFile*(host: KeymanagerHost, kind: ConfigFileKind,
|
||||
pubkey: ValidatorPubKey): bool =
|
||||
fileExists(host.validatorsDir.configFilePath(kind, pubkey))
|
||||
|
||||
func feeRecipientPath(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
host.validatorsDir.feeRecipientPath(pubkey)
|
||||
host.validatorsDir.configFilePath(ConfigFileKind.FeeRecipientFile, pubkey)
|
||||
|
||||
func gasLimitPath(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
host.validatorsDir.gasLimitPath(pubkey)
|
||||
host.validatorsDir.configFilePath(ConfigFileKind.GasLimitFile, pubkey)
|
||||
|
||||
func graffitiPath(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): string =
|
||||
host.validatorsDir.configFilePath(ConfigFileKind.GraffitiFile, pubkey)
|
||||
|
||||
proc removeFeeRecipientFile*(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): Result[void, string] =
|
||||
let path = host.feeRecipientPath(pubkey)
|
||||
if fileExists(path):
|
||||
let res = io2.removeFile(path)
|
||||
if res.isErr:
|
||||
return err res.error.ioErrorMsg
|
||||
|
||||
return ok()
|
||||
io2.removeFile(path).isOkOr:
|
||||
return err($uint(error) & " " & ioErrorMsg(error))
|
||||
ok()
|
||||
|
||||
proc removeGasLimitFile*(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): Result[void, string] =
|
||||
let path = host.gasLimitPath(pubkey)
|
||||
if fileExists(path):
|
||||
let res = io2.removeFile(path)
|
||||
if res.isErr:
|
||||
return err res.error.ioErrorMsg
|
||||
io2.removeFile(path).isOkOr:
|
||||
return err($uint(error) & " " & ioErrorMsg(error))
|
||||
ok()
|
||||
|
||||
return ok()
|
||||
proc removeGraffitiFile*(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey): Result[void, string] =
|
||||
let path = host.graffitiPath(pubkey)
|
||||
if fileExists(path):
|
||||
io2.removeFile(path).isOkOr:
|
||||
return err($uint(error) & " " & ioErrorMsg(error))
|
||||
ok()
|
||||
|
||||
proc setFeeRecipient*(host: KeymanagerHost, pubkey: ValidatorPubKey, feeRecipient: Eth1Address): Result[void, string] =
|
||||
let validatorKeystoreDir = host.validatorKeystoreDir(pubkey)
|
||||
@ -1451,6 +1519,23 @@ proc setGasLimit*(host: KeymanagerHost,
|
||||
io2.writeFile(validatorKeystoreDir / GasLimitFilename, $gasLimit)
|
||||
.mapErr(proc(e: auto): string = "Failed to write gas limit file: " & $e)
|
||||
|
||||
proc setGraffiti*(host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey,
|
||||
graffiti: GraffitiBytes): Result[void, string] =
|
||||
let
|
||||
validatorKeystoreDir = host.validatorKeystoreDir(pubkey)
|
||||
path = host.graffitiPath(pubkey)
|
||||
|
||||
? secureCreatePath(validatorKeystoreDir)
|
||||
.mapErr(proc(e: auto): string =
|
||||
"Could not create wallet directory [" & validatorKeystoreDir & "], " &
|
||||
"reason: (" & $int(e) & ") " & ioErrorMsg(e))
|
||||
|
||||
io2.writeFile(path, to0xHex(distinctBase(graffiti)))
|
||||
.mapErr(proc(e: auto): string =
|
||||
"Failed to write graffiti file," &
|
||||
" reason: (" & $int(e) & ") " & ioErrorMsg(e))
|
||||
|
||||
from ".."/spec/beaconstate import has_eth1_withdrawal_credential
|
||||
|
||||
proc getValidatorWithdrawalAddress*(
|
||||
@ -1499,6 +1584,12 @@ proc getSuggestedGasLimit*(
|
||||
pubkey: ValidatorPubKey): Result[uint64, ValidatorConfigFileStatus] =
|
||||
host.validatorsDir.getSuggestedGasLimit(pubkey, host.defaultGasLimit)
|
||||
|
||||
proc getSuggestedGraffiti*(
|
||||
host: KeymanagerHost,
|
||||
pubkey: ValidatorPubKey
|
||||
): Result[GraffitiBytes, ValidatorConfigFileStatus] =
|
||||
host.validatorsDir.getSuggestedGraffiti(pubkey, host.defaultGraffiti)
|
||||
|
||||
proc getBuilderConfig*(
|
||||
host: KeymanagerHost, pubkey: ValidatorPubKey):
|
||||
Result[Opt[string], ValidatorConfigFileStatus] =
|
||||
|
@ -6,6 +6,9 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.used.}
|
||||
{.push raises: [].}
|
||||
# TODO (cheatfate): This test is going to be rewritten from scratch.
|
||||
{.pop.}
|
||||
|
||||
import
|
||||
std/[typetraits, os, options, json, sequtils, uri, algorithm],
|
||||
@ -1044,7 +1047,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 403
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
asyncTest "Obtaining the fee recipient of a missing validator returns 404" & testFlavour:
|
||||
@ -1206,7 +1209,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 403
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
asyncTest "Obtaining the gas limit of a missing validator returns 404" & testFlavour:
|
||||
@ -1261,6 +1264,255 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
|
||||
check:
|
||||
finalResultFromApi == defaultGasLimit
|
||||
|
||||
suite "Graffiti management" & testFlavour:
|
||||
asyncTest "Missing Authorization header" & testFlavour:
|
||||
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.getGraffitiPlain(pubkey)
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.setGraffitiPlain(
|
||||
pubkey,
|
||||
default SetGraffitiRequest)
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.deleteGraffitiPlain(pubkey)
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
|
||||
asyncTest "Invalid Authorization Header" & testFlavour:
|
||||
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.setGraffitiPlain(
|
||||
pubkey,
|
||||
default SetGraffitiRequest,
|
||||
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.deleteGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "UnknownAuthScheme X")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
asyncTest "Invalid Authorization Token" & testFlavour:
|
||||
let pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 403
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.setGraffitiPlain(
|
||||
pubkey,
|
||||
default SetGraffitiRequest,
|
||||
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 403
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
block:
|
||||
let
|
||||
response = await client.deleteGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer InvalidToken")])
|
||||
responseJson = Json.decode(response.data, JsonNode)
|
||||
|
||||
check:
|
||||
response.status == 401
|
||||
responseJson["message"].getStr() == InvalidAuthorizationError
|
||||
|
||||
asyncTest "Obtaining the graffiti of a missing validator returns 404" &
|
||||
testFlavour:
|
||||
let
|
||||
pubkey =
|
||||
ValidatorPubKey.fromHex(unusedPublicKeys[0]).expect("valid key")
|
||||
response = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
response.status == 404
|
||||
|
||||
asyncTest "Setting the graffiti on a missing validator creates " &
|
||||
"a record for it" & testFlavour:
|
||||
let
|
||||
pubkey =
|
||||
ValidatorPubKey.fromHex(unusedPublicKeys[1]).expect("valid key")
|
||||
graffiti =
|
||||
SetGraffitiRequest(
|
||||
graffiti: GraffitiString.init("🚀\"🍻\"🚀").get())
|
||||
|
||||
let response =
|
||||
await client.setGraffitiPlain(pubkey, graffiti,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
check:
|
||||
response.status == 202
|
||||
|
||||
let fromApi =
|
||||
await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
fromApi.status == 200
|
||||
|
||||
let res =
|
||||
decodeBytes(GetGraffitiResponse, fromApi.data, fromApi.contentType)
|
||||
|
||||
check:
|
||||
res.isOk()
|
||||
res.get().data.pubkey == pubkey
|
||||
$res.get().data.graffiti == "🚀\"🍻\"🚀"
|
||||
|
||||
asyncTest "Obtaining the graffiti of an unconfigured validator returns " &
|
||||
"the suggested default" & testFlavour:
|
||||
let
|
||||
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
|
||||
fromApi = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
fromApi.status == 200
|
||||
|
||||
let res =
|
||||
decodeBytes(GetGraffitiResponse, fromApi.data, fromApi.contentType)
|
||||
|
||||
check:
|
||||
res.isOk()
|
||||
res.get().data.pubkey == pubkey
|
||||
|
||||
asyncTest "Configuring the graffiti" & testFlavour:
|
||||
let
|
||||
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[1]).expect("valid key")
|
||||
firstGraffiti = "🚀"
|
||||
secondGraffiti = "🚀🚀"
|
||||
firstRequest =
|
||||
SetGraffitiRequest(
|
||||
graffiti: GraffitiString.init(firstGraffiti).get())
|
||||
secondRequest =
|
||||
SetGraffitiRequest(
|
||||
graffiti: GraffitiString.init(secondGraffiti).get())
|
||||
|
||||
block:
|
||||
let response =
|
||||
await client.setGraffitiPlain(pubkey, firstRequest,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
check:
|
||||
response.status == 202
|
||||
|
||||
block:
|
||||
let resApi = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
resApi.status == 200
|
||||
|
||||
let res =
|
||||
decodeBytes(GetGraffitiResponse, resApi.data, resApi.contentType)
|
||||
|
||||
check:
|
||||
res.isOk()
|
||||
res.get().data.pubkey == pubkey
|
||||
$res.get().data.graffiti == firstGraffiti
|
||||
|
||||
block:
|
||||
let response =
|
||||
await client.setGraffitiPlain(pubkey, secondRequest,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
check:
|
||||
response.status == 202
|
||||
|
||||
block:
|
||||
let resApi = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
resApi.status == 200
|
||||
|
||||
let res =
|
||||
decodeBytes(GetGraffitiResponse, resApi.data, resApi.contentType)
|
||||
|
||||
check:
|
||||
res.isOk()
|
||||
res.get().data.pubkey == pubkey
|
||||
$res.get().data.graffiti == secondGraffiti
|
||||
|
||||
block:
|
||||
let response = await client.deleteGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
check:
|
||||
response.status == 204
|
||||
|
||||
block:
|
||||
let resApi = await client.getGraffitiPlain(
|
||||
pubkey,
|
||||
extraHeaders = @[("Authorization", "Bearer " & correctTokenValue)])
|
||||
|
||||
check:
|
||||
resApi.status == 200
|
||||
|
||||
let res =
|
||||
decodeBytes(GetGraffitiResponse, resApi.data, resApi.contentType)
|
||||
|
||||
check:
|
||||
res.isOk()
|
||||
res.get().data.pubkey == pubkey
|
||||
|
||||
suite "ImportRemoteKeys/ListRemoteKeys/DeleteRemoteKeys" & testFlavour:
|
||||
asyncTest "Importing list of remote keys" & testFlavour:
|
||||
let
|
||||
|
Loading…
x
Reference in New Issue
Block a user