Skip to content

Commit 2893ef8

Browse files
committed
feat: beacon client to fetch blob data
1 parent 63b5eac commit 2893ef8

1 file changed

Lines changed: 170 additions & 0 deletions

File tree

batcher/aligned-sdk/src/beacon.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use std::str::FromStr;
2+
3+
use ethers::core::k256::sha2::{Digest, Sha256};
4+
use reqwest::{Client, Url};
5+
use serde::{Deserialize, Serialize};
6+
use serde_json::Value;
7+
8+
pub const KZG_VERSIONED_HASH: u8 = 0x1;
9+
10+
pub struct BeaconClient {
11+
beacon_client_url: String,
12+
api_client: Client,
13+
}
14+
15+
#[derive(Debug, Serialize, Deserialize)]
16+
#[serde(untagged)]
17+
enum BeaconResponse {
18+
Success { data: Value },
19+
Error { code: u64, message: String },
20+
}
21+
22+
#[derive(Debug)]
23+
pub enum BeaconClientError {
24+
Url(url::ParseError),
25+
ReqwestError(reqwest::Error),
26+
APIError { code: u64, message: String },
27+
Deserialization(serde_json::Error),
28+
}
29+
30+
#[derive(Deserialize, Debug)]
31+
pub struct GetBlobResponse {
32+
pub blobs: Vec<BlobData>,
33+
}
34+
35+
impl GetBlobResponse {
36+
fn from_response_data(data: Value) -> Result<Self, serde_json::Error> {
37+
let blobs = Vec::<BlobData>::deserialize(data)?;
38+
39+
Ok(Self { blobs })
40+
}
41+
}
42+
43+
#[derive(Deserialize, Debug)]
44+
pub struct BlobData {
45+
pub index: u64,
46+
pub blob: String,
47+
pub kzg_commitment: String,
48+
pub kzg_proof: String,
49+
pub kzg_commitment_inclusion_proof: Vec<String>,
50+
}
51+
52+
#[derive(Deserialize, Debug)]
53+
pub struct GetBlockHeadersResponse {
54+
pub blocks: Vec<BeaconBlock>,
55+
}
56+
57+
impl GetBlockHeadersResponse {
58+
fn from_response_data(data: Value) -> Result<Self, serde_json::Error> {
59+
let blocks = Vec::<BeaconBlock>::deserialize(data)?;
60+
61+
Ok(Self { blocks })
62+
}
63+
}
64+
65+
#[derive(Deserialize, Debug)]
66+
pub struct BeaconBlock {
67+
pub root: String,
68+
pub canonical: bool,
69+
pub header: BeaconBlockHeader,
70+
}
71+
72+
#[derive(Deserialize, Debug)]
73+
pub struct BeaconBlockHeader {
74+
pub message: BeaconBlockMessage,
75+
}
76+
77+
#[derive(Deserialize, Debug)]
78+
pub struct BeaconBlockMessage {
79+
pub slot: u64,
80+
pub proposer_index: String,
81+
pub parent_root: String,
82+
pub state_root: String,
83+
pub body_root: String,
84+
}
85+
86+
impl BeaconClient {
87+
pub fn new(beacon_client_url: String) -> Self {
88+
Self {
89+
api_client: Client::new(),
90+
beacon_client_url,
91+
}
92+
}
93+
94+
pub async fn get_block_header_from_parent_hash(
95+
&self,
96+
parent_block_hash: [u8; 32],
97+
) -> Result<Option<BeaconBlock>, BeaconClientError> {
98+
let parent_block_hash_hex = format!("0x{}", hex::encode(parent_block_hash));
99+
let data = self
100+
.beacon_get(&format!(
101+
"/eth/v1/beacon/headers?parent_root={}",
102+
parent_block_hash_hex
103+
))
104+
.await?;
105+
106+
let res = GetBlockHeadersResponse::from_response_data(data)
107+
.map_err(BeaconClientError::Deserialization)?;
108+
109+
let block = res
110+
.blocks
111+
.into_iter()
112+
.find(|block| block.header.message.parent_root == parent_block_hash_hex);
113+
114+
Ok(block)
115+
}
116+
117+
pub async fn get_blobs_from_slot(
118+
&self,
119+
slot: u64,
120+
) -> Result<GetBlobResponse, BeaconClientError> {
121+
let data = self
122+
.beacon_get(&format!("/eth/v1/beacon/blob_sidecars/{}", slot))
123+
.await?;
124+
125+
GetBlobResponse::from_response_data(data).map_err(BeaconClientError::Deserialization)
126+
}
127+
128+
pub async fn get_blob_by_versioned_hash(
129+
&self,
130+
slot: u64,
131+
blob_versioned_hash_hex: String,
132+
) -> Result<Option<BlobData>, BeaconClientError> {
133+
let res = self.get_blobs_from_slot(slot).await?;
134+
135+
let blob = res.blobs.into_iter().find(|blob| {
136+
let mut hasher = Sha256::new();
137+
hasher.update(blob.kzg_commitment.clone());
138+
let mut hash: [u8; 32] = hasher.finalize().into();
139+
hash[0] = KZG_VERSIONED_HASH;
140+
let versioned_hash = format!("0x{}", hex::encode(hash));
141+
142+
versioned_hash == blob_versioned_hash_hex
143+
});
144+
145+
Ok(blob)
146+
}
147+
148+
async fn beacon_get(&self, path: &str) -> Result<Value, BeaconClientError> {
149+
let url = Url::from_str(&format!("{}{}", self.beacon_client_url, path))
150+
.map_err(BeaconClientError::Url)?;
151+
let req = self
152+
.api_client
153+
.get(url)
154+
.header("content-type", "application/json")
155+
.header("accept", "application/json");
156+
157+
let res = req.send().await.map_err(BeaconClientError::ReqwestError)?;
158+
let beacon_response = res
159+
.json::<BeaconResponse>()
160+
.await
161+
.map_err(BeaconClientError::ReqwestError)?;
162+
163+
match beacon_response {
164+
BeaconResponse::Success { data } => Ok(data),
165+
BeaconResponse::Error { code, message } => {
166+
Err(BeaconClientError::APIError { code, message })
167+
}
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)