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