use core::fmt;
use alloy::consensus::Transaction as Consensus;
use alloy::eips::eip4844::{kzg_to_versioned_hash, HeapBlob};
use alloy::primitives::{Address, BlockNumber, BlockTimestamp, TxIndex, B256, U256};
use alloy::rpc::types::{Block as ExecutionBlock, Transaction as ExecutionTransaction};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::clients::beacon::types::{Blob as BeaconBlob, KzgCommitment, Proof};
#[derive(Serialize, Deserialize, Debug)]
pub struct BlobscanBlock {
pub hash: B256,
pub number: u32,
pub slot: u32,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub number: BlockNumber,
pub hash: B256,
pub timestamp: BlockTimestamp,
pub slot: u32,
pub blob_gas_used: U256,
pub excess_blob_gas: U256,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub hash: B256,
pub from: Address,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<Address>,
pub block_number: BlockNumber,
pub index: TxIndex,
pub gas_price: U256,
pub max_fee_per_blob_gas: U256,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Blob {
pub versioned_hash: B256,
pub commitment: KzgCommitment,
pub proof: Proof,
pub data: HeapBlob,
pub tx_hash: B256,
pub index: u32,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSyncStateRequestBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_lower_synced_slot: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_slot: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_finalized_block: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_block_root: Option<B256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_block_slot: Option<u32>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GetSyncStateResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_lower_synced_slot: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_slot: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_block_root: Option<B256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_upper_synced_block_slot: Option<u32>,
}
#[derive(Debug, PartialEq)]
pub struct BlockchainSyncState {
pub last_finalized_block: Option<u32>,
pub last_lower_synced_slot: Option<u32>,
pub last_upper_synced_slot: Option<u32>,
pub last_upper_synced_block_root: Option<B256>,
pub last_upper_synced_block_slot: Option<u32>,
}
#[derive(Serialize, Debug)]
pub struct IndexRequestBody {
pub block: Block,
pub transactions: Vec<Transaction>,
pub blobs: Vec<Blob>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct HandleReorgRequestBody {
pub forwarded_blocks: Vec<B256>,
pub rewinded_blocks: Vec<B256>,
}
impl fmt::Debug for Blob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Blob {{ versioned_hash: {}, commitment: {}, tx_hash: {}, index: {}, data: [omitted] }}",
self.versioned_hash, self.commitment, self.tx_hash, self.index
)
}
}
impl<'a> TryFrom<(&'a ExecutionBlock<ExecutionTransaction>, u32)> for Block {
type Error = anyhow::Error;
fn try_from(
(execution_block, slot): (&'a ExecutionBlock<ExecutionTransaction>, u32),
) -> Result<Self, Self::Error> {
let number = execution_block.header.number;
let hash = execution_block.header.hash;
let timestamp = execution_block.header.timestamp;
let blob_gas_used = match execution_block.header.blob_gas_used {
Some(blob_gas_used) => U256::from::<u64>(blob_gas_used),
None => {
return Err(anyhow::anyhow!(
"Missing `blob_gas_used` field in execution block {hash} with number {number}",
hash = hash,
number = number
))
}
};
let excess_blob_gas = match execution_block.header.excess_blob_gas {
Some(excess_blob_gas) => U256::from::<u64>(excess_blob_gas),
None => {
return Err(anyhow::anyhow!(
"Missing `excess_blob_gas` field in execution block {hash} with number {number}",
hash = hash,
number = number
))
}
};
Ok(Self {
number,
hash,
timestamp,
slot,
blob_gas_used,
excess_blob_gas,
})
}
}
impl<'a>
TryFrom<(
&'a ExecutionTransaction,
&'a ExecutionBlock<ExecutionTransaction>,
)> for Transaction
{
type Error = anyhow::Error;
fn try_from(
(execution_tx, execution_block): (
&'a ExecutionTransaction,
&'a ExecutionBlock<ExecutionTransaction>,
),
) -> Result<Self, Self::Error> {
let block_number = execution_block.header.number;
let hash = execution_tx
.info()
.hash
.with_context(|| format!("Missing `hash` field in tx within block {block_number}"))?;
let index = execution_tx.transaction_index.with_context(|| {
format!("Missing `transaction_index` field in tx {hash} within block {block_number}")
})?;
let from = execution_tx.inner.signer();
let to = Some(execution_tx.to().with_context(|| {
format!("Missing `to` field in tx {hash} within block {block_number}")
})?);
let gas_price = U256::from::<u128>(execution_tx.effective_gas_price(None));
let max_fee_per_blob_gas = match execution_tx.max_fee_per_blob_gas() {
Some(max_fee_per_blob_gas) => U256::from::<u128>(max_fee_per_blob_gas),
None => {
return Err(anyhow::anyhow!(
"Missing `max_fee_per_blob_gas` field in tx {hash} within block {block_number}",
hash = hash,
block_number = block_number
))
}
};
Ok(Self {
block_number,
index,
hash,
from,
to,
gas_price,
max_fee_per_blob_gas,
})
}
}
impl<'a> From<(&'a BeaconBlob, u32, &B256)> for Blob {
fn from((blob, index, tx_hash): (&'a BeaconBlob, u32, &B256)) -> Self {
Self {
tx_hash: *tx_hash,
index,
commitment: blob.kzg_commitment,
proof: blob.kzg_proof,
data: blob.blob.clone(),
versioned_hash: kzg_to_versioned_hash(blob.kzg_commitment.as_ref()),
}
}
}
impl From<GetSyncStateResponse> for BlockchainSyncState {
fn from(response: GetSyncStateResponse) -> Self {
Self {
last_finalized_block: None,
last_lower_synced_slot: response.last_lower_synced_slot,
last_upper_synced_slot: response.last_upper_synced_slot,
last_upper_synced_block_root: response.last_upper_synced_block_root,
last_upper_synced_block_slot: response.last_upper_synced_block_slot,
}
}
}
impl From<BlockchainSyncState> for UpdateSyncStateRequestBody {
fn from(sync_state: BlockchainSyncState) -> Self {
Self {
last_lower_synced_slot: sync_state.last_lower_synced_slot,
last_upper_synced_slot: sync_state.last_upper_synced_slot,
last_finalized_block: sync_state.last_finalized_block,
last_upper_synced_block_root: sync_state.last_upper_synced_block_root,
last_upper_synced_block_slot: sync_state.last_upper_synced_block_slot,
}
}
}