Repository: ReamLabs/ream
Pull request #315: feat: implement /headers
Author: login: varun-doshi
Labels: name: Beacon API
Created: 2025-04-06T21:13:37Z | Merged: 2025-04-13T19:36:23Z

To run, start a node:
```
cargo run node
```
Make `GET` request to:
```
/beacon/headers
```
With optional params:
```
/beacon/headers?slot=123&parent_root=0x04567
```

Closes #196 

Linked issue #196: Implement /eth/v1/beacon/headers
https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders

Diff:
diff --git a/crates/rpc/src/handlers/block.rs b/crates/rpc/src/handlers/block.rs
index 65c6dce44..2b75fdc0f 100644
--- a/crates/rpc/src/handlers/block.rs
+++ b/crates/rpc/src/handlers/block.rs
@@ -72,7 +72,7 @@ pub async fn get_block_root_from_id(block_id: ID, db: &ReamDB) -> Result<B256, A
     Ok(block_root)
 }
 
-async fn get_beacon_block_from_id(
+pub async fn get_beacon_block_from_id(
     block_id: ID,
     db: &ReamDB,
 ) -> Result<SignedBeaconBlock, ApiError> {
diff --git a/crates/rpc/src/handlers/header.rs b/crates/rpc/src/handlers/header.rs
new file mode 100644
index 000000000..c72871d90
--- /dev/null
+++ b/crates/rpc/src/handlers/header.rs
@@ -0,0 +1,121 @@
+use alloy_primitives::B256;
+use ream_consensus::beacon_block_header::{BeaconBlockHeader, SignedBeaconBlockHeader};
+use ream_storage::{db::ReamDB, tables::Table};
+use serde::{Deserialize, Serialize};
+use tree_hash::TreeHash;
+use warp::{
+    http::status::StatusCode,
+    reject::Rejection,
+    reply::{Reply, with_status},
+};
+
+use super::block::get_beacon_block_from_id;
+use crate::types::{
+    errors::ApiError,
+    id::ID,
+    query::{ParentRootQuery, SlotQuery},
+    response::BeaconResponse,
+};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HeaderData {
+    root: B256,
+    canonical: bool,
+    header: SignedBeaconBlockHeader,
+}
+
+impl HeaderData {
+    pub fn new(root: B256, canonical: bool, header: SignedBeaconBlockHeader) -> Self {
+        Self {
+            root,
+            canonical,
+            header,
+        }
+    }
+}
+
+/// Called using `/eth/v1/beacon/headers`
+/// Optional paramaters `slot` and/or `parent_root`
+pub async fn get_headers(
+    slot: SlotQuery,
+    parent_root: ParentRootQuery,
+    db: ReamDB,
+) -> Result<impl Reply, Rejection> {
+    let (header, root) = match (slot.slot, parent_root.parent_root) {
+        (None, None) => {
+            let slot = db
+                .slot_index_provider()
+                .get_highest_slot()
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch latest slot")))?;
+
+            get_header_from_slot(slot, db).await?
+        }
+        (None, Some(parent_root)) => {
+            // get parent block to have access to `slot`
+            let parent_block = db
+                .beacon_block_provider()
+                .get(parent_root)
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch parent block")))?;
+
+            // fetch block header at `slot+1`
+            let (child_header, child_block_root) =
+                get_header_from_slot(parent_block.message.slot + 1, db)
+                    .await
+                    .map_err(|_| {
+                        ApiError::NotFound(format!(
+                            "Unable to fetch header with parent root: {parent_root:?}"
+                        ))
+                    })?;
+
+            if child_header.message.parent_root != parent_root {
+                return Err(ApiError::NotFound(format!(
+                    "Header with parent root :{parent_root:?}"
+                )))?;
+            }
+
+            (child_header, child_block_root)
+        }
+        (Some(slot), None) => get_header_from_slot(slot, db).await?,
+        (Some(slot), Some(parent_root)) => {
+            let (header, root) = get_header_from_slot(slot, db).await?;
+            if header.message.parent_root == parent_root {
+                (header, root)
+            } else {
+                return Err(ApiError::NotFound(format!(
+                    "Header at slot: {slot} with parent root: {parent_root:?} not found"
+                )))?;
+            }
+        }
+    };
+
+    Ok(with_status(
+        BeaconResponse::json(HeaderData::new(root, true, header)),
+        StatusCode::OK,
+    ))
+}
+
+pub async fn get_header_from_slot(
+    slot: u64,
+    db: ReamDB,
+) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
+    let beacon_block = get_beacon_block_from_id(ID::Slot(slot), &db).await?;
+
+    let header_message = BeaconBlockHeader {
+        slot: beacon_block.message.slot,
+        proposer_index: beacon_block.message.proposer_index,
+        state_root: beacon_block.message.state_root,
+        parent_root: beacon_block.message.parent_root,
+        body_root: beacon_block.message.body.tree_hash_root(),
+    };
+    let root = header_message.tree_hash_root();
+
+    Ok((
+        SignedBeaconBlockHeader {
+            message: header_message,
+            signature: beacon_block.signature,
+        },
+        root,
+    ))
+}
diff --git a/crates/rpc/src/handlers/mod.rs b/crates/rpc/src/handlers/mod.rs
index de9c0f389..deddeb2bc 100644
--- a/crates/rpc/src/handlers/mod.rs
+++ b/crates/rpc/src/handlers/mod.rs
@@ -3,6 +3,7 @@ pub mod checkpoint;
 pub mod config;
 pub mod fork;
 pub mod genesis;
+pub mod header;
 pub mod randao;
 pub mod state;
 pub mod validator;
diff --git a/crates/rpc/src/routes/beacon.rs b/crates/rpc/src/routes/beacon.rs
index 4a498475b..31b900e2c 100644
--- a/crates/rpc/src/routes/beacon.rs
+++ b/crates/rpc/src/routes/beacon.rs
@@ -16,13 +16,14 @@ use crate::{
         checkpoint::get_finality_checkpoint,
         fork::get_fork,
         genesis::get_genesis,
+        header::get_headers,
         randao::get_randao_mix,
         state::get_state_root,
         validator::get_validator_from_state,
     },
     types::{
         id::{ID, ValidatorID},
-        query::RandaoQuery,
+        query::{ParentRootQuery, RandaoQuery, SlotQuery},
     },
     utils::error::parsed_param,
 };
