1use crate::Error;
11use apex_sdk_core::{BlockEvent, BlockInfo, DetailedBlockInfo, ExtrinsicInfo};
12use subxt::{OnlineClient, PolkadotConfig};
13use tracing::debug;
14
15pub struct BlockQuery {
17 client: OnlineClient<PolkadotConfig>,
18}
19
20impl BlockQuery {
21 pub fn new(client: OnlineClient<PolkadotConfig>) -> Self {
23 Self { client }
24 }
25
26 pub async fn get_block_by_number(&self, block_number: u64) -> Result<BlockInfo, Error> {
33 debug!("Fetching block by number: {}", block_number);
34
35 let latest_block = self
37 .client
38 .blocks()
39 .at_latest()
40 .await
41 .map_err(|e| Error::Connection(format!("Failed to get latest block: {}", e)))?;
42
43 let latest_number = latest_block.number() as u64;
44
45 if block_number > latest_number {
47 return Err(Error::Transaction(format!(
48 "Block {} not found (latest: {})",
49 block_number, latest_number
50 )));
51 }
52
53 if block_number == latest_number {
55 return self.parse_block_info(latest_block).await;
56 }
57
58 let search_depth = latest_number.saturating_sub(block_number);
61 const MAX_TRAVERSE_DEPTH: u64 = 100;
62
63 if search_depth <= MAX_TRAVERSE_DEPTH {
64 let mut current_block = latest_block;
66 for _ in 0..search_depth {
67 let parent_hash = current_block.header().parent_hash;
68 match self.client.blocks().at(parent_hash).await {
69 Ok(parent) => {
70 if parent.number() as u64 == block_number {
71 return self.parse_block_info(parent).await;
72 }
73 current_block = parent;
74 }
75 Err(e) => {
76 return Err(Error::Connection(format!(
77 "Failed to traverse to block {}: {}",
78 block_number, e
79 )));
80 }
81 }
82 }
83 }
84
85 Err(Error::Transaction(format!(
88 "Block {} is too far from current height {}. Consider using get_block_by_hash if hash is known.",
89 block_number, latest_number
90 )))
91 }
92
93 pub async fn get_block_by_hash(&self, hash_hex: &str) -> Result<BlockInfo, Error> {
97 debug!("Fetching block by hash: {}", hash_hex);
98
99 let hash_hex = hash_hex.trim_start_matches("0x");
101 let hash_bytes = hex::decode(hash_hex)
102 .map_err(|e| Error::Transaction(format!("Invalid block hash: {}", e)))?;
103
104 if hash_bytes.len() != 32 {
105 return Err(Error::Transaction(
106 "Block hash must be 32 bytes".to_string(),
107 ));
108 }
109
110 let mut hash_array = [0u8; 32];
111 hash_array.copy_from_slice(&hash_bytes);
112 let block_hash: subxt::utils::H256 = hash_array.into();
113
114 let block = self
116 .client
117 .blocks()
118 .at(block_hash)
119 .await
120 .map_err(|e| Error::Connection(format!("Failed to get block: {}", e)))?;
121
122 self.parse_block_info(block).await
123 }
124
125 pub async fn get_detailed_block(&self, block_number: u64) -> Result<DetailedBlockInfo, Error> {
127 debug!("Fetching detailed block info for block: {}", block_number);
128
129 let latest_block = self
131 .client
132 .blocks()
133 .at_latest()
134 .await
135 .map_err(|e| Error::Connection(format!("Failed to get latest block: {}", e)))?;
136
137 let latest_number = latest_block.number() as u64;
138
139 if block_number > latest_number {
140 return Err(Error::Transaction(format!(
141 "Block {} not found (latest: {})",
142 block_number, latest_number
143 )));
144 }
145
146 let block = if block_number == latest_number {
148 latest_block
149 } else {
150 let search_depth = latest_number.saturating_sub(block_number);
152 const MAX_TRAVERSE_DEPTH: u64 = 100;
153
154 if search_depth > MAX_TRAVERSE_DEPTH {
155 return Err(Error::Transaction(format!(
156 "Block {} is too far from current height {}",
157 block_number, latest_number
158 )));
159 }
160
161 let mut current_block = latest_block;
162 for _ in 0..search_depth {
163 let parent_hash = current_block.header().parent_hash;
164 current_block =
165 self.client.blocks().at(parent_hash).await.map_err(|e| {
166 Error::Connection(format!("Failed to traverse blocks: {}", e))
167 })?;
168
169 if current_block.number() as u64 == block_number {
170 break;
171 }
172 }
173 current_block
174 };
175
176 let basic_info = self.parse_block_info(block.clone()).await?;
178
179 let extrinsics = self.extract_extrinsics(&block).await?;
181
182 let events = self.extract_block_events(&block).await?;
184
185 Ok(DetailedBlockInfo {
186 basic: basic_info,
187 extrinsics,
188 events,
189 })
190 }
191
192 async fn parse_block_info(
194 &self,
195 block: subxt::blocks::Block<PolkadotConfig, OnlineClient<PolkadotConfig>>,
196 ) -> Result<BlockInfo, Error> {
197 let number = block.number() as u64;
198 let hash = format!("0x{}", hex::encode(block.hash()));
199 let parent_hash = format!("0x{}", hex::encode(block.header().parent_hash));
200
201 let timestamp = self.extract_timestamp(&block).await?;
203
204 let extrinsics = block
206 .extrinsics()
207 .await
208 .map_err(|e| Error::Transaction(format!("Failed to get extrinsics: {}", e)))?;
209
210 let mut transactions = Vec::new();
211 let extrinsic_count = extrinsics.len() as u32;
212
213 for ext_details in extrinsics.iter() {
214 let ext_bytes = ext_details.bytes();
215 let hash = sp_core::blake2_256(ext_bytes);
216 transactions.push(format!("0x{}", hex::encode(hash)));
217 }
218
219 let is_finalized = self.check_finality(block.hash()).await?;
221
222 let state_root = Some(format!("0x{}", hex::encode(block.header().state_root)));
224 let extrinsics_root = Some(format!("0x{}", hex::encode(block.header().extrinsics_root)));
225
226 let event_count = self.count_block_events(&block).await.ok();
228
229 Ok(BlockInfo {
230 number,
231 hash,
232 parent_hash,
233 timestamp,
234 transactions,
235 state_root,
236 extrinsics_root,
237 extrinsic_count,
238 event_count,
239 is_finalized,
240 })
241 }
242
243 async fn extract_timestamp(
250 &self,
251 block: &subxt::blocks::Block<PolkadotConfig, OnlineClient<PolkadotConfig>>,
252 ) -> Result<u64, Error> {
253 if let Ok(extrinsics) = block.extrinsics().await {
257 for ext in extrinsics.iter() {
258 if let Ok(pallet) = ext.pallet_name() {
259 if pallet == "Timestamp" {
260 if let Ok(call) = ext.variant_name() {
261 if call == "set" {
262 debug!(
266 "Found Timestamp::set extrinsic in block {}",
267 block.number()
268 );
269 }
270 }
271 }
272 }
273 }
274 }
275
276 debug!(
280 "Using current time as timestamp for block {} (dynamic API limitation)",
281 block.number()
282 );
283 Ok(chrono::Utc::now().timestamp() as u64)
284 }
285
286 async fn check_finality(&self, block_hash: subxt::utils::H256) -> Result<bool, Error> {
292 let latest_block = self
294 .client
295 .blocks()
296 .at_latest()
297 .await
298 .map_err(|e| Error::Connection(format!("Failed to get latest block: {}", e)))?;
299
300 let latest_number = latest_block.number();
301
302 let check_block = self
304 .client
305 .blocks()
306 .at(block_hash)
307 .await
308 .map_err(|e| Error::Connection(format!("Failed to get block: {}", e)))?;
309
310 let block_number = check_block.number();
311
312 if latest_number.saturating_sub(block_number) > 100 {
315 return Ok(true);
316 }
317
318 Ok(false)
320 }
321
322 async fn extract_extrinsics(
324 &self,
325 block: &subxt::blocks::Block<PolkadotConfig, OnlineClient<PolkadotConfig>>,
326 ) -> Result<Vec<ExtrinsicInfo>, Error> {
327 let extrinsics = block
328 .extrinsics()
329 .await
330 .map_err(|e| Error::Transaction(format!("Failed to get extrinsics: {}", e)))?;
331
332 let mut extrinsic_infos = Vec::new();
333
334 for ext_details in extrinsics.iter() {
335 let index = ext_details.index();
336 let ext_bytes = ext_details.bytes();
337 let hash = format!("0x{}", hex::encode(sp_core::blake2_256(ext_bytes)));
338
339 let signed = ext_details.is_signed();
341 let signer = if signed {
342 ext_details
343 .address_bytes()
344 .map(|bytes| format!("0x{}", hex::encode(bytes)))
345 } else {
346 None
347 };
348
349 let pallet = ext_details.pallet_name().unwrap_or("Unknown").to_string();
351 let call = ext_details.variant_name().unwrap_or("Unknown").to_string();
352
353 let mut success = false;
355 if let Ok(events) = ext_details.events().await {
356 for event in events.iter().flatten() {
357 if event.pallet_name() == "System" && event.variant_name() == "ExtrinsicSuccess"
358 {
359 success = true;
360 break;
361 }
362 }
363 }
364
365 extrinsic_infos.push(ExtrinsicInfo {
366 index,
367 hash,
368 signed,
369 signer,
370 pallet,
371 call,
372 success,
373 });
374 }
375
376 Ok(extrinsic_infos)
377 }
378
379 async fn extract_block_events(
381 &self,
382 block: &subxt::blocks::Block<PolkadotConfig, OnlineClient<PolkadotConfig>>,
383 ) -> Result<Vec<BlockEvent>, Error> {
384 let extrinsics = block
385 .extrinsics()
386 .await
387 .map_err(|e| Error::Transaction(format!("Failed to get extrinsics: {}", e)))?;
388
389 let mut all_events = Vec::new();
390 let mut event_index = 0u32;
391
392 for ext_details in extrinsics.iter() {
393 let extrinsic_index = ext_details.index();
394
395 if let Ok(events) = ext_details.events().await {
396 for event in events.iter().flatten() {
397 all_events.push(BlockEvent {
398 index: event_index,
399 extrinsic_index: Some(extrinsic_index),
400 pallet: event.pallet_name().to_string(),
401 event: event.variant_name().to_string(),
402 });
403 event_index += 1;
404 }
405 }
406 }
407
408 Ok(all_events)
409 }
410
411 async fn count_block_events(
413 &self,
414 block: &subxt::blocks::Block<PolkadotConfig, OnlineClient<PolkadotConfig>>,
415 ) -> Result<u32, Error> {
416 let extrinsics = block
417 .extrinsics()
418 .await
419 .map_err(|e| Error::Transaction(format!("Failed to get extrinsics: {}", e)))?;
420
421 let mut count = 0u32;
422 for ext_details in extrinsics.iter() {
423 if let Ok(events) = ext_details.events().await {
424 count += events.iter().count() as u32;
425 }
426 }
427
428 Ok(count)
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 #[test]
435 fn test_block_hash_parsing() {
436 let hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
438 let stripped = hash.trim_start_matches("0x");
439 assert_eq!(stripped.len(), 64);
440
441 let hash2 = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
443 assert_eq!(hash2.len(), 64);
444 }
445}