Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add REST endpoint to retrieve historical_summaries #6675

Open
wants to merge 3 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_nimbus_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this index is heavily depends on current version of BeaconState object, which means that if BeaconState object will change in next fork this index should be modified or at least visible for new fork maintainers.
So we should add something like

static: doAssert high(ConsensusFork) == ConsensusFork.Electra

here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, and also, >= Capella is wrong because Electra has different gindex than Deneb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I see in Electra the amount of fields grows > 32. Basically what I write here (#6675 (comment)) is already happening.

I will adjust index for different forks and assert for unsupported/unknown forks.

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
60 changes: 60 additions & 0 deletions beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment #6675 (comment) means also that this will become array[6, Eth2Digest] for Electra. For the JSON part that is not really an issue. In Nim this means I will need to either create different versions of RestHistoricalSummaries depending on fork or perhap use seq instead.

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
Loading