1use crate::{
3 chain::client::QuantusClient,
4 cli::{address_format::QuantusSS58, storage},
5 error::QuantusError,
6 log_error, log_print, log_success,
7};
8use clap::Subcommand;
9use colored::Colorize;
10use qp_poseidon::PoseidonHasher;
11use sp_core::crypto::Ss58Codec;
12use std::str::FromStr;
13use subxt::events::EventDetails;
14
15#[derive(Subcommand, Debug)]
17pub enum BlockCommands {
18 Analyze {
20 #[arg(long)]
22 number: Option<u32>,
23
24 #[arg(long)]
26 hash: Option<String>,
27
28 #[arg(long)]
30 latest: bool,
31
32 #[arg(long)]
34 storage: bool,
35
36 #[arg(long)]
38 extrinsics: bool,
39
40 #[arg(long)]
42 extrinsics_details: bool,
43
44 #[arg(long)]
46 events: bool,
47
48 #[arg(long)]
50 all: bool,
51 },
52
53 List {
55 #[arg(long)]
57 start: u32,
58 #[arg(long)]
60 end: u32,
61 #[arg(long)]
63 step: Option<u32>,
64 },
65}
66
67pub async fn handle_block_command(
69 command: BlockCommands,
70 node_url: &str,
71) -> crate::error::Result<()> {
72 match command {
73 BlockCommands::Analyze {
74 number,
75 hash,
76 latest,
77 storage,
78 extrinsics,
79 extrinsics_details,
80 events,
81 all,
82 } =>
83 handle_block_analyze_command(
84 number,
85 hash,
86 latest,
87 storage,
88 extrinsics,
89 extrinsics_details,
90 events,
91 all,
92 node_url,
93 )
94 .await,
95 BlockCommands::List { start, end, step } =>
96 handle_block_list_command(start, end, step, node_url).await,
97 }
98}
99
100async fn handle_block_analyze_command(
102 number: Option<u32>,
103 hash: Option<String>,
104 latest: bool,
105 storage: bool,
106 extrinsics: bool,
107 extrinsics_details: bool,
108 events: bool,
109 all: bool,
110 node_url: &str,
111) -> crate::error::Result<()> {
112 log_print!("🔍 Block Analysis");
113
114 let quantus_client = QuantusClient::new(node_url).await?;
115
116 let (block_number, block_hash) = if let Some(num) = number {
118 let hash = storage::resolve_block_hash(&quantus_client, &num.to_string()).await?;
120 (num, hash)
121 } else if let Some(h) = hash {
122 let parsed_hash = storage::resolve_block_hash(&quantus_client, &h).await?;
124 let storage_at = quantus_client.client().storage().at(parsed_hash);
126 let number_addr = crate::chain::quantus_subxt::api::storage().system().number();
127 let block_num = storage_at.fetch_or_default(&number_addr).await.map_err(|e| {
128 QuantusError::NetworkError(format!("Failed to get block number: {e:?}"))
129 })?;
130 (block_num, parsed_hash)
131 } else if latest {
132 let hash = quantus_client.get_latest_block().await?;
134 let storage_at = quantus_client.client().storage().at(hash);
135 let number_addr = crate::chain::quantus_subxt::api::storage().system().number();
136 let block_num = storage_at.fetch_or_default(&number_addr).await.map_err(|e| {
137 QuantusError::NetworkError(format!("Failed to get latest block number: {e:?}"))
138 })?;
139 (block_num, hash)
140 } else {
141 return Err(QuantusError::Generic("Must specify --number, --hash, or --latest".to_string()));
142 };
143
144 log_print!("📦 Block #{} - {:#x}", block_number, block_hash);
145 log_print!("");
146
147 use jsonrpsee::core::client::ClientT;
149 let block_data: serde_json::Value = quantus_client
150 .rpc_client()
151 .request("chain_getBlock", [format!("{block_hash:#x}")])
152 .await
153 .map_err(|e| QuantusError::NetworkError(format!("Failed to get block data: {e:?}")))?;
154
155 show_block_header(&block_data)?;
157
158 if storage || all {
160 show_storage_statistics(&quantus_client, block_hash).await?;
161 }
162
163 if events || all {
165 show_block_events(block_number, node_url).await?;
166 }
167
168 if extrinsics || all {
170 show_extrinsic_details(&quantus_client, block_hash, &block_data).await?;
171 }
172
173 if extrinsics_details {
175 show_all_extrinsic_details(&quantus_client, block_hash, &block_data).await?;
176 }
177
178 log_success!("✅ Block analysis complete!");
179 log_print!("💡 Use --all to see all information, or combine --storage --events --extrinsics --extrinsics-details");
180
181 Ok(())
182}
183
184fn show_block_header(block_data: &serde_json::Value) -> crate::error::Result<()> {
186 if let Some(block) = block_data.get("block") {
187 if let Some(header) = block.get("header") {
188 log_print!("📋 Block Header:");
189 if let Some(parent_hash) = header.get("parentHash") {
190 log_print!(
191 " • Parent Hash: {}",
192 parent_hash.as_str().unwrap_or("unknown").bright_blue()
193 );
194 }
195 if let Some(state_root) = header.get("stateRoot") {
196 log_print!(
197 " • State Root: {}",
198 state_root.as_str().unwrap_or("unknown").bright_green()
199 );
200 }
201 if let Some(extrinsics_root) = header.get("extrinsicsRoot") {
202 log_print!(
203 " • Extrinsics Root: {}",
204 extrinsics_root.as_str().unwrap_or("unknown").bright_yellow()
205 );
206 }
207
208 if let Some(timestamp) = header.get("timestamp") {
210 if let Some(timestamp_num) = timestamp.as_u64() {
211 let timestamp_secs = timestamp_num / 1000;
213 let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
214 if let Some(dt) = datetime {
215 log_print!(
216 " • Timestamp: {} ({})",
217 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string().bright_cyan(),
218 timestamp_num.to_string().bright_yellow()
219 );
220 }
221 }
222 }
223 }
224
225 if let Some(extrinsics) = block.get("extrinsics") {
226 if let Some(extrinsics_array) = extrinsics.as_array() {
227 log_print!(
228 " • Extrinsics Count: {}",
229 extrinsics_array.len().to_string().bright_magenta()
230 );
231
232 let block_size = serde_json::to_string(block_data).unwrap_or_default().len();
234 log_print!(" • Approximate Size: {} bytes", block_size.to_string().bright_cyan());
235 }
236 }
237 }
238
239 log_print!("");
240 Ok(())
241}
242
243async fn show_storage_statistics(
245 quantus_client: &QuantusClient,
246 block_hash: subxt::utils::H256,
247) -> crate::error::Result<()> {
248 log_print!("💾 Storage Statistics:");
249
250 let account_count =
252 storage::count_storage_entries(quantus_client, "System", "Account", block_hash).await?;
253 log_print!(" • Accounts: {}", account_count.to_string().bright_green());
254
255 let blockhash_count =
257 storage::count_storage_entries(quantus_client, "System", "BlockHash", block_hash).await?;
258 log_print!(" • Block Hashes: {}", blockhash_count.to_string().bright_blue());
259
260 let storage_at = quantus_client.client().storage().at(block_hash);
262 let event_count_addr = crate::chain::quantus_subxt::api::storage().system().event_count();
263 let event_count = storage_at
264 .fetch_or_default(&event_count_addr)
265 .await
266 .map_err(|e| {
267 log_error!("Failed to get event count: {:?}", e);
268 e
269 })
270 .unwrap_or_default();
271 log_print!(" • Events: {}", event_count.to_string().bright_yellow());
272
273 let timestamp_addr = crate::chain::quantus_subxt::api::storage().timestamp().now();
275 match storage_at.fetch(×tamp_addr).await {
276 Ok(Some(timestamp)) => {
277 let timestamp_secs = timestamp / 1000;
279 let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
280 if let Some(dt) = datetime {
281 log_print!(
282 " • Block Time: {} ({})",
283 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string().bright_green(),
284 timestamp.to_string().bright_yellow()
285 );
286 }
287 },
288 Ok(None) => {
289 log_print!(" • Block Time: {}", "no timestamp".bright_yellow());
290 },
291 Err(_) => {
292 log_print!(" • Block Time: {}", "unknown".bright_yellow());
293 },
294 }
295
296 log_print!("");
297 Ok(())
298}
299
300async fn show_block_events(block_number: u32, node_url: &str) -> crate::error::Result<()> {
302 log_print!("📋 Events:");
303
304 match crate::cli::events::handle_events_command(
306 Some(block_number),
307 None,
308 false,
309 None,
310 false,
311 true,
312 node_url,
313 )
314 .await
315 {
316 Ok(_) => {
317 },
319 Err(e) => log_error!("Failed to get events: {}", e),
320 }
321
322 log_print!("");
323 Ok(())
324}
325
326async fn show_extrinsic_details(
328 quantus_client: &QuantusClient,
329 block_hash: subxt::utils::H256,
330 block_data: &serde_json::Value,
331) -> crate::error::Result<()> {
332 if let Some(block) = block_data.get("block") {
333 if let Some(extrinsics) = block.get("extrinsics") {
334 if let Some(extrinsics_array) = extrinsics.as_array() {
335 log_print!("🔧 Extrinsics Details:");
336 log_print!(
337 " • Total Count: {}",
338 extrinsics_array.len().to_string().bright_green()
339 );
340
341 let mut total_size_bytes = 0;
343 let mut total_size_chars = 0;
344 for extrinsic in extrinsics_array.iter() {
345 if let Some(ext_str) = extrinsic.as_str() {
346 total_size_chars += ext_str.len();
347 if let Some(hex_part) = ext_str.strip_prefix("0x") {
349 if hex_part.len() % 2 == 0 {
351 total_size_bytes += hex_part.len() / 2;
352 } else {
353 total_size_bytes += hex_part.len().div_ceil(2);
354 }
355 } else {
356 total_size_bytes += ext_str.len();
358 }
359 }
360 }
361 log_print!(
362 " • Total Size: {:.1} KB ({} chars)",
363 total_size_bytes as f64 / 1024.0,
364 total_size_chars.to_string().bright_cyan()
365 );
366
367 let events =
369 quantus_client.client().blocks().at(block_hash).await?.events().await?;
370 let mut events_by_ex_idx: std::collections::BTreeMap<usize, Vec<String>> =
371 std::collections::BTreeMap::new();
372 for ev in events.iter().flatten() {
373 if let subxt::events::Phase::ApplyExtrinsic(ex_idx) = ev.phase() {
374 let msg = format_event_details(&ev);
375 events_by_ex_idx.entry(ex_idx as usize).or_default().push(msg);
376 }
377 }
378
379 let block = quantus_client.client().blocks().at(block_hash).await?;
381 let extrinsics = block.extrinsics().await?;
382
383 let parent_hash = {
385 use jsonrpsee::core::client::ClientT;
386 let header: serde_json::Value = quantus_client
387 .rpc_client()
388 .request("chain_getHeader", [format!("{block_hash:#x}")])
389 .await
390 .map_err(|e| {
391 crate::error::QuantusError::NetworkError(format!(
392 "Failed to get header: {e:?}"
393 ))
394 })?;
395 let parent_hash_str = header["parentHash"].as_str().ok_or_else(|| {
396 crate::error::QuantusError::NetworkError(
397 "Missing parentHash in header".to_string(),
398 )
399 })?;
400 subxt::utils::H256::from_str(parent_hash_str).map_err(|e| {
401 crate::error::QuantusError::NetworkError(format!(
402 "Invalid parent hash: {e:?}"
403 ))
404 })?
405 };
406
407 let mut signer_nonce_tracker: std::collections::HashMap<String, u32> =
409 std::collections::HashMap::new();
410
411 for (index, extrinsic) in extrinsics_array.iter().take(3).enumerate() {
413 let ext_str = extrinsic.as_str().unwrap_or("unknown");
414 let (ext_size_bytes, preview, hash_hex) = summarize_extrinsic(ext_str);
415 log_print!(
416 " {}. Hash: {} | Size: {} bytes | Data: {}...",
417 (index + 1).to_string().bright_yellow(),
418 hash_hex.bright_blue(),
419 ext_size_bytes.to_string().bright_cyan(),
420 preview.bright_magenta()
421 );
422
423 if let Some(ext_details) = extrinsics.iter().nth(index) {
425 if ext_details.is_signed() {
426 let mut signer_from_events: Option<String> = None;
428 let mut tip_from_events: Option<u128> = None;
429
430 if let Some(event_list) = events_by_ex_idx.get(&index) {
431 for event_line in event_list {
432 if event_line.contains("TransactionFeePaid") {
433 if let Some(who_start) = event_line.find("who: ") {
435 if let Some(who_end) =
436 event_line[who_start + 5..].find(',')
437 {
438 let signer = &event_line
439 [who_start + 5..who_start + 5 + who_end];
440 signer_from_events = Some(signer.to_string());
441 }
442 }
443 if let Some(tip_start) = event_line.find("tip: ") {
445 if let Some(tip_end) =
446 event_line[tip_start + 5..].find(' ')
447 {
448 let tip_str = &event_line
449 [tip_start + 5..tip_start + 5 + tip_end];
450 if let Ok(tip_val) = tip_str.parse::<u128>() {
451 tip_from_events = Some(tip_val);
452 }
453 }
454 }
455 }
456 }
457 }
458
459 if let Some(ref signer) = signer_from_events {
460 log_print!(" • Signer: {}", signer);
461
462 if !signer_nonce_tracker.contains_key(signer) {
464 let nonce = get_account_nonce_at_block(
465 quantus_client,
466 signer,
467 parent_hash,
468 )
469 .await
470 .unwrap_or(0);
471 signer_nonce_tracker.insert(signer.clone(), nonce);
472 }
473
474 let current_nonce = *signer_nonce_tracker.get(signer).unwrap();
477 log_print!(" • Nonce: {}", current_nonce);
478
479 signer_nonce_tracker.insert(signer.clone(), current_nonce + 1);
481 }
482
483 if let Some(tip) = tip_from_events {
484 let tip_hei = tip as f64 / 1_000_000_000_000.0;
486 log_print!(" • Tip: {:.6} HEI", tip_hei);
487 }
488 } else {
489 log_print!(" • Unsigned extrinsic");
490 }
491 }
492
493 if let Some(list) = events_by_ex_idx.get(&index) {
495 for line in list {
496 log_print!(" ↪ {}", line);
497 }
498 }
499 }
500
501 if extrinsics_array.len() > 3 {
502 log_print!(
503 " ... and {} more extrinsics",
504 (extrinsics_array.len() - 3).to_string().bright_blue()
505 );
506 }
507 }
508 }
509 }
510 log_print!("");
511 Ok(())
512}
513
514async fn show_all_extrinsic_details(
516 quantus_client: &QuantusClient,
517 block_hash: subxt::utils::H256,
518 block_data: &serde_json::Value,
519) -> crate::error::Result<()> {
520 if let Some(block) = block_data.get("block") {
521 if let Some(extrinsics) = block.get("extrinsics") {
522 if let Some(extrinsics_array) = extrinsics.as_array() {
523 log_print!("🔧 ALL Extrinsics Details:");
524 log_print!(
525 " • Total Count: {}",
526 extrinsics_array.len().to_string().bright_green()
527 );
528
529 let mut total_size_bytes = 0;
531 let mut total_size_chars = 0;
532 for extrinsic in extrinsics_array.iter() {
533 if let Some(ext_str) = extrinsic.as_str() {
534 total_size_chars += ext_str.len();
535 if let Some(hex_part) = ext_str.strip_prefix("0x") {
537 if hex_part.len() % 2 == 0 {
539 total_size_bytes += hex_part.len() / 2;
540 } else {
541 total_size_bytes += hex_part.len().div_ceil(2);
542 }
543 } else {
544 total_size_bytes += ext_str.len();
546 }
547 }
548 }
549 log_print!(
550 " • Total Size: {:.1} KB ({} chars)",
551 total_size_bytes as f64 / 1024.0,
552 total_size_chars.to_string().bright_cyan()
553 );
554
555 let events =
557 quantus_client.client().blocks().at(block_hash).await?.events().await?;
558 let mut events_by_ex_idx: std::collections::BTreeMap<usize, Vec<String>> =
559 std::collections::BTreeMap::new();
560 for ev in events.iter().flatten() {
561 if let subxt::events::Phase::ApplyExtrinsic(ex_idx) = ev.phase() {
562 let msg = format_event_details(&ev);
563 events_by_ex_idx.entry(ex_idx as usize).or_default().push(msg);
564 }
565 }
566
567 let block = quantus_client.client().blocks().at(block_hash).await?;
569 let extrinsics = block.extrinsics().await?;
570
571 let parent_hash = {
573 use jsonrpsee::core::client::ClientT;
574 let header: serde_json::Value = quantus_client
575 .rpc_client()
576 .request("chain_getHeader", [format!("{block_hash:#x}")])
577 .await
578 .map_err(|e| {
579 crate::error::QuantusError::NetworkError(format!(
580 "Failed to get header: {e:?}"
581 ))
582 })?;
583 let parent_hash_str = header["parentHash"].as_str().ok_or_else(|| {
584 crate::error::QuantusError::NetworkError(
585 "Missing parentHash in header".to_string(),
586 )
587 })?;
588 subxt::utils::H256::from_str(parent_hash_str).map_err(|e| {
589 crate::error::QuantusError::NetworkError(format!(
590 "Invalid parent hash: {e:?}"
591 ))
592 })?
593 };
594
595 let mut signer_nonce_tracker: std::collections::HashMap<String, u32> =
597 std::collections::HashMap::new();
598
599 for (index, extrinsic) in extrinsics_array.iter().enumerate() {
601 let ext_str = extrinsic.as_str().unwrap_or("unknown");
602 let (ext_size_bytes, preview, hash_hex) = summarize_extrinsic(ext_str);
603 log_print!(
604 " {}. Hash: {} | Size: {} bytes | Data: {}...",
605 (index + 1).to_string().bright_yellow(),
606 hash_hex.bright_blue(),
607 ext_size_bytes.to_string().bright_cyan(),
608 preview.bright_magenta()
609 );
610
611 if let Some(ext_details) = extrinsics.iter().nth(index) {
613 if ext_details.is_signed() {
614 let mut signer_from_events: Option<String> = None;
616 let mut tip_from_events: Option<u128> = None;
617
618 if let Some(event_list) = events_by_ex_idx.get(&index) {
619 for event_line in event_list {
620 if event_line.contains("TransactionFeePaid") {
621 if let Some(who_start) = event_line.find("who: ") {
623 if let Some(who_end) =
624 event_line[who_start + 5..].find(',')
625 {
626 let signer = &event_line
627 [who_start + 5..who_start + 5 + who_end];
628 signer_from_events = Some(signer.to_string());
629 }
630 }
631 if let Some(tip_start) = event_line.find("tip: ") {
633 if let Some(tip_end) =
634 event_line[tip_start + 5..].find(' ')
635 {
636 let tip_str = &event_line
637 [tip_start + 5..tip_start + 5 + tip_end];
638 if let Ok(tip_val) = tip_str.parse::<u128>() {
639 tip_from_events = Some(tip_val);
640 }
641 }
642 }
643 }
644 }
645 }
646
647 if let Some(ref signer) = signer_from_events {
648 log_print!(" • Signer: {}", signer);
649
650 if !signer_nonce_tracker.contains_key(signer) {
652 let nonce = get_account_nonce_at_block(
653 quantus_client,
654 signer,
655 parent_hash,
656 )
657 .await
658 .unwrap_or(0);
659 signer_nonce_tracker.insert(signer.clone(), nonce);
660 }
661
662 let current_nonce = *signer_nonce_tracker.get(signer).unwrap();
665 log_print!(" • Nonce: {}", current_nonce);
666
667 signer_nonce_tracker.insert(signer.clone(), current_nonce + 1);
669 }
670
671 if let Some(tip) = tip_from_events {
672 let tip_hei = tip as f64 / 1_000_000_000_000.0;
674 log_print!(" • Tip: {:.6} HEI", tip_hei);
675 }
676 } else {
677 log_print!(" • Unsigned extrinsic");
678 }
679 }
680
681 if let Some(list) = events_by_ex_idx.get(&index) {
683 for line in list {
684 log_print!(" ↪ {}", line);
685 }
686 }
687 }
688 }
689 }
690 }
691 log_print!("");
692 Ok(())
693}
694
695fn summarize_extrinsic(ext_hex: &str) -> (usize, String, String) {
697 let ext_str = ext_hex;
698 let (bytes, size_bytes) = if let Some(hex_part) = ext_str.strip_prefix("0x") {
699 if let Ok(decoded) = hex::decode(hex_part) {
700 let size = decoded.len();
701 (decoded, size)
702 } else {
703 (ext_str.as_bytes().to_vec(), ext_str.len())
704 }
705 } else {
706 (ext_str.as_bytes().to_vec(), ext_str.len())
707 };
708
709 let h = <PoseidonHasher as sp_runtime::traits::Hash>::hash(&bytes);
711 let hash_hex = format!("{h:#x}");
712
713 let preview = if ext_str.len() > 20 { ext_str[..20].to_string() } else { ext_str.to_string() };
714 (size_bytes, preview, hash_hex)
715}
716
717fn format_event_details<T: subxt::Config>(event: &EventDetails<T>) -> String {
719 if let Ok(typed) = event.as_root_event::<crate::chain::quantus_subxt::api::Event>() {
720 let formatted = format_event_with_ss58_addresses(&typed);
721 return format!("📝 {}.{} {}", event.pallet_name(), event.variant_name(), formatted);
722 }
723 format!("📝 {}.{}", event.pallet_name(), event.variant_name())
724}
725
726fn format_event_with_ss58_addresses(event: &crate::chain::quantus_subxt::api::Event) -> String {
727 let debug_str = format!("{event:?}");
728 let mut result = debug_str.clone();
729 let mut attempts = 0;
730 while let Some(account_id) = extract_account_id_from_debug(&result) {
731 let ss58_address = format_account_id(&account_id);
732 let account_debug = format!("{account_id:?}");
733 result = result.replace(&account_debug, &ss58_address);
734 attempts += 1;
735 if attempts > 10 {
736 break;
737 }
738 }
739 result
740}
741
742fn extract_account_id_from_debug(debug_str: &str) -> Option<subxt::utils::AccountId32> {
743 if let Some(start) = debug_str.find("AccountId32([") {
744 if let Some(end) = debug_str[start..].find("])") {
746 let bytes_str = &debug_str[start + 13..start + end];
747 let bytes: Vec<u8> = bytes_str
748 .split(',')
749 .map(|s| s.trim().parse::<u8>().ok())
750 .collect::<Option<Vec<u8>>>()?;
751 if bytes.len() == 32 {
752 let mut account_bytes = [0u8; 32];
753 account_bytes.copy_from_slice(&bytes);
754 return Some(subxt::utils::AccountId32::from(account_bytes));
755 }
756 }
757 }
758 None
759}
760
761fn format_account_id(account_id: &subxt::utils::AccountId32) -> String {
762 account_id.to_quantus_ss58()
763}
764
765async fn get_account_nonce_at_block(
767 quantus_client: &QuantusClient,
768 account_address: &str,
769 block_hash: subxt::utils::H256,
770) -> crate::error::Result<u32> {
771 let (account_id_sp, _) =
773 sp_core::crypto::AccountId32::from_ss58check_with_version(account_address)
774 .map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
775
776 let account_bytes: [u8; 32] = *account_id_sp.as_ref();
778 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
779
780 use crate::chain::quantus_subxt::api;
782 let storage_addr = api::storage().system().account(account_id);
783 let storage_at = quantus_client.client().storage().at(block_hash);
784
785 let account_info = storage_at
786 .fetch_or_default(&storage_addr)
787 .await
788 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch account info: {e:?}")))?;
789
790 Ok(account_info.nonce)
791}
792
793pub async fn handle_block_list_command(
795 start: u32,
796 end: u32,
797 step: Option<u32>,
798 node_url: &str,
799) -> crate::error::Result<()> {
800 log_print!(
801 "📦 Listing blocks from {} to {}",
802 start.to_string().bright_green(),
803 end.to_string().bright_green()
804 );
805
806 let step = step.unwrap_or(1);
807 if step > 1 {
808 log_print!("📏 Step: {}", step.to_string().bright_cyan());
809 }
810
811 let quantus_client = QuantusClient::new(node_url).await?;
812 list_blocks_in_range(&quantus_client, start, end, step).await
813}
814
815async fn list_blocks_in_range(
817 quantus_client: &QuantusClient,
818 start: u32,
819 end: u32,
820 step: u32,
821) -> crate::error::Result<()> {
822 use jsonrpsee::core::client::ClientT;
823
824 log_print!("🔍 Fetching block information...");
825
826 let mut block_count = 0;
827 let mut total_extrinsics = 0;
828 let mut total_events = 0;
829 let mut total_size = 0;
830 let mut tps_values = Vec::new();
831 let mut previous_timestamp: Option<u64> = None;
832
833 log_print!("📊 Processing {} blocks...", ((end - start) / step + 1).to_string().bright_cyan());
835
836 log_print!("");
838 log_print!(
839 "{:<20} {:<20} {:<12} {:<10} {:<8} {:<8}",
840 "Block".bright_green().bold(),
841 "Time".bright_cyan().bold(),
842 "Extrinsics".bright_blue().bold(),
843 "Events".bright_yellow().bold(),
844 "Size".bright_magenta().bold(),
845 "TPS".bright_red().bold()
846 );
847 log_print!(
848 "{:<20} {:<20} {:<12} {:<10} {:<8} {:<8}",
849 "────────────────────".bright_green(),
850 "────────────────────".bright_cyan(),
851 "────────────".bright_blue(),
852 "──────────".bright_yellow(),
853 "──────".bright_magenta(),
854 "──────".bright_red()
855 );
856
857 for block_num in (start..=end).step_by(step as usize) {
858 let block_hash: subxt::utils::H256 = quantus_client
860 .rpc_client()
861 .request::<subxt::utils::H256, [u32; 1]>("chain_getBlockHash", [block_num])
862 .await
863 .map_err(|e| {
864 QuantusError::NetworkError(format!(
865 "Failed to get block hash for block {block_num}: {e:?}"
866 ))
867 })?;
868
869 let block_data: serde_json::Value = quantus_client
871 .rpc_client()
872 .request::<serde_json::Value, [String; 1]>(
873 "chain_getBlock",
874 [format!("{block_hash:#x}")],
875 )
876 .await
877 .map_err(|e| {
878 QuantusError::NetworkError(format!(
879 "Failed to get block data for block {block_num}: {e:?}"
880 ))
881 })?;
882
883 let extrinsics_count = if let Some(block) = block_data.get("block") {
885 if let Some(extrinsics) = block.get("extrinsics") {
886 if let Some(extrinsics_array) = extrinsics.as_array() {
887 extrinsics_array.len()
888 } else {
889 0
890 }
891 } else {
892 0
893 }
894 } else {
895 0
896 };
897
898 let storage_at = quantus_client.client().storage().at(block_hash);
900 let timestamp_addr = crate::chain::quantus_subxt::api::storage().timestamp().now();
901 let timestamp = storage_at.fetch(×tamp_addr).await.ok().flatten();
902
903 let event_count_addr = crate::chain::quantus_subxt::api::storage().system().event_count();
905 let event_count = storage_at.fetch(&event_count_addr).await.ok().flatten().unwrap_or(0);
906
907 let block_size_bytes = if let Some(block) = block_data.get("block") {
909 if let Some(extrinsics) = block.get("extrinsics") {
910 if let Some(extrinsics_array) = extrinsics.as_array() {
911 let mut total_bytes = 0;
913 for extrinsic in extrinsics_array.iter() {
914 if let Some(ext_str) = extrinsic.as_str() {
915 if let Some(hex_part) = ext_str.strip_prefix("0x") {
917 if hex_part.len() % 2 == 0 {
918 total_bytes += hex_part.len() / 2;
919 } else {
920 total_bytes += hex_part.len().div_ceil(2);
921 }
922 } else {
923 total_bytes += ext_str.len();
924 }
925 }
926 }
927 total_bytes + 1024 } else {
930 0
931 }
932 } else {
933 0
934 }
935 } else {
936 0
937 };
938 let block_size_kb = block_size_bytes as f64 / 1024.0;
939
940 block_count += 1;
942 total_extrinsics += extrinsics_count;
943 total_events += event_count;
944 total_size += block_size_bytes;
945
946 let tps_str = match (timestamp, previous_timestamp) {
948 (Some(ts), Some(prev_ts)) => {
949 let time_diff_ms = ts.saturating_sub(prev_ts);
950 if time_diff_ms > 0 {
951 let time_diff_secs = time_diff_ms as f64 / 1000.0;
952 let tps = extrinsics_count as f64 / time_diff_secs;
953 tps_values.push(tps);
954 format!("{tps:.1}")
955 } else {
956 "N/A".to_string()
957 }
958 },
959 _ => "N/A".to_string(),
960 };
961
962 let time_str = if let Some(ts) = timestamp {
964 let timestamp_secs = ts / 1000;
965 let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
966 if let Some(dt) = datetime {
967 dt.format("%Y-%m-%d %H:%M:%S").to_string()
968 } else {
969 "unknown".to_string()
970 }
971 } else {
972 "unknown".to_string()
973 };
974
975 log_print!(
976 "📦 {:<18} {:<20} {:<12} {:<10} {:<8} {:<8}",
977 format!("#{block_num}").bright_green(),
978 time_str.bright_cyan(),
979 extrinsics_count.to_string().bright_blue(),
980 event_count.to_string().bright_yellow(),
981 format!("{block_size_kb:.1}K").bright_magenta(),
982 tps_str.bright_red()
983 );
984
985 previous_timestamp = timestamp.or(previous_timestamp);
987 }
988
989 log_print!("");
991 log_print!("📊 Summary:");
992 log_print!(" • Blocks processed: {}", block_count.to_string().bright_green());
993 log_print!(" • Total extrinsics: {}", total_extrinsics.to_string().bright_blue());
994 log_print!(" • Total events: {}", total_events.to_string().bright_yellow());
995 log_print!(
996 " • Total size: {} KB",
997 format!("{:.1}", total_size as f64 / 1024.0).bright_magenta()
998 );
999 log_print!(
1000 " • Average extrinsics per block: {}",
1001 format!("{:.1}", total_extrinsics as f64 / block_count as f64).bright_cyan()
1002 );
1003 log_print!(
1004 " • Average events per block: {}",
1005 format!("{:.1}", total_events as f64 / block_count as f64).bright_cyan()
1006 );
1007
1008 if !tps_values.is_empty() {
1010 let max_tps = tps_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1011 let min_tps = tps_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1012 let avg_tps = tps_values.iter().sum::<f64>() / tps_values.len() as f64;
1013
1014 log_print!("");
1015 log_print!("🚀 TPS Statistics:");
1016 log_print!(" • MAX TPS: {}", format!("{max_tps:.1}").bright_green().bold());
1017 log_print!(" • MIN TPS: {}", format!("{min_tps:.1}").bright_yellow().bold());
1018 log_print!(" • AVG TPS: {}", format!("{avg_tps:.1}").bright_cyan().bold());
1019 log_print!(" • TPS samples: {}", tps_values.len().to_string().bright_magenta());
1020 } else {
1021 log_print!("");
1022 log_print!(
1023 "🚀 TPS Statistics: No TPS data available (need at least 2 blocks with timestamps)"
1024 );
1025 }
1026
1027 Ok(())
1028}