blob_indexer/clients/beacon/
types.rs1use 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}