Skip to content

Commit

Permalink
Add REST endpoint to retrieve historical_summaries
Browse files Browse the repository at this point in the history
  • Loading branch information
kdeme committed Oct 24, 2024
1 parent 8a6eab7 commit 6d1428a
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
2 changes: 2 additions & 0 deletions beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,5 @@ const
"Unable to load state for parent block, database corrupt?"
RewardOverflowError* =
"Reward value overflow"
HistoricalSummariesUnavailable* =
"Historical summaries unavailable"
49 changes: 49 additions & 0 deletions beacon_chain/rpc/rest_debug_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ RestJson.useDefaultSerializationFor(
GetGenesisResponse,
GetHeaderResponseDeneb,
GetHeaderResponseElectra,
GetHistoricalSummariesV1Response,
GetKeystoresResponse,
GetNextWithdrawalsResponse,
GetPoolAttesterSlashingsResponse,
Expand Down Expand Up @@ -131,6 +132,7 @@ RestJson.useDefaultSerializationFor(
RestEpochSyncCommittee,
RestExtraData,
RestGenesis,
RestHistoricalSummaries,
RestIndexedErrorMessage,
RestIndexedErrorMessageItem,
RestMetadata,
Expand Down Expand Up @@ -384,6 +386,7 @@ type
DataOptimisticAndFinalizedObject |
GetBlockV2Response |
GetDistributedKeystoresResponse |
GetHistoricalSummariesV1Response |
GetKeystoresResponse |
GetRemoteKeystoresResponse |
GetStateForkResponse |
Expand Down
53 changes: 53 additions & 0 deletions beacon_chain/spec/eth2_apis/rest_debug_calls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
10 changes: 10 additions & 0 deletions beacon_chain/spec/eth2_apis/rest_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 6d1428a

Please sign in to comment.