Skip to content

Add /eth/v1/debug/beacon/data_column_sidecars/{block_id} #7237

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

Merged
merged 4 commits into from
Jun 17, 2025
Merged
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/beacon_chain_db.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,8 @@ proc getDataColumnSidecarSZ*(db: BeaconChainDB, root: Eth2Digest,

proc getDataColumnSidecar*(db: BeaconChainDB, root: Eth2Digest, index: ColumnIndex,
value: var DataColumnSidecar): bool =
if db.columns == nil: # Fulu has not been scheduled; DB table does not exist
return false
db.columns.getSZSSZ(columnkey(root, index), value) == GetResult.found

proc getBlockSZ*(
Expand Down
108 changes: 64 additions & 44 deletions beacon_chain/rpc/rest_beacon_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,62 @@ proc toString*(kind: ValidatorFilterKind): string =
of ValidatorFilterKind.WithdrawalDone:
"withdrawal_done"

proc handleDataSidecarRequest*[
InvalidIndexValueError: static string,
DataSidecarsType: typedesc[List];
getDataSidecar: static proc
](
node: BeaconNode,
mediaType: Result[MediaType, cstring],
block_id: Result[BlockIdent, cstring],
indices: Result[seq[uint64], cstring],
maxDataSidecars: uint64): RestApiResponse =
let
contentType = mediaType.valueOr:
return RestApiResponse.jsonError(
Http406, ContentNotAcceptableError)
blockIdent = block_id.valueOr:
return RestApiResponse.jsonError(
Http400, InvalidBlockIdValueError, $error)
bid = node.getBlockId(blockIdent).valueOr:
return RestApiResponse.jsonError(
Http404, BlockNotFoundError)
indexFilter = (block: indices.valueOr:
return RestApiResponse.jsonError(
Http400, InvalidIndexValueError, $error)).toHashSet()

data = newClone(default(DataSidecarsType))
for dataIndex in 0'u64 ..< maxDataSidecars:
if indexFilter.len > 0 and dataIndex notin indexFilter:
continue
let dataSidecar = new DataSidecarsType.T
if getDataSidecar(node.dag.db, bid.root, dataIndex, dataSidecar[]):
discard data[].add dataSidecar[]

if contentType == sszMediaType:
RestApiResponse.sszResponse(
data[], headers = [("eth-consensus-version",
node.dag.cfg.consensusForkAtEpoch(bid.slot.epoch).toString())])
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseDataSidecars(
data[].asSeq(), node.dag.cfg.consensusForkAtEpoch(bid.slot.epoch),
Opt.some(node.dag.is_optimistic(bid)), node.dag.isFinalized(bid))
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)

proc handleDataSidecarRequest*[
InvalidIndexValueError: static string,
DataSidecarsType: typedesc[List];
getDataSidecar: static proc
](
node: BeaconNode,
mediaType: Result[MediaType, cstring],
block_id: Result[BlockIdent, cstring],
indices: Result[seq[uint64], cstring]): RestApiResponse =
handleDataSidecarRequest[
InvalidIndexValueError, DataSidecarsType, getDataSidecar
](node, mediaType, block_id, indices, DataSidecarsType.maxLen.uint64)

proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4881.md
router.api2(MethodGet, "/eth/v1/beacon/deposit_snapshot") do (
Expand Down Expand Up @@ -1684,55 +1740,19 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.2#/Beacon/getBlobSidecars
# https://github.com/ethereum/beacon-APIs/blob/v2.4.2/apis/beacon/blob_sidecars/blob_sidecars.yaml
router.api2(MethodGet, "/eth/v1/beacon/blob_sidecars/{block_id}") do (
block_id: BlockIdent, indices: seq[uint64]) -> RestApiResponse:
let
blockIdent = block_id.valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
$error)
bid = node.getBlockId(blockIdent).valueOr:
return RestApiResponse.jsonError(Http404, BlockNotFoundError)

contentType = block:
let res = preferredContentType(jsonMediaType,
sszMediaType)
if res.isErr():
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
res.get()

block_id: BlockIdent, indices: seq[uint64]) -> RestApiResponse:
# https://github.com/ethereum/beacon-APIs/blob/v2.4.2/types/deneb/blob_sidecar.yaml#L2-L28
# The merkleization limit of the list is `MAX_BLOB_COMMITMENTS_PER_BLOCK`,
# the serialization limit is configurable and is:
# - `MAX_BLOBS_PER_BLOCK` from Deneb onward
# - `MAX_BLOBS_PER_BLOCK_ELECTRA` from Electra.
let data = newClone(default(
List[BlobSidecar, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]))

if indices.isErr:
return RestApiResponse.jsonError(Http400,
InvalidSidecarIndexValueError)

let indexFilter = indices.get.toHashSet

for blobIndex in 0'u64 ..< node.dag.cfg.MAX_BLOBS_PER_BLOCK_ELECTRA:
if indexFilter.len > 0 and blobIndex notin indexFilter:
continue

var blobSidecar = new BlobSidecar

if node.dag.db.getBlobSidecar(bid.root, blobIndex, blobSidecar[]):
discard data[].add blobSidecar[]

if contentType == sszMediaType:
RestApiResponse.sszResponse(
data[], headers = [("eth-consensus-version",
node.dag.cfg.consensusForkAtEpoch(bid.slot.epoch).toString())])
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseBlobSidecars(
data[].asSeq(), node.dag.cfg.consensusForkAtEpoch(bid.slot.epoch),
Opt.some(node.dag.is_optimistic(bid)),
node.dag.isFinalized(bid))
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
handleDataSidecarRequest[
InvalidBlobSidecarIndexValueError,
List[BlobSidecar, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK],
getBlobSidecar
](
node, preferredContentType(jsonMediaType, sszMediaType),
block_id, indices, node.dag.cfg.MAX_BLOBS_PER_BLOCK_ELECTRA)

