sp1_cc_host_executor/beacon/
client.rs

1use alloy_primitives::B256;
2use ethereum_consensus::{phase0::SignedBeaconBlockHeader, Fork};
3use reqwest::Client as ReqwestClient;
4use serde::{Deserialize, Serialize};
5use serde_json::value::RawValue;
6use url::Url;
7
8use crate::BeaconError;
9
10use super::SignedBeaconBlock;
11
12/// A client used for connecting and querying a beacon node.
13#[derive(Debug, Clone)]
14pub struct BeaconClient {
15    rpc_url: Url,
16    client: ReqwestClient,
17}
18
19/// The raw response returned by the Beacon Node APIs.
20#[derive(Debug, Serialize, Deserialize)]
21struct BeaconResponse<T> {
22    data: T,
23}
24
25/// The raw response returned by the Beacon Node APIs.
26#[derive(Debug, Serialize, Deserialize)]
27struct BeaconRawResponse<'a> {
28    pub version: Fork,
29    pub execution_optimistic: bool,
30    pub finalized: bool,
31    #[serde(borrow)]
32    data: &'a RawValue,
33}
34
35/// The response returned by the `get_block_header` API.
36#[derive(Debug, Serialize, Deserialize)]
37struct BlockHeaderResponse {
38    pub root: B256,
39    pub canonical: bool,
40    pub header: SignedBeaconBlockHeader,
41}
42
43impl<'de> serde::Deserialize<'de> for SignedBeaconBlock {
44    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45    where
46        D: serde::Deserializer<'de>,
47    {
48        let BeaconRawResponse { version, data, .. } = BeaconRawResponse::deserialize(deserializer)?;
49        let data = match version {
50            Fork::Phase0 => serde_json::from_str(data.get()).map(SignedBeaconBlock::Phase0),
51            Fork::Altair => serde_json::from_str(data.get()).map(SignedBeaconBlock::Altair),
52            Fork::Bellatrix => serde_json::from_str(data.get()).map(SignedBeaconBlock::Bellatrix),
53            Fork::Capella => serde_json::from_str(data.get()).map(SignedBeaconBlock::Capella),
54            Fork::Deneb => serde_json::from_str(data.get()).map(SignedBeaconBlock::Deneb),
55            Fork::Electra => serde_json::from_str(data.get()).map(SignedBeaconBlock::Electra),
56        }
57        .map_err(serde::de::Error::custom)?;
58
59        Ok(data)
60    }
61}
62
63impl BeaconClient {
64    pub fn new(rpc_url: Url) -> Self {
65        Self { rpc_url, client: ReqwestClient::new() }
66    }
67
68    /// Gets the block at the given `beacon_id`.
69    pub async fn get_block(&self, beacon_id: String) -> Result<SignedBeaconBlock, BeaconError> {
70        let endpoint = format!("{}eth/v2/beacon/blocks/{}", self.rpc_url, beacon_id);
71
72        let response = self.client.get(&endpoint).send().await?;
73        let block = response.error_for_status()?.json::<SignedBeaconBlock>().await?;
74
75        Ok(block)
76    }
77
78    /// Gets the block header at the given given parent root.
79    pub async fn get_header_from_parent_root(
80        &self,
81        parent_root: String,
82    ) -> Result<(B256, SignedBeaconBlockHeader), BeaconError> {
83        let endpoint = format!("{}eth/v1/beacon/headers", self.rpc_url);
84
85        let response =
86            self.client.get(&endpoint).query(&[("parent_root", parent_root)]).send().await?;
87        let response =
88            response.error_for_status()?.json::<BeaconResponse<Vec<BlockHeaderResponse>>>().await?;
89
90        Ok((response.data[0].root, response.data[0].header.clone()))
91    }
92
93    /// Retrieves the execution bock hash at the given `beacon_id`.
94    pub async fn get_execution_payload_block_hash(
95        &self,
96        beacon_id: String,
97    ) -> Result<B256, BeaconError> {
98        let block = self.get_block(beacon_id).await?;
99        let block_hash = match block {
100            SignedBeaconBlock::Phase0(_) => None,
101            SignedBeaconBlock::Altair(_) => None,
102            SignedBeaconBlock::Bellatrix(b) => Some(b.message.body.execution_payload.block_hash),
103            SignedBeaconBlock::Capella(b) => Some(b.message.body.execution_payload.block_hash),
104            SignedBeaconBlock::Deneb(b) => Some(b.message.body.execution_payload.block_hash),
105            SignedBeaconBlock::Electra(b) => Some(b.message.body.execution_payload.block_hash),
106        };
107
108        block_hash.ok_or_else(|| BeaconError::ExecutionPayloadMissing).map(|h| B256::from_slice(&h))
109    }
110}