From 6135f417a2f4f913fa9ba1e0db4f70857c0c8a3b Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sun, 15 Jun 2025 15:20:16 +0100 Subject: [PATCH] Add data columns sidecars debug beacon API (#7591) Beacon API spec PR: https://github.com/ethereum/beacon-APIs/pull/537 --- beacon_node/http_api/src/block_id.rs | 54 ++++++++++++++++++++++++++-- beacon_node/http_api/src/lib.rs | 50 ++++++++++++++++++++++++++ common/eth2/src/types.rs | 7 ++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/beacon_node/http_api/src/block_id.rs b/beacon_node/http_api/src/block_id.rs index cdef1521ec..e33de25470 100644 --- a/beacon_node/http_api/src/block_id.rs +++ b/beacon_node/http_api/src/block_id.rs @@ -1,14 +1,16 @@ +use crate::version::inconsistent_fork_rejection; use crate::{state_id::checkpoint_slot_and_execution_optimistic, ExecutionOptimistic}; use beacon_chain::kzg_utils::reconstruct_blobs; use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; use eth2::types::BlobIndicesQuery; use eth2::types::BlockId as CoreBlockId; +use eth2::types::DataColumnIndicesQuery; use std::fmt; use std::str::FromStr; use std::sync::Arc; use types::{ - BlobSidecarList, EthSpec, FixedBytesExtended, Hash256, SignedBeaconBlock, - SignedBlindedBeaconBlock, Slot, + BlobSidecarList, DataColumnSidecarList, EthSpec, FixedBytesExtended, ForkName, Hash256, + SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, }; use warp::Rejection; @@ -19,6 +21,13 @@ pub struct BlockId(pub CoreBlockId); type Finalized = bool; +type DataColumnsResponse = ( + DataColumnSidecarList<::EthSpec>, + ForkName, + ExecutionOptimistic, + Finalized, +); + impl BlockId { pub fn from_slot(slot: Slot) -> Self { Self(CoreBlockId::Slot(slot)) @@ -260,6 +269,47 @@ impl BlockId { } } + pub fn get_data_columns( + &self, + query: DataColumnIndicesQuery, + chain: &BeaconChain, + ) -> Result, Rejection> { + let (root, execution_optimistic, finalized) = self.root(chain)?; + let block = BlockId::blinded_block_by_root(&root, chain)?.ok_or_else(|| { + warp_utils::reject::custom_not_found(format!("beacon block with root {}", root)) + })?; + + if !chain.spec.is_peer_das_enabled_for_epoch(block.epoch()) { + return Err(warp_utils::reject::custom_bad_request( + "block is pre-Fulu and has no data columns".to_string(), + )); + } + + let data_column_sidecars = if let Some(indices) = query.indices { + indices + .iter() + .filter_map(|index| chain.get_data_column(&root, index).transpose()) + .collect::, _>>() + .map_err(warp_utils::reject::unhandled_error)? + } else { + chain + .get_data_columns(&root) + .map_err(warp_utils::reject::unhandled_error)? + .unwrap_or_default() + }; + + let fork_name = block + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + + Ok(( + data_column_sidecars, + fork_name, + execution_optimistic, + finalized, + )) + } + #[allow(clippy::type_complexity)] pub fn get_blinded_block_and_blob_list_filtered( &self, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index a4ec41ac06..e53fecbdb7 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2883,6 +2883,55 @@ pub fn serve( * debug */ + // GET debug/beacon/data_column_sidecars/{block_id} + let get_debug_data_column_sidecars = eth_v1 + .and(warp::path("debug")) + .and(warp::path("beacon")) + .and(warp::path("data_column_sidecars")) + .and(block_id_or_err) + .and(warp::path::end()) + .and(multi_key_query::()) + .and(task_spawner_filter.clone()) + .and(chain_filter.clone()) + .and(warp::header::optional::("accept")) + .then( + |block_id: BlockId, + indices_res: Result, + task_spawner: TaskSpawner, + chain: Arc>, + accept_header: Option| { + task_spawner.blocking_response_task(Priority::P1, move || { + let indices = indices_res?; + let (data_columns, fork_name, execution_optimistic, finalized) = + block_id.get_data_columns(indices, &chain)?; + + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .body(data_columns.as_ssz_bytes().into()) + .map(|res: Response| add_ssz_content_type_header(res)) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => { + // Post as a V2 endpoint so we return the fork version. + let res = execution_optimistic_finalized_beacon_response( + ResponseIncludesVersion::Yes(fork_name), + execution_optimistic, + finalized, + &data_columns, + )?; + Ok(warp::reply::json(&res).into_response()) + } + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + // GET debug/beacon/states/{state_id} let get_debug_beacon_states = any_version .and(warp::path("debug")) @@ -4950,6 +4999,7 @@ pub fn serve( .uor(get_config_spec) .uor(get_config_deposit_contract) .uor(get_debug_beacon_states) + .uor(get_debug_data_column_sidecars) .uor(get_debug_beacon_heads) .uor(get_debug_fork_choice) .uor(get_node_identity) diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index f7bda17eb1..fa9f17f5cb 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -701,6 +701,13 @@ pub struct BlobIndicesQuery { pub indices: Option>, } +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DataColumnIndicesQuery { + #[serde(default, deserialize_with = "option_query_vec")] + pub indices: Option>, +} + #[derive(Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct ValidatorIndexData(#[serde(with = "serde_utils::quoted_u64_vec")] pub Vec);