blob_indexer/clients/beacon/
types.rs

1use std::{fmt, str::FromStr};
2
3use alloy::{consensus::Bytes48, eips::eip4844::HeapBlob, primitives::B256};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7use crate::clients::common::ClientError;
8
9use super::CommonBeaconClient;
10
11pub type KzgCommitment = Bytes48;
12
13pub type Proof = Bytes48;
14
15#[derive(Serialize, Debug, Clone, PartialEq)]
16pub enum BlockId {
17    Head,
18    Finalized,
19    Slot(u32),
20    Hash(B256),
21}
22
23#[derive(Serialize, Debug, Clone)]
24#[serde(rename_all = "snake_case")]
25pub enum Topic {
26    Head,
27    FinalizedCheckpoint,
28}
29
30impl fmt::Display for Topic {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Topic::Head => write!(f, "head"),
34            Topic::FinalizedCheckpoint => write!(f, "finalized_checkpoint"),
35        }
36    }
37}
38
39#[derive(Deserialize, Debug)]
40pub struct SpecResponse {
41    pub data: Spec,
42}
43
44#[derive(Deserialize, Debug)]
45pub struct Spec {
46    #[serde(rename = "DEPOSIT_NETWORK_ID", deserialize_with = "deserialize_u64")]
47    pub deposit_network_id: u64,
48}
49#[derive(Deserialize, Debug)]
50pub struct Block {
51    pub blob_kzg_commitments: Option<Vec<KzgCommitment>>,
52    pub execution_payload: Option<ExecutionPayload>,
53    pub parent_root: B256,
54    #[serde(deserialize_with = "deserialize_u32")]
55    pub slot: u32,
56}
57
58#[derive(Deserialize, Debug)]
59pub struct ExecutionPayload {
60    pub block_hash: B256,
61    #[serde(deserialize_with = "deserialize_u32")]
62    pub block_number: u32,
63}
64
65#[derive(Deserialize, Debug)]
66pub struct BlockBody {
67    pub execution_payload: Option<ExecutionPayload>,
68    pub blob_kzg_commitments: Option<Vec<KzgCommitment>>,
69}
70#[derive(Deserialize, Debug)]
71pub struct BlockMessage {
72    pub body: BlockBody,
73    pub parent_root: B256,
74    #[serde(deserialize_with = "deserialize_u32")]
75    pub slot: u32,
76}
77
78#[derive(Deserialize, Debug)]
79pub struct BlockData {
80    pub message: BlockMessage,
81}
82
83#[derive(Deserialize, Debug)]
84pub struct BlockResponse {
85    pub data: BlockData,
86}
87
88#[derive(Deserialize, Debug)]
89pub struct Blob {
90    pub kzg_commitment: KzgCommitment,
91    pub kzg_proof: Proof,
92    pub blob: HeapBlob,
93}
94
95#[derive(Deserialize, Debug)]
96pub struct BlobsResponse {
97    pub data: Vec<Blob>,
98}
99
100#[derive(Deserialize, Debug)]
101pub struct BlockHeaderResponse {
102    pub data: BlockHeaderData,
103}
104
105#[derive(Deserialize, Debug, Clone)]
106pub struct BlockHeader {
107    pub root: B256,
108    pub parent_root: B256,
109    pub slot: u32,
110}
111
112#[derive(Deserialize, Debug)]
113pub struct BlockHeaderData {
114    pub root: B256,
115    pub header: InnerBlockHeader,
116}
117#[derive(Deserialize, Debug)]
118pub struct InnerBlockHeader {
119    pub message: BlockHeaderMessage,
120}
121
122#[derive(Deserialize, Debug)]
123pub struct BlockHeaderMessage {
124    pub parent_root: B256,
125    #[serde(deserialize_with = "deserialize_u32")]
126    pub slot: u32,
127}
128
129#[derive(Deserialize, Debug)]
130pub struct HeadEventData {
131    #[serde(deserialize_with = "deserialize_u32")]
132    pub slot: u32,
133    #[allow(dead_code)]
134    pub block: B256,
135}
136
137#[derive(Deserialize, Debug)]
138pub struct FinalizedCheckpointEventData {
139    pub block: B256,
140}
141
142fn deserialize_u32<'de, D>(deserializer: D) -> Result<u32, D::Error>
143where
144    D: serde::Deserializer<'de>,
145{
146    let value = String::deserialize(deserializer)?;
147
148    value.parse::<u32>().map_err(serde::de::Error::custom)
149}
150
151fn deserialize_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
152where
153    D: serde::Deserializer<'de>,
154{
155    let value = String::deserialize(deserializer)?;
156
157    value.parse::<u64>().map_err(serde::de::Error::custom)
158}
159
160impl BlockId {
161    pub fn to_detailed_string(&self) -> String {
162        match self {
163            BlockId::Head => String::from("head"),
164            BlockId::Finalized => String::from("finalized"),
165            BlockId::Slot(slot) => slot.to_string(),
166            BlockId::Hash(hash) => format!("0x{:x}", hash),
167        }
168    }
169}
170
171impl fmt::Display for BlockId {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            BlockId::Head => write!(f, "head"),
175            BlockId::Finalized => write!(f, "finalized"),
176            BlockId::Slot(slot) => write!(f, "{}", slot),
177            BlockId::Hash(hash) => write!(f, "{}", hash),
178        }
179    }
180}
181
182impl FromStr for BlockId {
183    type Err = String;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        match s {
187            "head" => Ok(BlockId::Head),
188            "finalized" => Ok(BlockId::Finalized),
189            _ => match s.parse::<u32>() {
190                Ok(num) => Ok(BlockId::Slot(num)),
191                Err(_) => {
192                    if s.starts_with("0x") {
193                        match B256::from_str(s) {
194                            Ok(hash) => Ok(BlockId::Hash(hash)),
195                            Err(_) => Err(format!("Invalid block ID hash: {s}")),
196                        }
197                    } else {
198                        Err(
199                            format!("Invalid block ID: {s}. Expected 'head', 'finalized', a hash or a number."),
200                        )
201                    }
202                }
203            },
204        }
205    }
206}
207
208impl From<&Topic> for String {
209    fn from(value: &Topic) -> Self {
210        match value {
211            Topic::Head => String::from("head"),
212            Topic::FinalizedCheckpoint => String::from("finalized_checkpoint"),
213        }
214    }
215}
216
217impl From<B256> for BlockId {
218    fn from(value: B256) -> Self {
219        BlockId::Hash(value)
220    }
221}
222
223impl From<u32> for BlockId {
224    fn from(value: u32) -> Self {
225        BlockId::Slot(value)
226    }
227}
228
229impl From<BlockHeaderResponse> for BlockHeader {
230    fn from(response: BlockHeaderResponse) -> Self {
231        BlockHeader {
232            root: response.data.root,
233            parent_root: response.data.header.message.parent_root,
234            slot: response.data.header.message.slot,
235        }
236    }
237}
238
239impl From<BlockResponse> for Block {
240    fn from(response: BlockResponse) -> Self {
241        Block {
242            blob_kzg_commitments: response.data.message.body.blob_kzg_commitments,
243            execution_payload: response.data.message.body.execution_payload,
244            parent_root: response.data.message.parent_root,
245            slot: response.data.message.slot,
246        }
247    }
248}
249
250#[derive(Debug, thiserror::Error)]
251pub enum BlockIdResolutionError {
252    #[error("Block with id '{0}' not found")]
253    BlockNotFound(BlockId),
254    #[error("Failed to resolve block id '{block_id}'")]
255    FailedBlockIdResolution {
256        block_id: BlockId,
257        #[source]
258        error: ClientError,
259    },
260}
261
262#[async_trait]
263pub trait BlockIdResolution: Send + Sync {
264    async fn resolve_to_slot(
265        &self,
266        beacon_client: &dyn CommonBeaconClient,
267    ) -> Result<u32, BlockIdResolutionError>;
268}
269
270#[async_trait]
271impl BlockIdResolution for BlockId {
272    async fn resolve_to_slot(
273        &self,
274        beacon_client: &dyn CommonBeaconClient,
275    ) -> Result<u32, BlockIdResolutionError> {
276        match self {
277            BlockId::Slot(slot) => Ok(*slot),
278            _ => match beacon_client
279                .get_block_header(self.clone())
280                .await
281                .map_err(|err| BlockIdResolutionError::FailedBlockIdResolution {
282                    block_id: self.clone(),
283                    error: err,
284                })? {
285                Some(header) => Ok(header.slot),
286                None => Err(BlockIdResolutionError::BlockNotFound(self.clone())),
287            },
288        }
289    }
290}