Skip to content

Commit e123f65

Browse files
committed
Adjust historical_summaries REST endpoint to deal with forks
1 parent ef83fbe commit e123f65

File tree

4 files changed

+199
-52
lines changed

4 files changed

+199
-52
lines changed

Diff for: beacon_chain/rpc/rest_nimbus_api.nim

+37-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2024 Status Research & Development GmbH
2+
# Copyright (c) 2018-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -547,32 +547,49 @@ proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) =
547547

548548
node.withStateForBlockSlotId(bslot):
549549
return withState(state):
550-
when consensusFork >= ConsensusFork.Capella:
551-
# Build the proof for historical_summaries field (28th field in BeaconState)
552-
let gIndex = GeneralizedIndex(59) # 31 + 28 = 59
553-
var proof: array[5, Digest]
554-
if forkyState.data.build_proof(gIndex, proof).isErr:
550+
when consensusFork >= ConsensusFork.Electra:
551+
var proof: HistoricalSummariesProofElectra
552+
if forkyState.data.build_proof(HISTORICAL_SUMMARIES_GINDEX_ELECTRA, proof).isErr:
555553
return RestApiResponse.jsonError(Http500, InvalidMerkleProofIndexError)
556554

555+
let response = GetHistoricalSummariesV1ResponseElectra(
556+
historical_summaries: forkyState.data.historical_summaries,
557+
proof: proof,
558+
slot: bslot.slot,
559+
)
560+
557561
if contentType == jsonMediaType:
558-
let response = RestHistoricalSummaries(
559-
historical_summaries: forkyState.data.historical_summaries.asSeq(),
560-
proof: proof,
561-
slot: bslot.slot,
562+
RestApiResponse.jsonResponseFinalizedWVersion(
563+
response,
564+
node.getStateOptimistic(state),
565+
node.dag.isFinalized(bslot.bid),
566+
consensusFork,
562567
)
568+
elif contentType == sszMediaType:
569+
let headers = [("eth-consensus-version", consensusFork.toString())]
570+
RestApiResponse.sszResponse(response, headers)
571+
else:
572+
RestApiResponse.jsonError(Http500, InvalidAcceptError)
573+
elif consensusFork >= ConsensusFork.Capella:
574+
var proof: HistoricalSummariesProof
575+
if forkyState.data.build_proof(HISTORICAL_SUMMARIES_GINDEX, proof).isErr:
576+
return RestApiResponse.jsonError(Http500, InvalidMerkleProofIndexError)
577+
578+
let response = GetHistoricalSummariesV1Response(
579+
historical_summaries: forkyState.data.historical_summaries,
580+
proof: proof,
581+
slot: bslot.slot,
582+
)
563583

564-
RestApiResponse.jsonResponseFinalized(
565-
response, node.getStateOptimistic(state), node.dag.isFinalized(bslot.bid)
584+
if contentType == jsonMediaType:
585+
RestApiResponse.jsonResponseFinalizedWVersion(
586+
response,
587+
node.getStateOptimistic(state),
588+
node.dag.isFinalized(bslot.bid),
589+
consensusFork,
566590
)
567591
elif contentType == sszMediaType:
568-
let
569-
headers = [("eth-consensus-version", consensusFork.toString())]
570-
response = GetHistoricalSummariesV1Response(
571-
historical_summaries: forkyState.data.historical_summaries,
572-
proof: proof,
573-
slot: bslot.slot,
574-
)
575-
592+
let headers = [("eth-consensus-version", consensusFork.toString())]
576593
RestApiResponse.sszResponse(response, headers)
577594
else:
578595
RestApiResponse.jsonError(Http500, InvalidAcceptError)

Diff for: beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ RestJson.useDefaultSerializationFor(
8585
GetHeaderResponseDeneb,
8686
GetHeaderResponseElectra,
8787
GetHistoricalSummariesV1Response,
88+
GetHistoricalSummariesV1ResponseElectra,
8889
GetKeystoresResponse,
8990
GetNextWithdrawalsResponse,
9091
GetPoolAttesterSlashingsResponse,
@@ -133,7 +134,6 @@ RestJson.useDefaultSerializationFor(
133134
RestEpochSyncCommittee,
134135
RestExtraData,
135136
RestGenesis,
136-
RestHistoricalSummaries,
137137
RestIndexedErrorMessage,
138138
RestIndexedErrorMessageItem,
139139
RestMetadata,
@@ -404,6 +404,7 @@ type
404404
GetBlockV2Response |
405405
GetDistributedKeystoresResponse |
406406
GetHistoricalSummariesV1Response |
407+
GetHistoricalSummariesV1ResponseElectra |
407408
GetKeystoresResponse |
408409
GetRemoteKeystoresResponse |
409410
GetStateForkResponse |

Diff for: beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim

+73-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2024 Status Research & Development GmbH
2+
# Copyright (c) 2018-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -77,6 +77,64 @@ proc getTimeOffset*(client: RestClientRef,
7777
raise (ref RestResponseError)(
7878
msg: msg, status: error.code, message: error.message)
7979

80+
func decodeSszResponse(
81+
T: type ForkedHistoricalSummariesWithProof,
82+
data: openArray[byte],
83+
consensusFork: ConsensusFork,
84+
cfg: RuntimeConfig,
85+
): T {.raises: [RestDecodingError].} =
86+
if consensusFork >= ConsensusFork.Electra:
87+
let summaries =
88+
try:
89+
SSZ.decode(data, GetHistoricalSummariesV1ResponseElectra)
90+
except SerializationError as exc:
91+
raise newException(RestDecodingError, exc.msg)
92+
ForkedHistoricalSummariesWithProof.init(summaries, consensusFork)
93+
elif consensusFork >= ConsensusFork.Capella:
94+
let summaries =
95+
try:
96+
SSZ.decode(data, GetHistoricalSummariesV1Response)
97+
except SerializationError as exc:
98+
raise newException(RestDecodingError, exc.msg)
99+
ForkedHistoricalSummariesWithProof.init(summaries, consensusFork)
100+
else:
101+
raiseRestDecodingBytesError(cstring("Unsupported fork: " & $consensusFork))
102+
103+
proc decodeJsonResponse(
104+
T: type ForkedHistoricalSummariesWithProof,
105+
data: openArray[byte],
106+
consensusFork: ConsensusFork,
107+
cfg: RuntimeConfig,
108+
): T {.raises: [RestDecodingError].} =
109+
if consensusFork >= ConsensusFork.Electra:
110+
let summaries = decodeBytes(
111+
GetHistoricalSummariesV1ResponseElectra, data, Opt.none(ContentTypeData)
112+
).valueOr:
113+
raise newException(RestDecodingError, $error)
114+
ForkedHistoricalSummariesWithProof.init(summaries, consensusFork)
115+
elif consensusFork >= ConsensusFork.Capella:
116+
let summaries = decodeBytes(
117+
GetHistoricalSummariesV1Response, data, Opt.none(ContentTypeData)
118+
).valueOr:
119+
raise newException(RestDecodingError, $error)
120+
ForkedHistoricalSummariesWithProof.init(summaries, consensusFork)
121+
else:
122+
raiseRestDecodingBytesError(cstring("Unsupported fork: " & $consensusFork))
123+
124+
proc decodeHttpResponse(
125+
T: type ForkedHistoricalSummariesWithProof,
126+
data: openArray[byte],
127+
mediaType: MediaType,
128+
consensusFork: ConsensusFork,
129+
cfg: RuntimeConfig,
130+
): T {.raises: [RestDecodingError].} =
131+
if mediaType == OctetStreamMediaType:
132+
ForkedHistoricalSummariesWithProof.decodeSszResponse(data, consensusFork, cfg)
133+
elif mediaType == ApplicationJsonMediaType:
134+
ForkedHistoricalSummariesWithProof.decodeJsonResponse(data, consensusFork, cfg)
135+
else:
136+
raise newException(RestDecodingError, "Unsupported content-type")
137+
80138
proc getHistoricalSummariesV1Plain*(
81139
state_id: StateIdent
82140
): RestPlainResponse {.
@@ -88,7 +146,7 @@ proc getHistoricalSummariesV1Plain*(
88146

89147
proc getHistoricalSummariesV1*(
90148
client: RestClientRef, state_id: StateIdent, cfg: RuntimeConfig, restAccept = ""
91-
): Future[Option[GetHistoricalSummariesV1Response]] {.
149+
): Future[Opt[ForkedHistoricalSummariesWithProof]] {.
92150
async: (
93151
raises: [
94152
CancelledError, RestEncodingError, RestDnsResolveError, RestCommunicationError,
@@ -108,24 +166,20 @@ proc getHistoricalSummariesV1*(
108166
if resp.contentType.isNone() or isWildCard(resp.contentType.get().mediaType):
109167
raise newException(RestDecodingError, "Missing or incorrect Content-Type")
110168
else:
111-
let mediaType = resp.contentType.get().mediaType
112-
if mediaType == ApplicationJsonMediaType:
113-
let summaries = decodeBytes(
114-
GetHistoricalSummariesV1Response, resp.data, resp.contentType
169+
let
170+
consensusFork = ConsensusFork.decodeString(
171+
resp.headers.getString("eth-consensus-version")
115172
).valueOr:
116-
raise newException(RestDecodingError, $error)
117-
some(summaries)
118-
elif mediaType == OctetStreamMediaType:
119-
let summaries =
120-
try:
121-
SSZ.decode(resp.data, GetHistoricalSummariesV1Response)
122-
except SerializationError as exc:
123-
raise newException(RestDecodingError, exc.msg)
124-
some(summaries)
125-
else:
126-
raise newException(RestDecodingError, "Unsupported Content-Type")
173+
raiseRestDecodingBytesError(error)
174+
mediaType = resp.contentType.value().mediaType
175+
176+
Opt.some(
177+
ForkedHistoricalSummariesWithProof.decodeHttpResponse(
178+
resp.data, mediaType, consensusFork, cfg
179+
)
180+
)
127181
of 404:
128-
none(GetHistoricalSummariesV1Response)
182+
Opt.none(ForkedHistoricalSummariesWithProof)
129183
of 400, 500:
130184
let error = decodeBytes(RestErrorMessage, resp.data, resp.contentType).valueOr:
131185
let msg =
@@ -135,4 +189,4 @@ proc getHistoricalSummariesV1*(
135189
raise
136190
(ref RestResponseError)(msg: msg, status: error.code, message: error.message)
137191
else:
138-
raiseRestResponseError(resp)
192+
raiseRestResponseError(resp)

Diff for: beacon_chain/spec/eth2_apis/rest_types.nim

+87-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2024 Status Research & Development GmbH
2+
# Copyright (c) 2018-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -15,7 +15,7 @@
1515

1616
import
1717
std/[json, tables],
18-
stew/base10, web3/primitives, httputils,
18+
stew/base10, web3/primitives, httputils, stew/bitops2,
1919
".."/[deposit_snapshots, forks],
2020
".."/mev/[deneb_mev]
2121

@@ -343,11 +343,6 @@ type
343343
RestEpochRandao* = object
344344
randao*: Eth2Digest
345345

346-
RestHistoricalSummaries* = object
347-
historical_summaries*: seq[HistoricalSummary]
348-
proof*: array[5, Eth2Digest]
349-
slot*: Slot
350-
351346
DataEnclosedObject*[T] = object
352347
data*: T
353348

@@ -480,11 +475,6 @@ type
480475
GetStateV2Response* = ref ForkedHashedBeaconState
481476
GetAggregatedAttestationV2Response* = ForkedAttestation
482477

483-
GetHistoricalSummariesV1Response* = object
484-
historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT]
485-
proof*: array[5, Eth2Digest]
486-
slot*: Slot
487-
488478
RestRoot* = object
489479
root*: Eth2Digest
490480

@@ -1086,3 +1076,88 @@ func toValidatorIndex*(value: RestValidatorIndex): Result[ValidatorIndex,
10861076
err(ValidatorIndexError.TooHighValue)
10871077
else:
10881078
doAssert(false, "ValidatorIndex type size is incorrect")
1079+
1080+
## Types and helpers for historical_summaries + proof endpoint
1081+
const
1082+
# gIndex for historical_summaries field (27th field in BeaconState)
1083+
HISTORICAL_SUMMARIES_GINDEX* = GeneralizedIndex(59) # 32 + 27 = 59
1084+
HISTORICAL_SUMMARIES_GINDEX_ELECTRA* = GeneralizedIndex(91) # 64 + 27 = 91
1085+
1086+
type
1087+
# Note: these could go in separate Capella/Electra spec files if they were
1088+
# part of the specification.
1089+
HistoricalSummariesProof* = array[log2trunc(HISTORICAL_SUMMARIES_GINDEX), Eth2Digest]
1090+
HistoricalSummariesProofElectra* =
1091+
array[log2trunc(HISTORICAL_SUMMARIES_GINDEX_ELECTRA), Eth2Digest]
1092+
1093+
# REST API types
1094+
GetHistoricalSummariesV1Response* = object
1095+
historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT]
1096+
proof*: HistoricalSummariesProof
1097+
slot*: Slot
1098+
1099+
GetHistoricalSummariesV1ResponseElectra* = object
1100+
historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT]
1101+
proof*: HistoricalSummariesProofElectra
1102+
slot*: Slot
1103+
1104+
# REST client response type
1105+
ForkedHistoricalSummariesWithProof* = object
1106+
case kind*: ConsensusFork
1107+
of ConsensusFork.Phase0: discard
1108+
of ConsensusFork.Altair: discard
1109+
of ConsensusFork.Bellatrix: discard
1110+
of ConsensusFork.Capella: capellaData*: GetHistoricalSummariesV1Response
1111+
of ConsensusFork.Deneb: denebData*: GetHistoricalSummariesV1Response
1112+
of ConsensusFork.Electra: electraData*: GetHistoricalSummariesV1ResponseElectra
1113+
of ConsensusFork.Fulu: fuluData*: GetHistoricalSummariesV1ResponseElectra
1114+
1115+
template init*(
1116+
T: type ForkedHistoricalSummariesWithProof,
1117+
historical_summaries: GetHistoricalSummariesV1Response,
1118+
fork: ConsensusFork,
1119+
): T =
1120+
case fork
1121+
of ConsensusFork.Phase0:
1122+
raiseAssert $fork & " fork should not be used for historical summaries"
1123+
of ConsensusFork.Altair:
1124+
raiseAssert $fork & " fork should not be used for historical summaries"
1125+
of ConsensusFork.Bellatrix:
1126+
raiseAssert $fork & " fork should not be used for historical summaries"
1127+
of ConsensusFork.Capella:
1128+
ForkedHistoricalSummariesWithProof(
1129+
kind: ConsensusFork.Capella, capellaData: historical_summaries
1130+
)
1131+
of ConsensusFork.Deneb:
1132+
ForkedHistoricalSummariesWithProof(
1133+
kind: ConsensusFork.Deneb, denebData: historical_summaries
1134+
)
1135+
of ConsensusFork.Electra:
1136+
raiseAssert $fork & " fork should not be used for this type of historical summaries"
1137+
of ConsensusFork.Fulu:
1138+
raiseAssert $fork & " fork should not be used for this type of historical summaries"
1139+
1140+
template init*(
1141+
T: type ForkedHistoricalSummariesWithProof,
1142+
historical_summaries: GetHistoricalSummariesV1ResponseElectra,
1143+
fork: ConsensusFork,
1144+
): T =
1145+
case fork
1146+
of ConsensusFork.Phase0:
1147+
raiseAssert $fork & " fork should not be used for historical summaries"
1148+
of ConsensusFork.Altair:
1149+
raiseAssert $fork & " fork should not be used for historical summaries"
1150+
of ConsensusFork.Bellatrix:
1151+
raiseAssert $fork & " fork should not be used for historical summaries"
1152+
of ConsensusFork.Capella:
1153+
raiseAssert $fork & " fork should not be used for this type of historical summaries"
1154+
of ConsensusFork.Deneb:
1155+
raiseAssert $fork & " fork should not be used for this type of historical summaries"
1156+
of ConsensusFork.Electra:
1157+
ForkedHistoricalSummariesWithProof(
1158+
kind: ConsensusFork.Electra, electraData: historical_summaries
1159+
)
1160+
of ConsensusFork.Fulu:
1161+
ForkedHistoricalSummariesWithProof(
1162+
kind: ConsensusFork.Fulu, fuluData: historical_summaries
1163+
)

0 commit comments

Comments
 (0)