pcli/command/query/
chain.rs

1use anyhow::{anyhow, Context, Result};
2use comfy_table::{presets, Table};
3use futures::TryStreamExt;
4use penumbra_sdk_app::params::AppParameters;
5use penumbra_sdk_proto::{
6    core::{
7        app::v1::{
8            query_service_client::QueryServiceClient as AppQueryServiceClient, AppParametersRequest,
9        },
10        component::{
11            sct::v1::{
12                query_service_client::QueryServiceClient as SctQueryServiceClient,
13                EpochByHeightRequest,
14            },
15            stake::v1::{
16                query_service_client::QueryServiceClient as StakeQueryServiceClient,
17                ValidatorInfoRequest,
18            },
19        },
20    },
21    util::tendermint_proxy::v1::{
22        tendermint_proxy_service_client::TendermintProxyServiceClient, AbciQueryRequest,
23        GetStatusRequest,
24    },
25    Message,
26};
27use penumbra_sdk_stake::validator;
28
29use crate::App;
30
31#[derive(Debug, clap::Subcommand)]
32pub enum ChainCmd {
33    /// Display chain parameters.
34    Params,
35    /// Display information about the current chain state.
36    Info {
37        /// If true, will also display chain parameters.
38        #[clap(short, long)]
39        verbose: bool,
40    },
41    DetectDesync,
42}
43
44pub struct Stats {
45    current_block_height: u64,
46    current_epoch: u64,
47    total_validators: u64,
48    active_validators: u64,
49    inactive_validators: u64,
50    jailed_validators: u64,
51    tombstoned_validators: u64,
52    disabled_validators: u64,
53}
54
55impl ChainCmd {
56    pub async fn print_app_params(&self, app: &mut App) -> Result<()> {
57        let mut client = AppQueryServiceClient::new(app.pd_channel().await?);
58        let params: AppParameters = client
59            .app_parameters(tonic::Request::new(AppParametersRequest {}))
60            .await?
61            .into_inner()
62            .app_parameters
63            .ok_or_else(|| anyhow::anyhow!("empty AppParametersResponse message"))?
64            .try_into()?;
65
66        // Use serde-json to pretty print the params
67        let params_json = serde_json::to_string_pretty(&params)?;
68        println!("{}", params_json);
69
70        Ok(())
71    }
72
73    pub async fn get_stats(&self, app: &mut App) -> Result<Stats> {
74        let channel = app.pd_channel().await?;
75
76        let mut client = TendermintProxyServiceClient::new(channel.clone());
77        let current_block_height = client
78            .get_status(GetStatusRequest::default())
79            .await?
80            .into_inner()
81            .sync_info
82            .ok_or_else(|| anyhow!("missing sync_info"))?
83            .latest_block_height;
84
85        let mut client = SctQueryServiceClient::new(channel.clone());
86        let current_epoch: u64 = client
87            .epoch_by_height(tonic::Request::new(EpochByHeightRequest {
88                height: current_block_height.clone(),
89            }))
90            .await?
91            .into_inner()
92            .epoch
93            .context("failed to find EpochByHeight message")?
94            .index;
95
96        // Fetch validators.
97        let mut client = StakeQueryServiceClient::new(channel.clone());
98        let validators = client
99            .validator_info(ValidatorInfoRequest {
100                show_inactive: true,
101            })
102            .await?
103            .into_inner()
104            .try_collect::<Vec<_>>()
105            .await?
106            .into_iter()
107            .map(TryInto::try_into)
108            .collect::<Result<Vec<validator::Info>, _>>()?;
109
110        let total_validators = validators.len() as u64;
111        let active_validators = validators
112            .iter()
113            .filter(|v| v.status.state == validator::State::Active)
114            .count() as u64;
115        let inactive_validators = validators
116            .iter()
117            .filter(|v| v.status.state == validator::State::Inactive)
118            .count() as u64;
119        let jailed_validators = validators
120            .iter()
121            .filter(|v| v.status.state == validator::State::Jailed)
122            .count() as u64;
123        let tombstoned_validators = validators
124            .iter()
125            .filter(|v| v.status.state == validator::State::Tombstoned)
126            .count() as u64;
127        let disabled_validators = validators
128            .iter()
129            .filter(|v| v.status.state == validator::State::Disabled)
130            .count() as u64;
131
132        Ok(Stats {
133            current_block_height,
134            current_epoch,
135            total_validators,
136            active_validators,
137            inactive_validators,
138            jailed_validators,
139            tombstoned_validators,
140            disabled_validators,
141        })
142    }
143
144    pub async fn exec(&self, app: &mut App) -> Result<()> {
145        match self {
146            ChainCmd::DetectDesync => {
147                let mut client = TendermintProxyServiceClient::new(app.pd_channel().await?);
148                let status = client
149                    .get_status(GetStatusRequest::default())
150                    .await?
151                    .into_inner()
152                    .sync_info
153                    .ok_or_else(|| anyhow!("missing sync_info"))?;
154
155                let mut app_client = AppQueryServiceClient::new(app.pd_channel().await?);
156                let params = app_client
157                    .app_parameters(AppParametersRequest {})
158                    .await?
159                    .into_inner()
160                    .app_parameters
161                    .unwrap();
162                let chain_id = params.chain_id;
163
164                let height = status.latest_block_height as i64;
165
166                let response = client
167                    .abci_query(AbciQueryRequest {
168                        data: b"sct/block_manager/block_height".to_vec(),
169                        path: "state/key".to_string(),
170                        height,
171                        prove: false,
172                    })
173                    .await?
174                    .into_inner();
175
176                let raw_height_response = response.value;
177                let height_response: u64 = Message::decode(&raw_height_response[..])
178                    .map_err(|e| anyhow!("failed to decode height response: {}", e))?;
179
180                println!("chain_id: {}", chain_id);
181                println!("queried height: {}", height);
182                println!("height response: {}", height_response);
183                if height == height_response as i64 {
184                    println!(
185                        "Unaffected. No action item. The full node internal state version tracks the block height."
186                    );
187                } else {
188                    println!("Affected. The full node chain state is corrupted, please resync your node.");
189                }
190            }
191            ChainCmd::Params => {
192                self.print_app_params(app).await?;
193            }
194            // TODO: we could implement this as an RPC call using the metrics
195            // subsystems once #829 is complete
196            // OR (hdevalence): fold it into pcli q
197            ChainCmd::Info { verbose } => {
198                if *verbose {
199                    self.print_app_params(app).await?;
200                }
201
202                let stats = self.get_stats(app).await?;
203
204                println!("Chain Info:");
205                let mut table = Table::new();
206                table.load_preset(presets::NOTHING);
207                table
208                    .set_header(vec!["", ""])
209                    .add_row(vec![
210                        "Current Block Height",
211                        &format!("{}", stats.current_block_height),
212                    ])
213                    .add_row(vec!["Current Epoch", &format!("{}", stats.current_epoch)])
214                    .add_row(vec![
215                        "Total Validators",
216                        &format!("{}", stats.total_validators),
217                    ])
218                    .add_row(vec![
219                        "Active Validators",
220                        &format!("{}", stats.active_validators),
221                    ])
222                    .add_row(vec![
223                        "Inactive Validators",
224                        &format!("{}", stats.inactive_validators),
225                    ])
226                    .add_row(vec![
227                        "Jailed Validators",
228                        &format!("{}", stats.jailed_validators),
229                    ])
230                    .add_row(vec![
231                        "Tombstoned Validators",
232                        &format!("{}", stats.tombstoned_validators),
233                    ])
234                    .add_row(vec![
235                        "Disabled Validators",
236                        &format!("{}", stats.disabled_validators),
237                    ]);
238
239                println!("{table}");
240            }
241        };
242
243        Ok(())
244    }
245}