From 00d354d7bb5a20107c9c5a2502c2461281f7f75e Mon Sep 17 00:00:00 2001 From: cheatfate Date: Tue, 28 Nov 2023 07:44:07 +0200 Subject: [PATCH 1/7] Add implementation POST versions of /eth/v1/beacon/states/{state_id}/validators and /eth/v1/beacon/states/{state_id}/validator_balances. Add tests. --- beacon_chain/rpc/rest_beacon_api.nim | 390 +++---- beacon_chain/rpc/rest_constants.nim | 2 + .../eth2_apis/eth2_rest_serialization.nim | 118 +++ beacon_chain/spec/eth2_apis/rest_types.nim | 4 + ncli/resttest-rules.json | 978 +++++++++++++++++- 5 files changed, 1299 insertions(+), 193 deletions(-) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index bbed3dea6e..08dd260a4d 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -236,6 +236,129 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) return RestApiResponse.jsonError(Http404, StateNotFoundError) + proc getIndices( + node: BeaconNode, + validatorIds: openArray[ValidatorIdent], + state: ForkedHashedBeaconState + ): Result[seq[ValidatorIndex], RestErrorMessage] = + var + keyset: HashSet[ValidatorPubKey] + indexset: HashSet[ValidatorIndex] + + let validatorsCount = lenu64(getStateField(state, validators)) + + for item in validatorIds: + case item.kind + of ValidatorQueryKind.Key: + keyset.incl(item.key) + of ValidatorQueryKind.Index: + let vindex = item.index.toValidatorIndex().valueOr: + case error + of ValidatorIndexError.TooHighValue: + return err(RestErrorMessage.init( + Http400, TooHighValidatorIndexValueError)) + of ValidatorIndexError.UnsupportedValue: + return err(RestErrorMessage.init( + Http500, UnsupportedValidatorIndexValueError)) + if uint64(vindex) < validatorsCount: + # We only adding validator indices which are present in + # validators list at this moment. + indexset.incl(vindex) + + if len(keyset) > 0: + let optIndices = keysToIndices(node.restKeysCache, state, keyset.toSeq()) + # Remove all the duplicates. + for item in optIndices: + # We ignore missing keys. + if item.isSome(): + indexset.incl(item.get()) + ok(indexset.toSeq()) + + proc getValidators( + node: BeaconNode, + bslot: BlockSlotId, + validatorsMask: ValidatorFilter, + validatorIds: openArray[ValidatorIdent] + ): RestApiResponse = + node.withStateForBlockSlotId(bslot): + let + current_epoch = getStateField(state, slot).epoch() + validatorsCount = lenu64(getStateField(state, validators)) + indices = node.getIndices(validatorIds, state).valueOr: + return RestApiResponse.jsonError(error) + response = + block: + var res: seq[RestValidator] + 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 is no indices, so we going to filter all the validators. + for index, validator in getStateField(state, validators): + let + balance = getStateField(state, balances).item(index) + status = validator.getStatus(current_epoch).valueOr: + return RestApiResponse.jsonError( + Http400, ValidatorStatusNotFoundError, $error) + if status in validatorsMask: + res.add(RestValidator.init(ValidatorIndex(index), balance, + toString(status), validator)) + else: + for index in indices: + let + validator = getStateField(state, validators).item(index) + balance = getStateField(state, balances).item(index) + status = validator.getStatus(current_epoch).valueOr: + return RestApiResponse.jsonError( + Http400, ValidatorStatusNotFoundError, $error) + if status in validatorsMask: + res.add(RestValidator.init(index, balance, toString(status), + validator)) + res + return RestApiResponse.jsonResponseFinalized( + response, + node.getStateOptimistic(state), + node.dag.isFinalized(bslot.bid) + ) + RestApiResponse.jsonError(Http404, StateNotFoundError) + + proc getBalances( + node: BeaconNode, + bslot: BlockSlotId, + validatorIds: openArray[ValidatorIdent] + ): RestApiResponse = + node.withStateForBlockSlotId(bslot): + let + validatorsCount = lenu64(getStateField(state, validators)) + indices = node.getIndices(validatorIds, state).valueOr: + return RestApiResponse.jsonError(error) + response = + block: + var res: seq[RestValidatorBalance] + 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 is no indices, so we going to return balances of all + # known validators. + for index, balance in getStateField(state, balances): + res.add(RestValidatorBalance.init(ValidatorIndex(index), + balance)) + else: + for index in indices: + let balance = getStateField(state, balances).item(index) + res.add(RestValidatorBalance.init(index, balance)) + res + + return RestApiResponse.jsonResponseFinalized( + response, + node.getStateOptimistic(state), + node.dag.isFinalized(bslot.bid) + ) + RestApiResponse.jsonError(Http404, StateNotFoundError) + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators router.api(MethodGet, "/eth/v1/beacon/states/{state_id}/validators") do ( state_id: StateIdent, id: seq[ValidatorIdent], @@ -249,119 +372,54 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = # 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) - let validatorIds = - block: - if id.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidValidatorIdValueError) - let ires = id.get() - if len(ires) > ServerMaximumValidatorIds: - return RestApiResponse.jsonError(Http414, - MaximumNumberOfValidatorIdsError) - ires - - let validatorsMask = - block: - if status.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidValidatorStatusValueError) - let res = validateFilter(status.get()) - if res.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidValidatorStatusValueError, - $res.error()) - res.get() - - node.withStateForBlockSlotId(bslot): - let - current_epoch = getStateField(state, slot).epoch() - validatorsCount = lenu64(getStateField(state, validators)) - - let indices = + return RestApiResponse.jsonError( + Http404, StateNotFoundError, $error) + validatorIds = block: - var keyset: HashSet[ValidatorPubKey] - var indexset: HashSet[ValidatorIndex] - for item in validatorIds: - case item.kind - of ValidatorQueryKind.Key: - keyset.incl(item.key) - of ValidatorQueryKind.Index: - let vindex = - block: - let vres = item.index.toValidatorIndex() - if vres.isErr(): - case vres.error() - of ValidatorIndexError.TooHighValue: - return RestApiResponse.jsonError(Http400, - TooHighValidatorIndexValueError) - of ValidatorIndexError.UnsupportedValue: - return RestApiResponse.jsonError(Http500, - UnsupportedValidatorIndexValueError) - let index = vres.get() - index - if uint64(vindex) < validatorsCount: - # We only adding validator indices which are present in - # validators list at this moment. - indexset.incl(vindex) - - if len(keyset) > 0: - let optIndices = keysToIndices(node.restKeysCache, state, - keyset.toSeq()) - # Remove all the duplicates. - for item in optIndices: - # We ignore missing keys. - if item.isSome(): - indexset.incl(item.get()) - indexset.toSeq() - - let response = + if id.isErr(): + return RestApiResponse.jsonError( + Http400, InvalidValidatorIdValueError) + let ires = id.get() + if len(ires) > ServerMaximumValidatorIds: + return RestApiResponse.jsonError( + Http414, MaximumNumberOfValidatorIdsError) + ires + validatorsMask = block: - var res: seq[RestValidator] - 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 is no indices, so we going to filter all the validators. - for index, validator in getStateField(state, validators): - let - balance = getStateField(state, balances).item(index) - status = - block: - let sres = validator.getStatus(current_epoch) - if sres.isErr(): - return RestApiResponse.jsonError(Http400, - ValidatorStatusNotFoundError, - $sres.get()) - sres.get() - if status in validatorsMask: - res.add(RestValidator.init(ValidatorIndex(index), balance, - toString(status), validator)) - else: - for index in indices: - let - validator = getStateField(state, validators).item(index) - balance = getStateField(state, balances).item(index) - status = - block: - let sres = validator.getStatus(current_epoch) - if sres.isErr(): - return RestApiResponse.jsonError(Http400, - ValidatorStatusNotFoundError, - $sres.get()) - sres.get() - if status in validatorsMask: - res.add(RestValidator.init(index, balance, toString(status), - validator)) - res - return RestApiResponse.jsonResponseFinalized( - response, - node.getStateOptimistic(state), - node.dag.isFinalized(bslot.bid) - ) - return RestApiResponse.jsonError(Http404, StateNotFoundError) + if status.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidValidatorStatusValueError) + validateFilter(status.get()).valueOr: + return RestApiResponse.jsonError( + Http400, InvalidValidatorStatusValueError, $error) + getValidators(node, bslot, validatorsMask, validatorIds) + + # https://ethereum.github.io/beacon-APIs/#/Beacon/postStateValidators + router.api(MethodPost, "/eth/v1/beacon/states/{state_id}/validators") do ( + state_id: StateIdent, contentBody: Option[ContentBody]) -> RestApiResponse: + let + (validatorIds, validatorsMask) = + block: + if contentBody.isNone(): + return RestApiResponse.jsonError(Http400, EmptyRequestBodyError) + let request = + decodeBody(RestValidatorRequest, contentBody.get()).valueOr: + return RestApiResponse.jsonError( + Http400, InvalidRequestBodyError, $error) + let + ids = request.ids.valueOr: @[] + filter = request.status.valueOr: {} + (ids, filter) + 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) + getValidators(node, bslot, validatorsMask, validatorIds) # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator router.api(MethodGet, @@ -441,84 +499,42 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = # 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) - - let validatorIds = - block: - if id.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidValidatorIdValueError) - let ires = id.get() - if len(ires) > ServerMaximumValidatorIds: - return RestApiResponse.jsonError(Http400, - MaximumNumberOfValidatorIdsError) - ires - - node.withStateForBlockSlotId(bslot): - let validatorsCount = lenu64(getStateField(state, validators)) - - let indices = + return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) + validatorIds = block: - var keyset: HashSet[ValidatorPubKey] - var indexset: HashSet[ValidatorIndex] - for item in validatorIds: - case item.kind - of ValidatorQueryKind.Key: - keyset.incl(item.key) - of ValidatorQueryKind.Index: - let vindex = - block: - let vres = item.index.toValidatorIndex() - if vres.isErr(): - case vres.error() - of ValidatorIndexError.TooHighValue: - return RestApiResponse.jsonError(Http400, - TooHighValidatorIndexValueError) - of ValidatorIndexError.UnsupportedValue: - return RestApiResponse.jsonError(Http500, - UnsupportedValidatorIndexValueError) - vres.get() - # We only adding validator indices which are present in - # validators list at this moment. - if uint64(vindex) < validatorsCount: - indexset.incl(vindex) - - if len(keyset) > 0: - let optIndices = keysToIndices(node.restKeysCache, state, - keyset.toSeq()) - # Remove all the duplicates. - for item in optIndices: - # We ignore missing keys. - if item.isSome(): - indexset.incl(item.get()) - indexset.toSeq() - - let response = - block: - var res: seq[RestValidatorBalance] - 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 is no indices, so we going to return balances of all - # known validators. - for index, balance in getStateField(state, balances): - res.add(RestValidatorBalance.init(ValidatorIndex(index), - balance)) - else: - for index in indices: - let balance = getStateField(state, balances).item(index) - res.add(RestValidatorBalance.init(index, balance)) - res - return RestApiResponse.jsonResponseFinalized( - response, - node.getStateOptimistic(state), - node.dag.isFinalized(bslot.bid) - ) + if id.isErr(): + return RestApiResponse.jsonError( + Http400, InvalidValidatorIdValueError) + let ires = id.get() + if len(ires) > ServerMaximumValidatorIds: + return RestApiResponse.jsonError( + Http400, MaximumNumberOfValidatorIdsError) + ires + getBalances(node, bslot, validatorIds) - return RestApiResponse.jsonError(Http404, StateNotFoundError) + # https://ethereum.github.io/beacon-APIs/#/Beacon/postStateValidatorBalances + router.api(MethodPost, + "/eth/v1/beacon/states/{state_id}/validator_balances") 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) + getBalances(node, bslot, validatorIds) # https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees router.api(MethodGet, diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index 49819d7233..f0dc219e44 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -26,6 +26,8 @@ const "Block header/data has not been found" EmptyRequestBodyError* = "Empty request's body" + InvalidRequestBodyError* = + "Invalid request's body" InvalidBlockObjectError* = "Unable to decode block object(s)" InvalidAttestationObjectError* = diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 9fdf7ce8bf..d5ba40e02c 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -3758,6 +3758,45 @@ proc encodeString*(value: set[EventTopic]): Result[string, cstring] = res.setLen(len(res) - 1) ok(res) +proc toList*(value: set[ValidatorFilterKind]): seq[string] = + const + pendingSet = {ValidatorFilterKind.PendingInitialized, + ValidatorFilterKind.PendingQueued} + activeSet = {ValidatorFilterKind.ActiveOngoing, + ValidatorFilterKind.ActiveExiting, + ValidatorFilterKind.ActiveSlashed} + exitedSet = {ValidatorFilterKind.ExitedUnslashed, + ValidatorFilterKind.ExitedSlashed} + withdrawSet = {ValidatorFilterKind.WithdrawalPossible, + ValidatorFilterKind.WithdrawalDone} + var + res: seq[string] + v = value + + if pendingSet * v != {}: + res.add("pending") + v.excl(pendingSet) + if activeSet * v != {}: + res.add("active") + v.excl(activeSet) + if exitedSet * v != {}: + res.add("exited") + v.excl(exitedSet) + if withdrawSet * v != {}: + res.add("withdrawal") + v.excl(withdrawSet) + + if ValidatorFilterKind.PendingInitialized in v: res.add("pending_initialized") + if ValidatorFilterKind.PendingQueued in v: res.add("pending_queued") + if ValidatorFilterKind.ActiveOngoing in v: res.add("active_ongoing") + if ValidatorFilterKind.ActiveExiting in v: res.add("active_exiting") + if ValidatorFilterKind.ActiveSlashed in v: res.add("active_slashed") + if ValidatorFilterKind.ExitedUnslashed in v: res.add("exited_unslashed") + if ValidatorFilterKind.ExitedSlashed in v: res.add("exited_slashed") + if ValidatorFilterKind.WithdrawalPossible in v: res.add("withdrawal_possible") + if ValidatorFilterKind.WithdrawalDone in v: res.add("withdrawal_done") + res + proc decodeString*(t: typedesc[ValidatorSig], value: string): Result[ValidatorSig, cstring] = if len(value) != ValidatorSigSize + 2: @@ -3982,3 +4021,82 @@ proc decodeString*(t: typedesc[EventBeaconBlockObject], allowUnknownFields = true)) except SerializationError as exc: err(exc.formatMsg("")) + +## ValidatorIdent +proc writeValue*(w: var JsonWriter[RestJson], + value: ValidatorIdent) {.raises: [IOError].} = + writeValue(w, value.encodeString().get()) + +proc readValue*(reader: var JsonReader[RestJson], + value: var ValidatorIdent) {. + raises: [IOError, SerializationError].} = + value = decodeString(ValidatorIdent, reader.readValue(string)).valueOr: + raise newException(SerializationError, $error) + +## RestValidatorRequest +proc readValue*(reader: var JsonReader[RestJson], + value: var RestValidatorRequest) {. + raises: [IOError, SerializationError].} = + var + statuses: Opt[seq[string]] + ids: Opt[seq[string]] + + for fieldName in readObjectFields(reader): + case fieldName + of "ids": + if ids.isSome(): + reader.raiseUnexpectedField("Multiple `ids` fields found", + "RestValidatorRequest") + ids = Opt.some(reader.readValue(seq[string])) + of "statuses": + if statuses.isSome(): + reader.raiseUnexpectedField("Multiple `statuses` fields found", + "RestValidatorRequest") + statuses = Opt.some(reader.readValue(seq[string])) + else: + unrecognizedFieldWarning() + + let + validatorIds = + block: + # TODO (cheatfate): According to specification, all items should be + # unique. + if ids.isSome(): + var res: seq[ValidatorIdent] + for item in ids.get(): + let value = decodeString(ValidatorIdent, item).valueOr: + reader.raiseUnexpectedValue($error) + res.add(value) + Opt.some(res) + else: + Opt.none(seq[ValidatorIdent]) + filter = + block: + if statuses.isSome(): + var res: ValidatorFilter + for item in statuses.get(): + let value = decodeString(ValidatorFilter, item).valueOr: + reader.raiseUnexpectedValue($error) + if value * res != {}: + reader.raiseUnexpectedValue( + "The `statuses` array should consist of only unique values") + res.incl(value) + Opt.some(res) + else: + Opt.none(ValidatorFilter) + + value = RestValidatorRequest(ids: validatorIds, status: filter) + +proc writeValue*(writer: var JsonWriter[RestJson], + value: RestValidatorRequest) {.raises: [IOError].} = + writer.beginRecord() + if value.ids.isSome(): + var res: seq[string] + for item in value.ids.get(): + res.add(item.encodeString().get()) + writer.writeField("ids", res) + if value.status.isSome(): + let res = value.status.get().toList() + if len(res) > 0: + writer.writeField("statuses", res) + writer.endRecord() diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index b4cf1e0c94..ee208ac630 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -121,6 +121,10 @@ type RestNumeric* = distinct int + RestValidatorRequest* = object + ids*: Opt[seq[ValidatorIdent]] + status*: Opt[ValidatorFilter] + RestAttesterDuty* = object pubkey*: ValidatorPubKey validator_index*: ValidatorIndex diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index ec19ceb581..9ac3a04a40 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -1015,6 +1015,688 @@ "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] } }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Empty request", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators"], + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"1\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators"], + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\",\"11\",\"12\",\"13\",\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\"20\",\"21\",\"22\",\"23\",\"24\",\"25\",\"26\",\"27\",\"28\",\"29\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "testnet", "post"], + "comment": "Index which not exists in state", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"2147483647\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": []}] + } + }, + { + "topics": ["beacon", "states_validators", "testnet", "post"], + "comment": "Key which is not exist in state", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": []}] + } + }, + { + "topics": ["beacon", "states_validators", "testnet", "post"], + "comment": "Index and key which not exists in state", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"2147483647\",\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": []}] + } + }, + { + "topics": ["beacon", "states_validators", "testnet", "post"], + "comment": "3 identifiers where only one exists", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"2147483647\",\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"1\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "mainnet", "post"], + "comment": "Index value equal to high(uint32) + 1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"2147483648\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "500"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 500, "message": ""}}] + } + }, + { + "topics": ["beacon", "states_validators", "mainnet", "post"], + "comment": "Index value equal to VALIDATOR_REGISTRY_LIMIT - 1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"1099511627775\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "500"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 500, "message": ""}}] + } + }, + { + "topics": ["beacon", "states_validators", "mainnet", "post"], + "comment": "Index value equal to VALIDATOR_REGISTRY_LIMIT", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"1099511627776\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators"], + "comment": "Index value which is bigger max(ValidatorIndex)", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"18446744073709551615\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators"], + "comment": "Index value which is bigger uint64", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"18446744073709551616\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Incorrect hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Incorrect hexadecimal values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x0\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Incorrect hexadecimal values #3", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x00\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Incorrect hexadecimal values #4", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0xZZ\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Correct hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct hexadecimal values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct values of different types", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004\",\"5\",\"6\",\"7\",\"8\",\"9\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #3", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #4", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #5", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\",\"active_slashed\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #6", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\",\"active_slashed\",\"exited_unslashed\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #7", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\",\"active_slashed\",\"exited_unslashed\",\"exited_slashed\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #8", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\",\"active_slashed\",\"exited_unslashed\",\"exited_slashed\",\"withdrawal_possible\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #9", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"pending_initialized\",\"pending_queued\",\"active_ongoing\",\"active_exiting\",\"active_slashed\",\"exited_unslashed\",\"exited_slashed\",\"withdrawal_possible\",\"withdrawal_done\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #10", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"active\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #11", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"active\",\"pending\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #12", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"active\",\"pending\",\"exited\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Correct status value #13", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\"],\"statuses\":[\"active\",\"pending\",\"exited\",\"withdrawal\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + } + }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Non-unique status values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"pending\",\"pending_initialized\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique status values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"active\",\"active_ongoing\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique status values #3", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"exited\",\"exited_unslashed\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique status values #4", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"withdrawal\",\"withdrawal_done\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique status values #5", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"active\",\"active\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique status values #6", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"statuses\": [\"pending_initialized\",\"pending_initialized\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validatorid"], "comment": "Correct ValidatorIndex value", @@ -1394,39 +2076,323 @@ "topics": ["beacon", "states_validator_balances"], "comment": "Correct hexadecimal values #1", "request": { - "url": "/eth/v1/beacon/states/head/validators?id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "url": "/eth/v1/beacon/states/head/validator_balances?id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": ""}]}] } }, { "topics": ["beacon", "states_validator_balances"], "comment": "Correct hexadecimal values #2", "request": { - "url": "/eth/v1/beacon/states/head/validators?id=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "url": "/eth/v1/beacon/states/head/validator_balances?id=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": ""}]}] } }, { "topics": ["beacon", "states_validator_balances"], "comment": "Correct values of different types", "request": { - "url": "/eth/v1/beacon/states/head/validators?id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004&id=5&id=6&id=7&id=8&id=9", + "url": "/eth/v1/beacon/states/head/validator_balances?id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003&id=0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004&id=5&id=6&id=7&id=8&id=9", "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": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances_slow", "slow", "post"], + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "post"], + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "post"], + "comment": "Maximum number of id[] is 30", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\",\"11\",\"12\",\"13\",\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\"20\",\"21\",\"22\",\"23\",\"24\",\"25\",\"26\",\"27\",\"28\",\"29\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "mainnet", "post"], + "comment": "Index value equal to high(int32)", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"2147483647\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": ["400", "200"]} + } + }, + { + "topics": ["beacon", "states_validator_balances", "mainnet", "post"], + "comment": "Index value equal to high(int32) + 1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"2147483648\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "500"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 500, "message": ""}}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "mainnet", "post"], + "comment": "Index value equal to VALIDATOR_REGISTRY_LIMIT - 1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"1099511627775\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "500"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 500, "message": ""}}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "mainnet", "post"], + "comment": "Index value equal to VALIDATOR_REGISTRY_LIMIT", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"1099511627776\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Index value which is bigger max(ValidatorIndex)", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"18446744073709551615\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Index value which is bigger uint64", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"18446744073709551616\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Incorrect hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Incorrect hexadecimal values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x0\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Incorrect hexadecimal values #3", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x00\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Incorrect hexadecimal values #4", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0xJJ\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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_validator_balances", "post"], + "comment": "Correct hexadecimal values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "post"], + "comment": "Correct hexadecimal values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] + } + }, + { + "topics": ["beacon", "states_validator_balances", "post"], + "comment": "Correct values of different types", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004\",\"5\",\"6\",\"7\",\"8\",\"9\"]" + }, + "url": "/eth/v1/beacon/states/head/validator_balances", + "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": "", "balance": ""}]}] } }, { From d60caf654dd3b4a0534cb12382884d1dc0b1ffd9 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Tue, 28 Nov 2023 13:27:39 +0200 Subject: [PATCH 2/7] Address review comments. Fix toList() issue. --- beacon_chain/rpc/rest_constants.nim | 4 ++-- beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index f0dc219e44..2cfd122f6a 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -25,9 +25,9 @@ const BlockNotFoundError* = "Block header/data has not been found" EmptyRequestBodyError* = - "Empty request's body" + "Empty request body" InvalidRequestBodyError* = - "Invalid request's body" + "Invalid request body" InvalidBlockObjectError* = "Unable to decode block object(s)" InvalidAttestationObjectError* = diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index d5ba40e02c..ce0197186e 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -3773,16 +3773,16 @@ proc toList*(value: set[ValidatorFilterKind]): seq[string] = res: seq[string] v = value - if pendingSet * v != {}: + if pendingSet * v == pendingSet: res.add("pending") v.excl(pendingSet) - if activeSet * v != {}: + if activeSet * v == activeSet: res.add("active") v.excl(activeSet) - if exitedSet * v != {}: + if exitedSet * v == exitedSet: res.add("exited") v.excl(exitedSet) - if withdrawSet * v != {}: + if withdrawSet * v == withdrawSet: res.add("withdrawal") v.excl(withdrawSet) From 185be251929dec1512d30304ba49867620ffdbc1 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Tue, 28 Nov 2023 14:40:17 +0200 Subject: [PATCH 3/7] Fix tests. --- ncli/resttest-rules.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 9ac3a04a40..c32657b2f2 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -4592,7 +4592,7 @@ "response": { "status": {"operator": "equals", "value": "400"}, "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": "Empty request's body"}}] + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": "Empty request body"}}] } }, { @@ -4609,7 +4609,7 @@ "response": { "status": {"operator": "equals", "value": "400"}, "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmpnsav", "value": {"code": 400, "message": "Empty request's body"}}] + "body": [{"operator": "jstructcmpnsav", "value": {"code": 400, "message": "Empty request body"}}] } }, { From af09815035d6d4fe81326e663caaa80cc1e7f9b2 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Tue, 28 Nov 2023 18:21:42 +0200 Subject: [PATCH 4/7] Address review comments 2. --- .../eth2_apis/eth2_rest_serialization.nim | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index ce0197186e..f8dfcd4ee7 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -3773,28 +3773,28 @@ proc toList*(value: set[ValidatorFilterKind]): seq[string] = res: seq[string] v = value - if pendingSet * v == pendingSet: - res.add("pending") - v.excl(pendingSet) - if activeSet * v == activeSet: - res.add("active") - v.excl(activeSet) - if exitedSet * v == exitedSet: - res.add("exited") - v.excl(exitedSet) - if withdrawSet * v == withdrawSet: - res.add("withdrawal") - v.excl(withdrawSet) - - if ValidatorFilterKind.PendingInitialized in v: res.add("pending_initialized") - if ValidatorFilterKind.PendingQueued in v: res.add("pending_queued") - if ValidatorFilterKind.ActiveOngoing in v: res.add("active_ongoing") - if ValidatorFilterKind.ActiveExiting in v: res.add("active_exiting") - if ValidatorFilterKind.ActiveSlashed in v: res.add("active_slashed") - if ValidatorFilterKind.ExitedUnslashed in v: res.add("exited_unslashed") - if ValidatorFilterKind.ExitedSlashed in v: res.add("exited_slashed") - if ValidatorFilterKind.WithdrawalPossible in v: res.add("withdrawal_possible") - if ValidatorFilterKind.WithdrawalDone in v: res.add("withdrawal_done") + template processSet(argSet, argName: untyped): untyped = + if argSet * v == argSet: + res.add(argName) + v.excl(argSet) + + template processSingle(argSingle, argName): untyped = + if argSingle in v: + res.add(argName) + + processSet(pendingSet, "pending") + processSet(activeSet, "active") + processSet(exitedSet, "exited") + processSet(withdrawSet, "withdrawal") + processSingle(ValidatorFilterKind.PendingInitialized, "pending_initialized") + processSingle(ValidatorFilterKind.PendingQueued, "pending_queued") + processSingle(ValidatorFilterKind.ActiveOngoing, "active_ongoing") + processSingle(ValidatorFilterKind.ActiveExiting, "active_exiting") + processSingle(ValidatorFilterKind.ActiveSlashed, "active_slashed") + processSingle(ValidatorFilterKind.ExitedUnslashed, "exited_unslashed") + processSingle(ValidatorFilterKind.ExitedSlashed, "exited_slashed") + processSingle(ValidatorFilterKind.WithdrawalPossible, "withdrawal_possible") + processSingle(ValidatorFilterKind.WithdrawalDone, "withdrawal_done") res proc decodeString*(t: typedesc[ValidatorSig], From 47d96ef451a6e9aff5cbe2ea61d8a5b6c7e666cc Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 29 Nov 2023 06:52:27 +0200 Subject: [PATCH 5/7] Address review comments 3. Fix unique check for validator identifiers. --- beacon_chain/rpc/rest_beacon_api.nim | 34 ++++++++------ beacon_chain/rpc/rest_constants.nim | 2 + .../eth2_apis/eth2_rest_serialization.nim | 4 +- ncli/resttest-rules.json | 44 +++++++++++++++++-- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 08dd260a4d..d011e27c83 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -242,15 +242,18 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = state: ForkedHashedBeaconState ): Result[seq[ValidatorIndex], RestErrorMessage] = var - keyset: HashSet[ValidatorPubKey] - indexset: HashSet[ValidatorIndex] + keyset: Table[ValidatorPubKey, uint] + indexset: Table[ValidatorIndex, uint] let validatorsCount = lenu64(getStateField(state, validators)) for item in validatorIds: case item.kind of ValidatorQueryKind.Key: - keyset.incl(item.key) + # Test for uniqueness of value. + if keyset.hasKeyOrPut(item.key, 0'u): + return err(RestErrorMessage.init( + Http400, NonUniqueValidatorIdError, $item.key)) of ValidatorQueryKind.Index: let vindex = item.index.toValidatorIndex().valueOr: case error @@ -261,18 +264,22 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return err(RestErrorMessage.init( Http500, UnsupportedValidatorIndexValueError)) if uint64(vindex) < validatorsCount: - # We only adding validator indices which are present in + # We're only adding validator indices which are present in # validators list at this moment. - indexset.incl(vindex) + if indexset.hasKeyOrPut(vindex, 0'u): + return err(RestErrorMessage.init( + Http400, NonUniqueValidatorIdError, + Base10.toString(uint64(vindex)))) if len(keyset) > 0: - let optIndices = keysToIndices(node.restKeysCache, state, keyset.toSeq()) + let optIndices = keysToIndices(node.restKeysCache, state, + keyset.keys().toSeq()) # Remove all the duplicates. for item in optIndices: # We ignore missing keys. if item.isSome(): - indexset.incl(item.get()) - ok(indexset.toSeq()) + indexset[item.get()] = 0'u + ok(indexset.keys().toSeq()) proc getValidators( node: BeaconNode, @@ -282,7 +289,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ): RestApiResponse = node.withStateForBlockSlotId(bslot): let - current_epoch = getStateField(state, slot).epoch() + stateEpoch = getStateField(state, slot).epoch() validatorsCount = lenu64(getStateField(state, validators)) indices = node.getIndices(validatorIds, state).valueOr: return RestApiResponse.jsonError(error) @@ -294,11 +301,12 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = # that we can't find validator identifiers in state, so we should # return empty response. if len(validatorIds) == 0: - # There is no indices, so we going to filter all the validators. + # There are no indices, so we're going to filter all the + # validators. for index, validator in getStateField(state, validators): let balance = getStateField(state, balances).item(index) - status = validator.getStatus(current_epoch).valueOr: + status = validator.getStatus(stateEpoch).valueOr: return RestApiResponse.jsonError( Http400, ValidatorStatusNotFoundError, $error) if status in validatorsMask: @@ -309,7 +317,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = let validator = getStateField(state, validators).item(index) balance = getStateField(state, balances).item(index) - status = validator.getStatus(current_epoch).valueOr: + status = validator.getStatus(stateEpoch).valueOr: return RestApiResponse.jsonError( Http400, ValidatorStatusNotFoundError, $error) if status in validatorsMask: @@ -341,7 +349,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = # that we can't find validator identifiers in state, so we should # return empty response. if len(validatorIds) == 0: - # There is no indices, so we going to return balances of all + # There are no indices, so we're going to return balances of all # known validators. for index, balance in getStateField(state, balances): res.add(RestValidatorBalance.init(ValidatorIndex(index), diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index 2cfd122f6a..2366c60b79 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -103,6 +103,8 @@ const "Invalid block identifier value" InvalidValidatorIdValueError* = "Invalid validator's identifier value(s)" + NonUniqueValidatorIdError* = + "Non-unique validator identifier value(s)" MaximumNumberOfValidatorIdsError* = "Maximum number of validator identifier values exceeded" InvalidValidatorStatusValueError* = diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index f8dfcd4ee7..8fd857711f 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -4059,8 +4059,7 @@ proc readValue*(reader: var JsonReader[RestJson], let validatorIds = block: - # TODO (cheatfate): According to specification, all items should be - # unique. + # Test for uniqueness of value will be happened on higher layer. if ids.isSome(): var res: seq[ValidatorIdent] for item in ids.get(): @@ -4077,6 +4076,7 @@ proc readValue*(reader: var JsonReader[RestJson], for item in statuses.get(): let value = decodeString(ValidatorFilter, item).valueOr: reader.raiseUnexpectedValue($error) + # Test for uniqueness of value. if value * res != {}: reader.raiseUnexpectedValue( "The `statuses` array should consist of only unique values") diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index c32657b2f2..772068f102 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -919,9 +919,9 @@ "headers": {"Accept": "application/json"} }, "response": { - "status": {"operator": "equals", "value": "200"}, + "status": {"operator": "equals", "value": "400"}, "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] } }, { @@ -932,9 +932,9 @@ "headers": {"Accept": "application/json"} }, "response": { - "status": {"operator": "equals", "value": "200"}, + "status": {"operator": "equals", "value": "400"}, "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] } }, { @@ -1589,6 +1589,42 @@ "body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}] } }, + { + "topics": ["beacon", "states_validators", "post"], + "comment": "Non-unique id values #1", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0\",\"0\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], + "comment": "Non-unique id values #2", + "request": { + "method": "POST", + "body": { + "content-type": "application/json", + "data": "{\"ids\":[\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"]}" + }, + "url": "/eth/v1/beacon/states/head/validators", + "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_validators", "post"], "comment": "Non-unique status values #1", From f75a1ed52ee3e1f618904b368fe1815145c9c860 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 29 Nov 2023 07:59:53 +0200 Subject: [PATCH 6/7] Address review comments. --- beacon_chain/rpc/rest_beacon_api.nim | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index d011e27c83..16cb88ed1b 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -242,8 +242,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = state: ForkedHashedBeaconState ): Result[seq[ValidatorIndex], RestErrorMessage] = var - keyset: Table[ValidatorPubKey, uint] - indexset: Table[ValidatorIndex, uint] + keyset: HashSet[ValidatorPubKey] + indexset: HashSet[ValidatorIndex] let validatorsCount = lenu64(getStateField(state, validators)) @@ -251,7 +251,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = case item.kind of ValidatorQueryKind.Key: # Test for uniqueness of value. - if keyset.hasKeyOrPut(item.key, 0'u): + if keyset.containsOrIncl(item.key): return err(RestErrorMessage.init( Http400, NonUniqueValidatorIdError, $item.key)) of ValidatorQueryKind.Index: @@ -266,20 +266,19 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = if uint64(vindex) < validatorsCount: # We're only adding validator indices which are present in # validators list at this moment. - if indexset.hasKeyOrPut(vindex, 0'u): + if indexset.containsOrIncl(vindex): return err(RestErrorMessage.init( Http400, NonUniqueValidatorIdError, Base10.toString(uint64(vindex)))) if len(keyset) > 0: - let optIndices = keysToIndices(node.restKeysCache, state, - keyset.keys().toSeq()) + let optIndices = keysToIndices(node.restKeysCache, state, keyset.toSeq()) # Remove all the duplicates. for item in optIndices: # We ignore missing keys. if item.isSome(): - indexset[item.get()] = 0'u - ok(indexset.keys().toSeq()) + indexset.incl(item.get()) + ok(indexset.toSeq()) proc getValidators( node: BeaconNode, From efc9202c7890ca455872231e60b623d68b3334af Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 29 Nov 2023 08:03:19 +0200 Subject: [PATCH 7/7] Fix constant value. --- beacon_chain/rpc/rest_constants.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index 2366c60b79..5df072a337 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -102,7 +102,7 @@ const InvalidBlockIdValueError* = "Invalid block identifier value" InvalidValidatorIdValueError* = - "Invalid validator's identifier value(s)" + "Invalid validator identifier value(s)" NonUniqueValidatorIdError* = "Non-unique validator identifier value(s)" MaximumNumberOfValidatorIdsError* =