pcli/command/query/
chain.rs1use 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 Params,
35 Info {
37 #[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 let params_json = serde_json::to_string_pretty(¶ms)?;
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 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 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}