sp1_cc_host_executor/beacon/
client.rs1use 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#[derive(Debug, Clone)]
14pub struct BeaconClient {
15 rpc_url: Url,
16 client: ReqwestClient,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
21struct BeaconResponse<T> {
22 data: T,
23}
24
25#[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#[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 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 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 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}