@@ -99,6 +100,20 @@ pub fn get_beacon_routes(
         })
         .with(log("validator"));
 
+    let headers = beacon_base
+        .and(path("headers"))
+        .and(query::<SlotQuery>())
+        .and(query::<ParentRootQuery>())
+        .and(end())
+        .and(get())
+        .and(db_filter.clone())
+        .and_then({
+            move |slot: SlotQuery, parent_root: ParentRootQuery, db: ReamDB| {
+                get_headers(slot, parent_root, db)
+            }
+        })
+        .with(log("headers"));
+
     let block_root = beacon_base
         .and(path("blocks"))
         .and(parsed_param::<ID>())
@@ -126,6 +141,7 @@ pub fn get_beacon_routes(
         .or(state_root)
         .or(block_root)
         .or(block_rewards)
+        .or(headers)
 }
 
 pub fn get_beacon_routes_v2(
diff --git a/crates/rpc/src/types/query.rs b/crates/rpc/src/types/query.rs
index ec887cf1a..1c81d3d9c 100644
--- a/crates/rpc/src/types/query.rs
+++ b/crates/rpc/src/types/query.rs
@@ -1,6 +1,22 @@
+use alloy_primitives::B256;
 use serde::Deserialize;
 
 #[derive(Debug, Deserialize)]
 pub struct RandaoQuery {
     pub epoch: Option<u64>,
 }
+
+#[derive(Debug, Deserialize)]
+pub struct SlotQuery {
+    pub slot: Option<u64>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct RootQuery {
+    pub root: Option<B256>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ParentRootQuery {
+    pub parent_root: Option<B256>,
+}


Reviews:
user: login: KolbyML
body: left some feedback
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T09:29:23Z
user: login: varun-doshi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T10:59:07Z
user: login: KolbyML
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T18:56:38Z
user: login: KolbyML
body: :shipit: looks good
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T19:16:27Z
user: login: KolbyML
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T19:28:57Z

Review comments:
diff_hunk: @@ -1,6 +1,20 @@
+use alloy_primitives::B256;
 use serde::Deserialize;
 
 #[derive(Debug, Deserialize)]
 pub struct RandaoQuery {
     pub epoch: Option<u64>,
 }
+
+#[derive(Debug, Deserialize)]
+pub struct SlotQuery {
+    pub slot: Option<u64>,
+}
+#[derive(Debug, Deserialize)]
+pub struct RootQuery {
+    pub root: Option<B256>,
+}
+#[derive(Debug, Deserialize)]
+pub struct ParentRootQuery {
+    pub parent_root: Option<B256>,
+}
path: crates/rpc/src/types/query.rs
user: login: KolbyML
body: ```suggestion
#[derive(Debug, Deserialize)]
pub struct SlotQuery {
    pub slot: Option<u64>,
}

#[derive(Debug, Deserialize)]
pub struct RootQuery {
    pub root: Option<B256>,
}

#[derive(Debug, Deserialize)]
pub struct ParentRootQuery {
    pub parent_root: Option<B256>,
}
```

I shouldn't need to comment on this as often as I do
created_at: 2025-04-12T09:19:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,128 @@
+use alloy_primitives::B256;
+use ream_consensus::beacon_block_header::{BeaconBlockHeader, SignedBeaconBlockHeader};
+use ream_storage::{db::ReamDB, tables::Table};
+use serde::{Deserialize, Serialize};
+use tree_hash::TreeHash;
+use warp::{
+    http::status::StatusCode,
+    reject::Rejection,
+    reply::{Reply, with_status},
+};
+
+use crate::types::{
+    errors::ApiError,
+    query::{ParentRootQuery, SlotQuery},
+    response::BeaconResponse,
+};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HeaderData {
+    root: B256,
+    canonical: bool,
+    header: SignedBeaconBlockHeader,
+}
+
+impl HeaderData {
+    pub fn new(root: B256, canonical: bool, header: SignedBeaconBlockHeader) -> Self {
+        Self {
+            root,
+            canonical,
+            header,
+        }
+    }
+}
+
+/// Called using `/eth/v1/beacon/headers`
+/// Optional paramaters `slot` and/or `parent_root`
+pub async fn get_headers(
+    slot: SlotQuery,
+    parent_root: ParentRootQuery,
+    db: ReamDB,
+) -> Result<impl Reply, Rejection> {
+    let (header, root) = match (slot.slot, parent_root.parent_root) {
+        (None, None) => {
+            let slot = db
+                .slot_index_provider()
+                .get_highest_slot()
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch latest slot")))?;
+
+            get_header_from_slot(slot, db).await?
+        }
+        (None, Some(parent_root)) => {
+            // get parent block to have access to `slot`
+            let parent_block = db
+                .beacon_block_provider()
+                .get(parent_root)
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch parent block")))?;
+
+            // fetch block header at `slot+1`
+            let (child_header, child_block_root) =
+                get_header_from_slot(parent_block.message.slot + 1, db)
+                    .await
+                    .map_err(|_| {
+                        ApiError::NotFound(format!(
+                            "Unable to fetch header with parent root: {parent_root:?}"
+                        ))
+                    })?;
+
+            if child_header.message.parent_root != parent_root {
+                return Err(ApiError::NotFound(format!(
+                    "Header with parent root :{parent_root:?}"
+                )))?;
+            }
+
+            (child_header, child_block_root)
+        }
+        (Some(slot), None) => get_header_from_slot(slot, db).await?,
+        (Some(slot), Some(parent_root)) => {
+            let (header, root) = get_header_from_slot(slot, db).await?;
+            if header.message.parent_root == parent_root {
+                (header, root)
+            } else {
+                return Err(ApiError::NotFound(format!(
+                    "Header at slot: {slot} with parent root: {parent_root:?} not found"
+                )))?;
+            }
+        }
+    };
+
+    Ok(with_status(
+        BeaconResponse::json(HeaderData::new(root, true, header)),
+        StatusCode::OK,
+    ))
+}
+
+pub async fn get_header_from_slot(
+    slot: u64,
+    db: ReamDB,
+) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
+    let block_root = db
+        .slot_index_provider()
+        .get(slot)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Block Root not found")))?;
+
+    let signed_block = db
+        .beacon_block_provider()
+        .get(block_root)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Header not found")))?;
+
+    let header_message = BeaconBlockHeader {
+        slot: signed_block.message.slot,
+        proposer_index: signed_block.message.proposer_index,
+        state_root: signed_block.message.state_root,
+        parent_root: signed_block.message.parent_root,
+        body_root: signed_block.message.body.tree_hash_root(),
+    };
+
+    Ok((
+        SignedBeaconBlockHeader {
+            message: header_message,
+            signature: signed_block.signature,
+        },
+        block_root,
+    ))
+}
path: crates/rpc/src/handlers/header.rs
user: login: KolbyML
body: ```suggestion
pub async fn get_header_from_slot(
    slot: u64,
    db: ReamDB,
) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
    let beacon_block = get_beacon_block_from_id(ID::Slot(slot), &db).await?;

    let header_message = BeaconBlockHeader {
        slot:  beacon_block.message.slot,
        proposer_index: beacon_block.message.proposer_index,
        state_root: beacon_block.message.state_root,
        parent_root: beacon_block.message.parent_root,
        body_root: beacon_block.message.body.tree_hash_root(),
    };

    Ok((
        SignedBeaconBlockHeader {
            message: header_message,
            signature: signed_block.signature,
        },
        block_root: header_message.tree_hash_root(),
    ))
}
```
created_at: 2025-04-12T09:26:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,128 @@
+use alloy_primitives::B256;
+use ream_consensus::beacon_block_header::{BeaconBlockHeader, SignedBeaconBlockHeader};
+use ream_storage::{db::ReamDB, tables::Table};
+use serde::{Deserialize, Serialize};
+use tree_hash::TreeHash;
+use warp::{
+    http::status::StatusCode,
+    reject::Rejection,
+    reply::{Reply, with_status},
+};
+
+use crate::types::{
+    errors::ApiError,
+    query::{ParentRootQuery, SlotQuery},
+    response::BeaconResponse,
+};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HeaderData {
+    root: B256,
+    canonical: bool,
+    header: SignedBeaconBlockHeader,
+}
+
+impl HeaderData {
+    pub fn new(root: B256, canonical: bool, header: SignedBeaconBlockHeader) -> Self {
+        Self {
+            root,
+            canonical,
+            header,
+        }
+    }
+}
+
+/// Called using `/eth/v1/beacon/headers`
+/// Optional paramaters `slot` and/or `parent_root`
+pub async fn get_headers(
+    slot: SlotQuery,
+    parent_root: ParentRootQuery,
+    db: ReamDB,
+) -> Result<impl Reply, Rejection> {
+    let (header, root) = match (slot.slot, parent_root.parent_root) {
+        (None, None) => {
+            let slot = db
+                .slot_index_provider()
+                .get_highest_slot()
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch latest slot")))?;
+
+            get_header_from_slot(slot, db).await?
+        }
+        (None, Some(parent_root)) => {
+            // get parent block to have access to `slot`
+            let parent_block = db
+                .beacon_block_provider()
+                .get(parent_root)
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch parent block")))?;
+
+            // fetch block header at `slot+1`
+            let (child_header, child_block_root) =
+                get_header_from_slot(parent_block.message.slot + 1, db)
+                    .await
+                    .map_err(|_| {
+                        ApiError::NotFound(format!(
+                            "Unable to fetch header with parent root: {parent_root:?}"
+                        ))
+                    })?;
+
+            if child_header.message.parent_root != parent_root {
+                return Err(ApiError::NotFound(format!(
+                    "Header with parent root :{parent_root:?}"
+                )))?;
+            }
+
+            (child_header, child_block_root)
+        }
+        (Some(slot), None) => get_header_from_slot(slot, db).await?,
+        (Some(slot), Some(parent_root)) => {
+            let (header, root) = get_header_from_slot(slot, db).await?;
+            if header.message.parent_root == parent_root {
+                (header, root)
+            } else {
+                return Err(ApiError::NotFound(format!(
+                    "Header at slot: {slot} with parent root: {parent_root:?} not found"
+                )))?;
+            }
+        }
+    };
+
+    Ok(with_status(
+        BeaconResponse::json(HeaderData::new(root, true, header)),
+        StatusCode::OK,
+    ))
+}
+
+pub async fn get_header_from_slot(
+    slot: u64,
+    db: ReamDB,
+) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
+    let block_root = db
+        .slot_index_provider()
+        .get(slot)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Block Root not found")))?;
+
+    let signed_block = db
+        .beacon_block_provider()
+        .get(block_root)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Header not found")))?;
+
+    let header_message = BeaconBlockHeader {
+        slot: signed_block.message.slot,
+        proposer_index: signed_block.message.proposer_index,
+        state_root: signed_block.message.state_root,
+        parent_root: signed_block.message.parent_root,
+        body_root: signed_block.message.body.tree_hash_root(),
+    };
+
+    Ok((
+        SignedBeaconBlockHeader {
+            message: header_message,
+            signature: signed_block.signature,
+        },
+        block_root,
+    ))
+}
path: crates/rpc/src/handlers/header.rs
user: login: varun-doshi
body: From the spec API, it seems like the `root`  field is the `parent_root`
Correct me if Im wrong.
created_at: 2025-04-12T10:59:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use alloy_primitives::B256;
+use ream_consensus::beacon_block_header::{BeaconBlockHeader, SignedBeaconBlockHeader};
+use ream_storage::{db::ReamDB, tables::Table};
+use serde::{Deserialize, Serialize};
+use tree_hash::TreeHash;
+use warp::{
+    http::status::StatusCode,
+    reject::Rejection,
+    reply::{Reply, with_status},
+};
+
+use super::block::get_beacon_block_from_id;
+use crate::types::{
+    errors::ApiError,
+    id::ID,
+    query::{ParentRootQuery, SlotQuery},
+    response::BeaconResponse,
+};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HeaderData {
+    root: B256,
+    canonical: bool,
+    header: SignedBeaconBlockHeader,
+}
+
+impl HeaderData {
+    pub fn new(root: B256, canonical: bool, header: SignedBeaconBlockHeader) -> Self {
+        Self {
+            root,
+            canonical,
+            header,
+        }
+    }
+}
+
+/// Called using `/eth/v1/beacon/headers`
+/// Optional paramaters `slot` and/or `parent_root`
+pub async fn get_headers(
+    slot: SlotQuery,
+    parent_root: ParentRootQuery,
+    db: ReamDB,
+) -> Result<impl Reply, Rejection> {
+    let (header, root) = match (slot.slot, parent_root.parent_root) {
+        (None, None) => {
+            let slot = db
+                .slot_index_provider()
+                .get_highest_slot()
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch latest slot")))?;
+
+            get_header_from_slot(slot, db).await?
+        }
+        (None, Some(parent_root)) => {
+            // get parent block to have access to `slot`
+            let parent_block = db
+                .beacon_block_provider()
+                .get(parent_root)
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch parent block")))?;
+
+            // fetch block header at `slot+1`
+            let (child_header, child_block_root) =
+                get_header_from_slot(parent_block.message.slot + 1, db)
+                    .await
+                    .map_err(|_| {
+                        ApiError::NotFound(format!(
+                            "Unable to fetch header with parent root: {parent_root:?}"
+                        ))
+                    })?;
+
+            if child_header.message.parent_root != parent_root {
+                return Err(ApiError::NotFound(format!(
+                    "Header with parent root :{parent_root:?}"
+                )))?;
+            }
+
+            (child_header, child_block_root)
+        }
+        (Some(slot), None) => get_header_from_slot(slot, db).await?,
+        (Some(slot), Some(parent_root)) => {
+            let (header, root) = get_header_from_slot(slot, db).await?;
+            if header.message.parent_root == parent_root {
+                (header, root)
+            } else {
+                return Err(ApiError::NotFound(format!(
+                    "Header at slot: {slot} with parent root: {parent_root:?} not found"
+                )))?;
+            }
+        }
+    };
+
+    Ok(with_status(
+        BeaconResponse::json(HeaderData::new(root, true, header)),
+        StatusCode::OK,
+    ))
+}
+
+pub async fn get_header_from_slot(
+    slot: u64,
+    db: ReamDB,
+) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
+    let beacon_block = get_beacon_block_from_id(ID::Slot(slot), &db).await?;
+
+    let header_message = BeaconBlockHeader {
+        slot: beacon_block.message.slot,
+        proposer_index: beacon_block.message.proposer_index,
+        state_root: beacon_block.message.state_root,
+        parent_root: beacon_block.message.parent_root,
+        body_root: beacon_block.message.body.tree_hash_root(),
+    };
+    let root = header_message.parent_root;
path: crates/rpc/src/handlers/header.rs
user: login: KolbyML
body: ```suggestion
    let root = header_message.tree_hash_root();
```
created_at: 2025-04-12T18:56:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,128 @@
+use alloy_primitives::B256;
+use ream_consensus::beacon_block_header::{BeaconBlockHeader, SignedBeaconBlockHeader};
+use ream_storage::{db::ReamDB, tables::Table};
+use serde::{Deserialize, Serialize};
+use tree_hash::TreeHash;
+use warp::{
+    http::status::StatusCode,
+    reject::Rejection,
+    reply::{Reply, with_status},
+};
+
+use crate::types::{
+    errors::ApiError,
+    query::{ParentRootQuery, SlotQuery},
+    response::BeaconResponse,
+};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HeaderData {
+    root: B256,
+    canonical: bool,
+    header: SignedBeaconBlockHeader,
+}
+
+impl HeaderData {
+    pub fn new(root: B256, canonical: bool, header: SignedBeaconBlockHeader) -> Self {
+        Self {
+            root,
+            canonical,
+            header,
+        }
+    }
+}
+
+/// Called using `/eth/v1/beacon/headers`
+/// Optional paramaters `slot` and/or `parent_root`
+pub async fn get_headers(
+    slot: SlotQuery,
+    parent_root: ParentRootQuery,
+    db: ReamDB,
+) -> Result<impl Reply, Rejection> {
+    let (header, root) = match (slot.slot, parent_root.parent_root) {
+        (None, None) => {
+            let slot = db
+                .slot_index_provider()
+                .get_highest_slot()
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch latest slot")))?;
+
+            get_header_from_slot(slot, db).await?
+        }
+        (None, Some(parent_root)) => {
+            // get parent block to have access to `slot`
+            let parent_block = db
+                .beacon_block_provider()
+                .get(parent_root)
+                .map_err(|_| ApiError::InternalError)?
+                .ok_or_else(|| ApiError::NotFound(String::from("Unable to fetch parent block")))?;
+
+            // fetch block header at `slot+1`
+            let (child_header, child_block_root) =
+                get_header_from_slot(parent_block.message.slot + 1, db)
+                    .await
+                    .map_err(|_| {
+                        ApiError::NotFound(format!(
+                            "Unable to fetch header with parent root: {parent_root:?}"
+                        ))
+                    })?;
+
+            if child_header.message.parent_root != parent_root {
+                return Err(ApiError::NotFound(format!(
+                    "Header with parent root :{parent_root:?}"
+                )))?;
+            }
+
+            (child_header, child_block_root)
+        }
+        (Some(slot), None) => get_header_from_slot(slot, db).await?,
+        (Some(slot), Some(parent_root)) => {
+            let (header, root) = get_header_from_slot(slot, db).await?;
+            if header.message.parent_root == parent_root {
+                (header, root)
+            } else {
+                return Err(ApiError::NotFound(format!(
+                    "Header at slot: {slot} with parent root: {parent_root:?} not found"
+                )))?;
+            }
+        }
+    };
+
+    Ok(with_status(
+        BeaconResponse::json(HeaderData::new(root, true, header)),
+        StatusCode::OK,
+    ))
+}
+
+pub async fn get_header_from_slot(
+    slot: u64,
+    db: ReamDB,
+) -> Result<(SignedBeaconBlockHeader, B256), ApiError> {
+    let block_root = db
+        .slot_index_provider()
+        .get(slot)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Block Root not found")))?;
+
+    let signed_block = db
+        .beacon_block_provider()
+        .get(block_root)
+        .map_err(|_| ApiError::InternalError)?
+        .ok_or_else(|| ApiError::NotFound(String::from("Header not found")))?;
+
+    let header_message = BeaconBlockHeader {
+        slot: signed_block.message.slot,
+        proposer_index: signed_block.message.proposer_index,
+        state_root: signed_block.message.state_root,
+        parent_root: signed_block.message.parent_root,
+        body_root: signed_block.message.body.tree_hash_root(),
+    };
+
+    Ok((
+        SignedBeaconBlockHeader {
+            message: header_message,
+            signature: signed_block.signature,
+        },
+        block_root,
+    ))
+}
path: crates/rpc/src/handlers/header.rs
user: login: KolbyML
body: they are just showing dummy values
created_at: 2025-04-13T19:28:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: KolbyML
created_at: 2025-04-07T03:19:15Z
body: I would need to do a bit more research into this one before I can continue reviewing the PR, as you added a table, but we currently don't use a table like this in any of our in memory store code
author_association: CONTRIBUTOR
user: login: KolbyML
created_at: 2025-04-07T03:23:47Z
body: We would need to store the SignedBeaconHeader during the `on_block()` function, I would want to do a bit more research before continuing to review this
author_association: CONTRIBUTOR
user: login: KolbyML
created_at: 2025-04-11T16:21:21Z
body: After this https://github.com/ReamLabs/ream/pull/327 PR is merged you can rebase this PR 

Remove the `SignedBeaconBlockHeader` Table you added

https://ethereum.github.io/consensus-specs/specs/deneb/validator/#modified-getpayloadresponse
![image](https://github.com/user-attachments/assets/02c5dc7f-2054-4cb2-a22d-c247d6974d25)

You should just get the SignedBeaconBlock then convert it to a SignedBeaconBlockHeader as shown above

author_association: CONTRIBUTOR