blob_indexer/clients/blobscan/
types.rs1use 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
221impl 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}