diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index b141097b2a..0a08497431 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -389,6 +389,44 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) RestApiResponse.jsonError(Http404, StateNotFoundError) + proc getValidatorIdentities( + node: BeaconNode, + bslot: BlockSlotId, + validatorIds: openArray[ValidatorIdent] + ): RestApiResponse = + node.withStateForBlockSlotId(bslot): + let + indices = node.getIndices(validatorIds, state).valueOr: + return RestApiResponse.jsonError(error) + response = + block: + var res: seq[RestValidatorIdentity] + if len(indices) == 0: + # Case when `len(indices) == 0 and len(validatorIds) != 0` means + # that we can't find validator identifiers in state, so we should + # return empty response. + if len(validatorIds) == 0: + # There are no indices, so we're going to filter all the + # validators. + for index, validator in getStateField(state, validators): + res.add(RestValidatorIdentity.init(ValidatorIndex(index), + validator.pubkeyData.pubkey(), + validator.activation_epoch)) + else: + for index in indices: + let + validator = getStateField(state, validators).item(index) + res.add(RestValidatorIdentity.init(index, + validator.pubkeyData.pubkey(), + validator.activation_epoch)) + res + return RestApiResponse.jsonResponseFinalized( + response, + node.getStateOptimistic(state), + node.dag.isFinalized(bslot.bid) + ) + RestApiResponse.jsonError(Http404, StateNotFoundError) + proc getBalances( node: BeaconNode, bslot: BlockSlotId, @@ -613,6 +651,31 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) getBalances(node, bslot, validatorIds) + # https://ethereum.github.io/beacon-APIs/#/Beacon/postStateValidatorIdentities + router.metricsApi2( + MethodPost, "/eth/v1/beacon/states/{state_id}/validator_identities", + {RestServerMetricsType.Status, Response}) do ( + state_id: StateIdent, contentBody: Option[ContentBody]) -> RestApiResponse: + let + validatorIds = + block: + if contentBody.isNone(): + return RestApiResponse.jsonError(Http400, EmptyRequestBodyError) + let body = contentBody.get() + decodeBody(seq[ValidatorIdent], body).valueOr: + return RestApiResponse.jsonError( + Http400, InvalidValidatorIdValueError, $error) + sid = state_id.valueOr: + return RestApiResponse.jsonError(Http400, InvalidStateIdValueError, + $error) + bslot = node.getBlockSlotId(sid).valueOr: + if sid.kind == StateQueryKind.Root: + # TODO (cheatfate): Its impossible to retrieve state by `state_root` + # in current version of database. + return RestApiResponse.jsonError(Http500, NoImplementationError) + return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) + getValidatorIdentities(node, bslot, validatorIds) + # https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees router.metricsApi2( MethodGet, "/eth/v1/beacon/states/{state_id}/committees", diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index fd590dc527..a2be6e87c4 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -153,6 +153,7 @@ RestJson.useDefaultSerializationFor( RestSyncCommitteeSubscription, RestSyncInfo, RestValidator, + RestValidatorIdentity, RestValidatorBalance, SPDIR, SPDIR_Meta, diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index be7553901e..17b29f3edb 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -223,6 +223,11 @@ type status*: string validator*: Validator + RestValidatorIdentity* = object + index*: ValidatorIndex + pubkey*: ValidatorPubkey + activation_epoch*: Epoch + RestBlockHeader* = object slot*: Slot proposer_index*: ValidatorIndex @@ -775,6 +780,12 @@ func init*(t: typedesc[RestValidator], index: ValidatorIndex, RestValidator(index: index, balance: Base10.toString(balance), status: status, validator: validator) +func init*(t: typedesc[RestValidatorIdentity], index: ValidatorIndex, + pubkey: ValidatorPubKey, + activation_epoch: Epoch): RestValidatorIdentity = + RestValidatorIdentity(index: index, pubkey: pubkey, + activation_epoch: activation_epoch) + func init*(t: typedesc[RestValidatorBalance], index: ValidatorIndex, balance: Gwei): RestValidatorBalance = RestValidatorBalance(index: index, balance: Base10.toString(balance)) diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 529613d51d..17881c66cb 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -2431,6 +2431,59 @@ "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": ""}]}] } }, + { + "topics": ["beacon", "states_validator_identities", "slow", "post"], + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[]" + }, + "url": "/eth/v1/beacon/states/head/validator_identities", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "pubkey": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_identities", "post"], + "comment": "Correct hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_identities", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "pubkey": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_identities", "post"], + "comment": "Incorrect hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_identities", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, { "topics": ["beacon", "states_committees"], "request": {