# https://ethereum.github.io/beacon-APIs/?urls.primaryName=v3.1.0#/Beacon/getPendingDeposits
router.metricsApi2(
Expand Down
4 changes: 3 additions & 1 deletion beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,10 @@ const
"Failed to obtain fork information"
InvalidTimestampValue* =
"Invalid or missing timestamp value"
InvalidSidecarIndexValueError* =
InvalidBlobSidecarIndexValueError* =
"Invalid blob index"
InvalidDataColumnSidecarIndexValueError* =
"Invalid data column index"
InvalidBroadcastValidationType* =
"Invalid broadcast_validation type value"
PathNotFoundError* =
Expand Down
17 changes: 15 additions & 2 deletions beacon_chain/rpc/rest_debug_api.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2024 Status Research & Development GmbH
# Copyright (c) 2021-2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
Expand All @@ -11,7 +11,7 @@ import std/sequtils
import chronicles, metrics
import ".."/beacon_node,
".."/spec/forks,
"."/[rest_utils, state_ttl_cache]
"."/[rest_beacon_api, rest_utils, state_ttl_cache]

from ../fork_choice/proto_array import ProtoArrayItem, items

Expand All @@ -20,6 +20,19 @@ export rest_utils
logScope: topics = "rest_debug"

proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Debug/getDebugDataColumnSidecars
# https://github.com/ethereum/beacon-APIs/blob/v4.0.0-alpha.0/apis/debug/data_column_sidecars.yaml
router.api2(
MethodGet, "/eth/v1/debug/beacon/data_column_sidecars/{block_id}") do (
block_id: BlockIdent, indices: seq[uint64]) -> RestApiResponse:
handleDataSidecarRequest[
InvalidDataColumnSidecarIndexValueError,
List[DataColumnSidecar, NUMBER_OF_COLUMNS],
getDataColumnSidecar
](
node, preferredContentType(jsonMediaType, sszMediaType),
block_id, indices)

# https://ethereum.github.io/beacon-APIs/#/Debug/getState
router.api2(MethodGet, "/eth/v1/debug/beacon/states/{state_id}") do (
state_id: StateIdent) -> RestApiResponse:
Expand Down
12 changes: 6 additions & 6 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ RestJson.useDefaultSerializationFor(
Checkpoint,
ConsolidationRequest,
ContributionAndProof,
DataColumnSidecar,
DataEnclosedObject,
DataMetaEnclosedObject,
DataOptimisticAndFinalizedObject,
Expand Down Expand Up @@ -642,9 +643,9 @@ proc jsonResponseBlock*(t: typedesc[RestApiResponse],
default(seq[byte])
RestApiResponse.response(res, Http200, "application/json", headers = headers)

proc jsonResponseBlobSidecars*(
proc jsonResponseDataSidecars*(
t: typedesc[RestApiResponse],
data: openArray[BlobSidecar],
data: openArray[BlobSidecar | DataColumnSidecar],
version: ConsensusFork,
execOpt: Opt[bool],
finalized: bool
Expand Down Expand Up @@ -1406,11 +1407,10 @@ proc writeValue*(
) {.raises: [IOError].} =
writeValue(writer, hexOriginal(distinctBase(value)))

## KzgCommitment and KzgProof; both are the same type, but this makes it
## explicit.
## KzgCommitment, KzgProof, and KzgCell
## https://github.com/ethereum/beacon-APIs/blob/v2.4.2/types/primitive.yaml#L135-L146
proc readValue*(reader: var JsonReader[RestJson],
value: var (KzgCommitment|KzgProof)) {.
value: var (KzgCommitment|KzgProof|KzgCell)) {.
raises: [IOError, SerializationError].} =
try:
hexToByteArray(reader.readValue(string), distinctBase(value.bytes))
Expand All @@ -1419,7 +1419,7 @@ proc readValue*(reader: var JsonReader[RestJson],
"KzgCommitment value should be a valid hex string")

proc writeValue*(
writer: var JsonWriter[RestJson], value: KzgCommitment | KzgProof
writer: var JsonWriter[RestJson], value: KzgCommitment | KzgProof | KzgCell
) {.raises: [IOError].} =
writeValue(writer, hexOriginal(distinctBase(value.bytes)))

Expand Down
24 changes: 24 additions & 0 deletions ncli/resttest-rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -4241,6 +4241,30 @@
"status": {"operator": "equals", "value": "410"}
}
},
{
"topics": ["debug", "beacon_data_column_sidecars_blockid"],
"request": {
"url": "/eth/v1/debug/beacon/data_column_sidecars/head",
"headers": {"Accept": "application/json"}
},
"response": {"status": {"operator": "equals", "value": "200"}}
},
{
"topics": ["debug", "beacon_data_column_sidecars_blockid"],
"request": {
"url": "/eth/v1/debug/beacon/data_column_sidecars/finalized",
"headers": {"Accept": "application/json"}
},
"response": {"status": {"operator": "equals", "value": "200"}}
},
{
"topics": ["debug", "beacon_data_column_sidecars_blockid"],
"request": {
"url": "/eth/v1/debug/beacon/data_column_sidecars/0x0000000000000000000000000000000000000000000000000000000000000000",
"headers": {"Accept": "application/json"}
},
"response": {"status": {"operator": "equals", "value": "404"}}
},
{
"topics": ["debug", "beacon_states_head_slow", "slow"],
"request": {
Expand Down
Loading