forest/cli/subcommands/chain_cmd/
list.rs1use std::num::NonZeroUsize;
5
6use anyhow::Context as _;
7use itertools::Itertools;
8use num::{BigInt, Zero as _};
9
10use crate::{
11 rpc::{
12 self, RpcMethodExt as _,
13 chain::{
14 ChainGetBlockMessages, ChainGetParentMessages, ChainGetParentReceipts, ChainGetTipSet,
15 ChainGetTipSetByHeight, ChainHead,
16 },
17 },
18 shim::econ::{BLOCK_GAS_LIMIT, TokenAmount},
19};
20
21#[derive(Debug, clap::Args)]
23pub struct ChainListCommand {
24 #[arg(long)]
26 epoch: Option<u64>,
27 #[arg(long, default_value_t = NonZeroUsize::new(30).unwrap())]
29 count: NonZeroUsize,
30 #[arg(long)]
31 gas_stats: bool,
33}
34
35impl ChainListCommand {
36 pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
37 let count = self.count.into();
38 let mut ts = if let Some(epoch) = self.epoch {
39 ChainGetTipSetByHeight::call(&client, (epoch as _, None.into())).await?
40 } else {
41 ChainHead::call(&client, ()).await?
42 };
43 let mut tipsets = Vec::with_capacity(count);
44 loop {
45 tipsets.push(ts.clone());
46 if ts.epoch() == 0 || tipsets.len() >= count {
47 break;
48 }
49 ts = ChainGetTipSet::call(&client, (ts.parents().into(),)).await?;
50 }
51 tipsets.reverse();
52
53 for (i, ts) in tipsets.iter().enumerate() {
54 if self.gas_stats {
55 let base_fee = &ts.block_headers().first().parent_base_fee;
56 let max_fee = base_fee * BLOCK_GAS_LIMIT;
57 println!(
58 "{height}: {n} blocks (baseFee: {base_fee} -> maxFee: {max_fee} FIL)",
59 height = ts.epoch(),
60 n = ts.len()
61 );
62 for b in ts.block_headers() {
63 let msgs = ChainGetBlockMessages::call(&client, (*b.cid(),)).await?;
64 let len = msgs.bls_msg.len() + msgs.secp_msg.len();
65 let mut limit_sum = 0;
66 let mut premium_sum = TokenAmount::zero();
67 let mut premium_avg = BigInt::zero();
68 for m in &msgs.bls_msg {
69 limit_sum += m.gas_limit;
70 premium_sum += m.gas_premium.clone();
71 }
72 for m in &msgs.secp_msg {
73 limit_sum += m.message().gas_limit;
74 premium_sum += m.message().gas_premium.clone();
75 }
76
77 if len > 0 {
78 premium_avg = premium_sum.atto() / BigInt::from(len);
79 }
80
81 println!(
82 "\t{miner}: \t{len} msgs, gasLimit: {limit_sum} / {BLOCK_GAS_LIMIT} ({ratio:.2}), avgPremium: {premium_avg}",
83 miner = b.miner_address,
84 ratio = (limit_sum as f64) / (BLOCK_GAS_LIMIT as f64) * 100.0
85 );
86 }
87 if let Some(child_ts) = tipsets.get(i + 1) {
88 let msgs = ChainGetParentMessages::call(
89 &client,
90 (*child_ts.block_headers().first().cid(),),
91 )
92 .await?;
93 let limit_sum: u64 = msgs.iter().map(|m| m.message.gas_limit).sum();
94 let gas_used: u64 = {
95 let receipts = ChainGetParentReceipts::call(
96 &client,
97 (*child_ts.block_headers().first().cid(),),
98 )
99 .await?;
100 receipts.iter().map(|r| r.gas_used).sum()
101 };
102 let gas_efficiency = 100. * (gas_used as f64) / (limit_sum as f64);
103 let gas_capacity = 100. * (limit_sum as f64) / (BLOCK_GAS_LIMIT as f64);
104
105 println!(
106 "\ttipset: \t{n} msgs, {gas_used} ({gas_efficiency:.2}%) / {limit_sum} ({gas_capacity:.2}%)",
107 n = msgs.len()
108 );
109 }
110 } else {
111 let epoch = ts.epoch();
112 let time = chrono::DateTime::from_timestamp(ts.min_timestamp() as _, 0)
113 .context("invalid timestamp")?
114 .format("%b %e %X");
115 let tsk = ts
116 .block_headers()
117 .iter()
118 .map(|h| format!("{}: {},", h.cid(), h.miner_address))
119 .join("");
120 println!("{epoch}: ({time}) [ {tsk} ]");
121 }
122 }
123
124 Ok(())
125 }
126}