blob_indexer/clients/blobscan/
types.rs

1use core::fmt;
2
3use alloy::consensus::Transaction as Consensus;
4use alloy::eips::eip4844::{kzg_to_versioned_hash, HeapBlob};
5use alloy::primitives::{Address, BlockNumber, BlockTimestamp, TxIndex, B256, U256};
6use alloy::rpc::types::{Block as ExecutionBlock, Transaction as ExecutionTransaction};
7use anyhow::{Context, Result};
8
9use serde::{Deserialize, Serialize};
10
11use crate::clients::beacon::types::{Blob as BeaconBlob, KzgCommitment, Proof};
12
13#[derive(Serialize, Deserialize, Debug)]
14pub struct BlobscanBlock {
15    pub hash: B256,
16    pub number: u32,
17    pub slot: u32,
18}
19
20#[derive(Serialize, Deserialize, Debug)]
21#[serde(rename_all = "camelCase")]
22pub struct Block {
23    pub number: BlockNumber,
24    pub hash: B256,
25    pub timestamp: BlockTimestamp,
26    pub slot: u32,
27    pub blob_gas_used: U256,
28    pub excess_blob_gas: U256,
29}
30
31#[derive(Serialize, Deserialize, Debug)]
32#[serde(rename_all = "camelCase")]
33pub struct Transaction {
34    pub hash: B256,
35    pub from: Address,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub to: Option<Address>,
38    pub block_number: BlockNumber,
39    pub index: TxIndex,
40    pub gas_price: U256,
41    pub max_fee_per_blob_gas: U256,
42}
43
44#[derive(Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct Blob {
47    pub versioned_hash: B256,
48    pub commitment: KzgCommitment,
49    pub proof: Proof,
50    pub data: HeapBlob,
51    pub tx_hash: B256,
52    pub index: u32,
53}
54
55#[derive(Serialize, Debug)]
56#[serde(rename_all = "camelCase")]
57pub struct BlockchainSyncStateRequest {
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub last_lower_synced_slot: Option<u32>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub last_upper_synced_slot: Option<u32>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub last_finalized_block: Option<u32>,
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub last_upper_synced_block_root: Option<B256>,
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub last_upper_synced_block_slot: Option<u32>,
68}
69
70#[derive(Deserialize, Debug)]
71#[serde(rename_all = "camelCase")]
72pub struct BlockchainSyncStateResponse {
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub last_lower_synced_slot: Option<u32>,
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub last_upper_synced_slot: Option<u32>,
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub last_upper_synced_block_root: Option<B256>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub last_upper_synced_block_slot: Option<u32>,
81}
82
83#[derive(Debug, PartialEq)]
84pub struct BlockchainSyncState {
85    pub last_finalized_block: Option<u32>,
86    pub last_lower_synced_slot: Option<u32>,
87    pub last_upper_synced_slot: Option<u32>,
88    pub last_upper_synced_block_root: Option<B256>,
89    pub last_upper_synced_block_slot: Option<u32>,
90}
91
92#[derive(Serialize, Debug)]
93pub struct IndexRequest {
94    pub block: Block,
95    pub transactions: Vec<Transaction>,
96    pub blobs: Vec<Blob>,
97}
98
99#[derive(Deserialize, Serialize, Debug)]
100#[serde(rename_all = "camelCase")]
101pub struct ReorgedBlocksRequestBody {
102    pub forwarded_blocks: Vec<B256>,
103    pub rewinded_blocks: Vec<B256>,
104}
105
106impl fmt::Debug for Blob {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(
109            f,
110            "Blob {{ versioned_hash: {}, commitment: {}, tx_hash: {}, index: {}, data: [omitted] }}",
111            self.versioned_hash, self.commitment, self.tx_hash, self.index
112        )
113    }
114}
115
116impl<'a> TryFrom<(&'a ExecutionBlock<ExecutionTransaction>, u32)> for Block {
117    type Error = anyhow::Error;
118
119    fn try_from(
120        (execution_block, slot): (&'a ExecutionBlock<ExecutionTransaction>, u32),
121    ) -> Result<Self, Self::Error> {
122        let number = execution_block.header.number;
123        let hash = execution_block.header.hash;
124        let timestamp = execution_block.header.timestamp;
125        let blob_gas_used = match execution_block.header.blob_gas_used {
126            Some(blob_gas_used) => U256::from::<u64>(blob_gas_used),
127            None => {
128                return Err(anyhow::anyhow!(
129                    "Missing `blob_gas_used` field in execution block {hash} with number {number}",
130                    hash = hash,
131                    number = number
132                ))
133            }
134        };
135        let excess_blob_gas = match execution_block.header.excess_blob_gas {
136            Some(excess_blob_gas) => U256::from::<u64>(excess_blob_gas),
137            None => {
138                return Err(anyhow::anyhow!(
139                "Missing `excess_blob_gas` field in execution block {hash} with number {number}",
140                hash = hash,
141                number = number
142            ))
143            }
144        };
145
146        Ok(Self {
147            number,
148            hash,
149            timestamp,
150            slot,
151            blob_gas_used,
152            excess_blob_gas,
153        })
154    }
155}
156
157impl<'a>
158    TryFrom<(
159        &'a ExecutionTransaction,
160        &'a ExecutionBlock<ExecutionTransaction>,
161    )> for Transaction
162{
163    type Error = anyhow::Error;
164
165    fn try_from(
166        (execution_tx, execution_block): (
167            &'a ExecutionTransaction,
168            &'a ExecutionBlock<ExecutionTransaction>,
169        ),
170    ) -> Result<Self, Self::Error> {
171        let block_number = execution_block.header.number;
172        let hash = execution_tx
173            .info()
174            .hash
175            .with_context(|| format!("Missing `hash` field in tx within block {block_number}"))?;
176        let index = execution_tx.transaction_index.with_context(|| {
177            format!("Missing `transaction_index` field in tx {hash} within block {block_number}")
178        })?;
179        let from = execution_tx.inner.signer();
180        let to = Some(execution_tx.to().with_context(|| {
181            format!("Missing `to` field in tx {hash} within block {block_number}")
182        })?);
183        let gas_price = U256::from::<u128>(execution_tx.effective_gas_price(None));
184
185        let max_fee_per_blob_gas = match execution_tx.max_fee_per_blob_gas() {
186            Some(max_fee_per_blob_gas) => U256::from::<u128>(max_fee_per_blob_gas),
187            None => {
188                return Err(anyhow::anyhow!(
189                    "Missing `max_fee_per_blob_gas` field in tx {hash} within block {block_number}",
190                    hash = hash,
191                    block_number = block_number
192                ))
193            }
194        };
195
196        Ok(Self {
197            block_number,
198            index,
199            hash,
200            from,
201            to,
202            gas_price,
203            max_fee_per_blob_gas,
204        })
205    }
206}
207
208impl<'a> From<(&'a BeaconBlob, u32, &B256)> for Blob {
209    fn from((blob, index, tx_hash): (&'a BeaconBlob, u32, &B256)) -> Self {
210        Self {
211            tx_hash: *tx_hash,
212            index,
213            commitment: blob.kzg_commitment,
214            proof: blob.kzg_proof,
215            data: blob.blob.clone(),
216            versioned_hash: kzg_to_versioned_hash(blob.kzg_commitment.as_ref()),
217        }
218    }
219}
220
221// impl<'a> From<(&'a BeaconBlob, &'a B256, usize, &'a B256)> for Blob {
222//     fn from(
223//         (blob_data, versioned_hash, index, tx_hash): (&'a BeaconBlob, &'a B256, usize, &'a B256),
224//     ) -> Self {
225//         Self {
226//             tx_hash: *tx_hash,
227//             index: index as u32,
228//             commitment: blob_data.kzg_commitment.clone(),
229//             proof: blob_data.kzg_proof.clone(),
230//             data: blob_data.blob.clone(),
231//             versioned_hash: *versioned_hash,
232//         }
233//     }
234// }
235
236impl From<BlockchainSyncStateResponse> for BlockchainSyncState {
237    fn from(response: BlockchainSyncStateResponse) -> Self {
238        Self {
239            last_finalized_block: None,
240            last_lower_synced_slot: response.last_lower_synced_slot,
241            last_upper_synced_slot: response.last_upper_synced_slot,
242            last_upper_synced_block_root: response.last_upper_synced_block_root,
243            last_upper_synced_block_slot: response.last_upper_synced_block_slot,
244        }
245    }
246}
247
248impl From<BlockchainSyncState> for BlockchainSyncStateRequest {
249    fn from(sync_state: BlockchainSyncState) -> Self {
250        Self {
251            last_lower_synced_slot: sync_state.last_lower_synced_slot,
252            last_upper_synced_slot: sync_state.last_upper_synced_slot,
253            last_finalized_block: sync_state.last_finalized_block,
254            last_upper_synced_block_root: sync_state.last_upper_synced_block_root,
255            last_upper_synced_block_slot: sync_state.last_upper_synced_block_slot,
256        }
257    }
258}