From 816276dfb8ef8af523a6c8d491eda494ec1d115c Mon Sep 17 00:00:00 2001 From: kdeme Date: Wed, 23 Oct 2024 22:58:35 +0200 Subject: [PATCH 1/3] Add REST endpoint to retrieve historical_summaries --- beacon_chain/rpc/rest_constants.nim | 2 + beacon_chain/rpc/rest_debug_api.nim | 49 +++++++++++++++++ .../eth2_apis/eth2_rest_serialization.nim | 3 ++ .../spec/eth2_apis/rest_debug_calls.nim | 53 +++++++++++++++++++ beacon_chain/spec/eth2_apis/rest_types.nim | 10 ++++ 5 files changed, 117 insertions(+) diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index ca1f8a510b..66e4b26abd 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -269,3 +269,5 @@ const "Unable to load state for parent block, database corrupt?" RewardOverflowError* = "Reward value overflow" + HistoricalSummariesUnavailable* = + "Historical summaries unavailable" diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index 1013c09e75..cb4a305529 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -138,3 +138,52 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = bestDescendant: item.bestDescendant)) RestApiResponse.jsonResponsePlain(response) + + router.metricsApi2( + MethodGet, + "/eth/v1/debug/beacon/states/{state_id}/historical_summaries", + {RestServerMetricsType.Status, Response}, + ) do(state_id: StateIdent) -> RestApiResponse: + let + sid = state_id.valueOr: + return RestApiResponse.jsonError(Http400, InvalidStateIdValueError, $error) + bslot = node.getBlockSlotId(sid).valueOr: + return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) + contentType = preferredContentType(jsonMediaType, sszMediaType).valueOr: + return RestApiResponse.jsonError(Http406, ContentNotAcceptableError) + + node.withStateForBlockSlotId(bslot): + return withState(state): + when consensusFork >= ConsensusFork.Capella: + # Build the proof for historical_summaries field (28th field in BeaconState) + let gIndex = GeneralizedIndex(59) # 31 + 28 = 59 + var proof: array[5, Digest] + if forkyState.data.build_proof(gIndex, proof).isErr: + return RestApiResponse.jsonError(Http500, InvalidMerkleProofIndexError) + + if contentType == jsonMediaType: + let response = RestHistoricalSummaries( + historical_summaries: forkyState.data.historical_summaries.asSeq(), + proof: proof, + slot: bslot.slot, + ) + + RestApiResponse.jsonResponseFinalized( + response, node.getStateOptimistic(state), node.dag.isFinalized(bslot.bid) + ) + elif contentType == sszMediaType: + let + headers = [("eth-consensus-version", consensusFork.toString())] + response = GetHistoricalSummariesV1Response( + historical_summaries: forkyState.data.historical_summaries, + proof: proof, + slot: bslot.slot, + ) + + RestApiResponse.sszResponse(response, headers) + else: + RestApiResponse.jsonError(Http500, InvalidAcceptError) + else: + RestApiResponse.jsonError(Http404, HistoricalSummariesUnavailable) + + RestApiResponse.jsonError(Http404, StateNotFoundError) diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 8d51372d0a..cb25a0aeb2 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -83,6 +83,7 @@ RestJson.useDefaultSerializationFor( GetGenesisResponse, GetHeaderResponseDeneb, GetHeaderResponseElectra, + GetHistoricalSummariesV1Response, GetKeystoresResponse, GetNextWithdrawalsResponse, GetPoolAttesterSlashingsResponse, @@ -131,6 +132,7 @@ RestJson.useDefaultSerializationFor( RestEpochSyncCommittee, RestExtraData, RestGenesis, + RestHistoricalSummaries, RestIndexedErrorMessage, RestIndexedErrorMessageItem, RestMetadata, @@ -384,6 +386,7 @@ type DataOptimisticAndFinalizedObject | GetBlockV2Response | GetDistributedKeystoresResponse | + GetHistoricalSummariesV1Response | GetKeystoresResponse | GetRemoteKeystoresResponse | GetStateForkResponse | diff --git a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim index c5c517d623..5304da9a73 100644 --- a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim @@ -73,3 +73,56 @@ proc getStateV2*(client: RestClientRef, state_id: StateIdent, msg: msg, status: error.code, message: error.message) else: raiseRestResponseError(resp) + +proc getHistoricalSummariesV1Plain*( + state_id: StateIdent +): RestPlainResponse {. + rest, + endpoint: "/eth/v1/debug/beacon/states/{state_id}/historical_summaries", + accept: preferSSZ, + meth: MethodGet +.} + +proc getHistoricalSummariesV1*( + client: RestClientRef, state_id: StateIdent, cfg: RuntimeConfig, restAccept = "" +): Future[Option[GetHistoricalSummariesV1Response]] {.async.} = + let resp = + if len(restAccept) > 0: + await client.getHistoricalSummariesV1Plain(state_id, restAcceptType = restAccept) + else: + await client.getHistoricalSummariesV1Plain(state_id) + + return + case resp.status + of 200: + if resp.contentType.isNone() or isWildCard(resp.contentType.get().mediaType): + raise newException(RestError, "Missing or incorrect Content-Type") + else: + let mediaType = resp.contentType.get().mediaType + if mediaType == ApplicationJsonMediaType: + let summaries = decodeBytes( + GetHistoricalSummariesV1Response, resp.data, resp.contentType + ).valueOr: + raise newException(RestError, $error) + some(summaries) + elif mediaType == OctetStreamMediaType: + let summaries = + try: + SSZ.decode(resp.data, GetHistoricalSummariesV1Response) + except SerializationError as exc: + raise newException(RestError, exc.msg) + some(summaries) + else: + raise newException(RestError, "Unsupported Content-Type") + of 404: + none(GetHistoricalSummariesV1Response) + of 400, 500: + let error = decodeBytes(RestErrorMessage, resp.data, resp.contentType).valueOr: + let msg = + "Incorrect response error format (" & $resp.status & ") [" & $error & "]" + raise (ref RestResponseError)(msg: msg, status: resp.status) + let msg = "Error response (" & $resp.status & ") [" & error.message & "]" + raise + (ref RestResponseError)(msg: msg, status: error.code, message: error.message) + else: + raiseRestResponseError(resp) diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 9ba40f4f46..6702756285 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -337,6 +337,11 @@ type RestEpochRandao* = object randao*: Eth2Digest + RestHistoricalSummaries* = object + historical_summaries*: seq[HistoricalSummary] + proof*: array[5, Eth2Digest] + slot*: Slot + DataEnclosedObject*[T] = object data*: T @@ -469,6 +474,11 @@ type GetStateV2Response* = ref ForkedHashedBeaconState GetAggregatedAttestationV2Response* = ForkedAttestation + GetHistoricalSummariesV1Response* = object + historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT] + proof*: array[5, Eth2Digest] + slot*: Slot + RestRoot* = object root*: Eth2Digest From 39d1d8272c608db39cb80a8ddec9105e1a2c74eb Mon Sep 17 00:00:00 2001 From: kdeme Date: Thu, 24 Oct 2024 12:24:12 +0200 Subject: [PATCH 2/3] Adress review comments: Correct errors + asyncraises --- .../spec/eth2_apis/rest_debug_calls.nim | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim index 5304da9a73..8592b3eaf3 100644 --- a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim @@ -85,7 +85,14 @@ proc getHistoricalSummariesV1Plain*( proc getHistoricalSummariesV1*( client: RestClientRef, state_id: StateIdent, cfg: RuntimeConfig, restAccept = "" -): Future[Option[GetHistoricalSummariesV1Response]] {.async.} = +): Future[Option[GetHistoricalSummariesV1Response]] {. + async: ( + raises: [ + CancelledError, RestEncodingError, RestDnsResolveError, RestCommunicationError, + RestDecodingError, RestResponseError, + ] + ) +.} = let resp = if len(restAccept) > 0: await client.getHistoricalSummariesV1Plain(state_id, restAcceptType = restAccept) @@ -96,24 +103,24 @@ proc getHistoricalSummariesV1*( case resp.status of 200: if resp.contentType.isNone() or isWildCard(resp.contentType.get().mediaType): - raise newException(RestError, "Missing or incorrect Content-Type") + raise newException(RestDecodingError, "Missing or incorrect Content-Type") else: let mediaType = resp.contentType.get().mediaType if mediaType == ApplicationJsonMediaType: let summaries = decodeBytes( GetHistoricalSummariesV1Response, resp.data, resp.contentType ).valueOr: - raise newException(RestError, $error) + raise newException(RestDecodingError, $error) some(summaries) elif mediaType == OctetStreamMediaType: let summaries = try: SSZ.decode(resp.data, GetHistoricalSummariesV1Response) except SerializationError as exc: - raise newException(RestError, exc.msg) + raise newException(RestDecodingError, exc.msg) some(summaries) else: - raise newException(RestError, "Unsupported Content-Type") + raise newException(RestDecodingError, "Unsupported Content-Type") of 404: none(GetHistoricalSummariesV1Response) of 400, 500: From 566e69b1c44e4f271af75f6a7a7df6c0498890da Mon Sep 17 00:00:00 2001 From: kdeme Date: Thu, 24 Oct 2024 14:46:57 +0200 Subject: [PATCH 3/3] Move endpoint to /nimbus/v1/debug/ namespace for now --- beacon_chain/rpc/rest_debug_api.nim | 49 --------------- beacon_chain/rpc/rest_nimbus_api.nim | 49 +++++++++++++++ .../spec/eth2_apis/rest_debug_calls.nim | 60 ------------------- .../spec/eth2_apis/rest_nimbus_calls.nim | 60 +++++++++++++++++++ 4 files changed, 109 insertions(+), 109 deletions(-) diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index cb4a305529..1013c09e75 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -138,52 +138,3 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = bestDescendant: item.bestDescendant)) RestApiResponse.jsonResponsePlain(response) - - router.metricsApi2( - MethodGet, - "/eth/v1/debug/beacon/states/{state_id}/historical_summaries", - {RestServerMetricsType.Status, Response}, - ) do(state_id: StateIdent) -> RestApiResponse: - let - sid = state_id.valueOr: - return RestApiResponse.jsonError(Http400, InvalidStateIdValueError, $error) - bslot = node.getBlockSlotId(sid).valueOr: - return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) - contentType = preferredContentType(jsonMediaType, sszMediaType).valueOr: - return RestApiResponse.jsonError(Http406, ContentNotAcceptableError) - - node.withStateForBlockSlotId(bslot): - return withState(state): - when consensusFork >= ConsensusFork.Capella: - # Build the proof for historical_summaries field (28th field in BeaconState) - let gIndex = GeneralizedIndex(59) # 31 + 28 = 59 - var proof: array[5, Digest] - if forkyState.data.build_proof(gIndex, proof).isErr: - return RestApiResponse.jsonError(Http500, InvalidMerkleProofIndexError) - - if contentType == jsonMediaType: - let response = RestHistoricalSummaries( - historical_summaries: forkyState.data.historical_summaries.asSeq(), - proof: proof, - slot: bslot.slot, - ) - - RestApiResponse.jsonResponseFinalized( - response, node.getStateOptimistic(state), node.dag.isFinalized(bslot.bid) - ) - elif contentType == sszMediaType: - let - headers = [("eth-consensus-version", consensusFork.toString())] - response = GetHistoricalSummariesV1Response( - historical_summaries: forkyState.data.historical_summaries, - proof: proof, - slot: bslot.slot, - ) - - RestApiResponse.sszResponse(response, headers) - else: - RestApiResponse.jsonError(Http500, InvalidAcceptError) - else: - RestApiResponse.jsonError(Http404, HistoricalSummariesUnavailable) - - RestApiResponse.jsonError(Http404, StateNotFoundError) diff --git a/beacon_chain/rpc/rest_nimbus_api.nim b/beacon_chain/rpc/rest_nimbus_api.nim index 027ae72a62..09be4932c5 100644 --- a/beacon_chain/rpc/rest_nimbus_api.nim +++ b/beacon_chain/rpc/rest_nimbus_api.nim @@ -531,3 +531,52 @@ proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) = delay: uint64(delay.nanoseconds) ) RestApiResponse.jsonResponsePlain(response) + + router.metricsApi2( + MethodGet, + "/nimbus/v1/debug/beacon/states/{state_id}/historical_summaries", + {RestServerMetricsType.Status, Response}, + ) do(state_id: StateIdent) -> RestApiResponse: + let + sid = state_id.valueOr: + return RestApiResponse.jsonError(Http400, InvalidStateIdValueError, $error) + bslot = node.getBlockSlotId(sid).valueOr: + return RestApiResponse.jsonError(Http404, StateNotFoundError, $error) + contentType = preferredContentType(jsonMediaType, sszMediaType).valueOr: + return RestApiResponse.jsonError(Http406, ContentNotAcceptableError) + + node.withStateForBlockSlotId(bslot): + return withState(state): + when consensusFork >= ConsensusFork.Capella: + # Build the proof for historical_summaries field (28th field in BeaconState) + let gIndex = GeneralizedIndex(59) # 31 + 28 = 59 + var proof: array[5, Digest] + if forkyState.data.build_proof(gIndex, proof).isErr: + return RestApiResponse.jsonError(Http500, InvalidMerkleProofIndexError) + + if contentType == jsonMediaType: + let response = RestHistoricalSummaries( + historical_summaries: forkyState.data.historical_summaries.asSeq(), + proof: proof, + slot: bslot.slot, + ) + + RestApiResponse.jsonResponseFinalized( + response, node.getStateOptimistic(state), node.dag.isFinalized(bslot.bid) + ) + elif contentType == sszMediaType: + let + headers = [("eth-consensus-version", consensusFork.toString())] + response = GetHistoricalSummariesV1Response( + historical_summaries: forkyState.data.historical_summaries, + proof: proof, + slot: bslot.slot, + ) + + RestApiResponse.sszResponse(response, headers) + else: + RestApiResponse.jsonError(Http500, InvalidAcceptError) + else: + RestApiResponse.jsonError(Http404, HistoricalSummariesUnavailable) + + RestApiResponse.jsonError(Http404, StateNotFoundError) diff --git a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim index 8592b3eaf3..c5c517d623 100644 --- a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim @@ -73,63 +73,3 @@ proc getStateV2*(client: RestClientRef, state_id: StateIdent, msg: msg, status: error.code, message: error.message) else: raiseRestResponseError(resp) - -proc getHistoricalSummariesV1Plain*( - state_id: StateIdent -): RestPlainResponse {. - rest, - endpoint: "/eth/v1/debug/beacon/states/{state_id}/historical_summaries", - accept: preferSSZ, - meth: MethodGet -.} - -proc getHistoricalSummariesV1*( - client: RestClientRef, state_id: StateIdent, cfg: RuntimeConfig, restAccept = "" -): Future[Option[GetHistoricalSummariesV1Response]] {. - async: ( - raises: [ - CancelledError, RestEncodingError, RestDnsResolveError, RestCommunicationError, - RestDecodingError, RestResponseError, - ] - ) -.} = - let resp = - if len(restAccept) > 0: - await client.getHistoricalSummariesV1Plain(state_id, restAcceptType = restAccept) - else: - await client.getHistoricalSummariesV1Plain(state_id) - - return - case resp.status - of 200: - if resp.contentType.isNone() or isWildCard(resp.contentType.get().mediaType): - raise newException(RestDecodingError, "Missing or incorrect Content-Type") - else: - let mediaType = resp.contentType.get().mediaType - if mediaType == ApplicationJsonMediaType: - let summaries = decodeBytes( - GetHistoricalSummariesV1Response, resp.data, resp.contentType - ).valueOr: - raise newException(RestDecodingError, $error) - some(summaries) - elif mediaType == OctetStreamMediaType: - let summaries = - try: - SSZ.decode(resp.data, GetHistoricalSummariesV1Response) - except SerializationError as exc: - raise newException(RestDecodingError, exc.msg) - some(summaries) - else: - raise newException(RestDecodingError, "Unsupported Content-Type") - of 404: - none(GetHistoricalSummariesV1Response) - of 400, 500: - let error = decodeBytes(RestErrorMessage, resp.data, resp.contentType).valueOr: - let msg = - "Incorrect response error format (" & $resp.status & ") [" & $error & "]" - raise (ref RestResponseError)(msg: msg, status: resp.status) - let msg = "Error response (" & $resp.status & ") [" & error.message & "]" - raise - (ref RestResponseError)(msg: msg, status: error.code, message: error.message) - else: - raiseRestResponseError(resp) diff --git a/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim b/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim index 3de4ceded1..3c488d475d 100644 --- a/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim @@ -76,3 +76,63 @@ proc getTimeOffset*(client: RestClientRef, let msg = "Error response (" & $resp.status & ") [" & error.message & "]" raise (ref RestResponseError)( msg: msg, status: error.code, message: error.message) + +proc getHistoricalSummariesV1Plain*( + state_id: StateIdent +): RestPlainResponse {. + rest, + endpoint: "/nimbus/v1/debug/beacon/states/{state_id}/historical_summaries", + accept: preferSSZ, + meth: MethodGet +.} + +proc getHistoricalSummariesV1*( + client: RestClientRef, state_id: StateIdent, cfg: RuntimeConfig, restAccept = "" +): Future[Option[GetHistoricalSummariesV1Response]] {. + async: ( + raises: [ + CancelledError, RestEncodingError, RestDnsResolveError, RestCommunicationError, + RestDecodingError, RestResponseError, + ] + ) +.} = + let resp = + if len(restAccept) > 0: + await client.getHistoricalSummariesV1Plain(state_id, restAcceptType = restAccept) + else: + await client.getHistoricalSummariesV1Plain(state_id) + + return + case resp.status + of 200: + if resp.contentType.isNone() or isWildCard(resp.contentType.get().mediaType): + raise newException(RestDecodingError, "Missing or incorrect Content-Type") + else: + let mediaType = resp.contentType.get().mediaType + if mediaType == ApplicationJsonMediaType: + let summaries = decodeBytes( + GetHistoricalSummariesV1Response, resp.data, resp.contentType + ).valueOr: + raise newException(RestDecodingError, $error) + some(summaries) + elif mediaType == OctetStreamMediaType: + let summaries = + try: + SSZ.decode(resp.data, GetHistoricalSummariesV1Response) + except SerializationError as exc: + raise newException(RestDecodingError, exc.msg) + some(summaries) + else: + raise newException(RestDecodingError, "Unsupported Content-Type") + of 404: + none(GetHistoricalSummariesV1Response) + of 400, 500: + let error = decodeBytes(RestErrorMessage, resp.data, resp.contentType).valueOr: + let msg = + "Incorrect response error format (" & $resp.status & ") [" & $error & "]" + raise (ref RestResponseError)(msg: msg, status: resp.status) + let msg = "Error response (" & $resp.status & ") [" & error.message & "]" + raise + (ref RestResponseError)(msg: msg, status: error.code, message: error.message) + else: + raiseRestResponseError(resp)