1use anyhow::{anyhow, Context, Result};
2
3pub(crate) mod auction;
4mod chain;
5mod community_pool;
6mod dex;
7mod governance;
8mod ibc_query;
9mod shielded_pool;
10mod tx;
11mod validator;
12
13use auction::AuctionCmd;
14use base64::prelude::*;
15use chain::ChainCmd;
16use cnidarium::proto::v1::non_verifiable_key_value_request::Key as NVKey;
17use colored_json::ToColoredJson;
18use community_pool::CommunityPoolCmd;
19use dex::DexCmd;
20use governance::GovernanceCmd;
21use ibc_query::IbcCmd;
22use shielded_pool::ShieldedPool;
23use tx::Tx;
24pub(super) use validator::ValidatorCmd;
25
26use crate::App;
27
28#[derive(Clone, clap::ValueEnum, Debug)]
29pub enum OutputFormat {
30 Json,
31 Base64,
32}
33
34impl Default for OutputFormat {
35 fn default() -> Self {
36 Self::Json
37 }
38}
39
40#[derive(Debug, clap::Subcommand)]
41pub enum QueryCmd {
42 Key {
44 key: String,
50 #[clap(long, default_value = "jmt")]
56 storage_backend: String,
57 },
58 #[clap(subcommand)]
60 ShieldedPool(ShieldedPool),
61 Tx(Tx),
63 #[clap(subcommand)]
65 Chain(ChainCmd),
66 #[clap(subcommand)]
68 Validator(ValidatorCmd),
69 #[clap(subcommand)]
71 Governance(GovernanceCmd),
72 #[clap(subcommand)]
74 CommunityPool(CommunityPoolCmd),
75 #[clap(subcommand)]
77 Dex(DexCmd),
78 #[clap(subcommand)]
80 Ibc(IbcCmd),
81 Watch {
83 #[clap(long, default_value = "")]
87 key_regex: String,
88 #[clap(long, default_value = "")]
92 nv_key_regex: String,
93 },
94 #[clap(subcommand)]
96 Auction(AuctionCmd),
97}
98
99impl QueryCmd {
100 pub async fn exec(&self, app: &mut App) -> Result<()> {
101 if let QueryCmd::Watch {
102 key_regex,
103 nv_key_regex,
104 } = self
105 {
106 return watch(key_regex.clone(), nv_key_regex.clone(), app).await;
107 }
108
109 if let QueryCmd::Tx(tx) = self {
111 return tx.exec(app).await;
112 }
113
114 if let QueryCmd::Chain(chain) = self {
115 return chain.exec(app).await;
116 }
117
118 if let QueryCmd::Validator(validator) = self {
119 return validator.exec(app).await;
120 }
121
122 if let QueryCmd::Dex(dex) = self {
123 return dex.exec(app).await;
124 }
125
126 if let QueryCmd::Governance(governance) = self {
127 return governance.exec(app).await;
128 }
129
130 if let QueryCmd::CommunityPool(cp) = self {
131 return cp.exec(app).await;
132 }
133
134 if let QueryCmd::Ibc(ibc) = self {
135 return ibc.exec(app).await;
136 }
137
138 if let QueryCmd::Auction(auction) = self {
139 return auction.exec(app).await;
140 }
141
142 if let QueryCmd::ShieldedPool(ShieldedPool::CompactBlock { height }) = self {
144 use penumbra_sdk_proto::core::component::compact_block::v1::{
145 query_service_client::QueryServiceClient as CompactBlockQueryServiceClient,
146 CompactBlockRequest,
147 };
148 let mut client = CompactBlockQueryServiceClient::new(app.pd_channel().await?);
149 let compact_block = client
150 .compact_block(CompactBlockRequest { height: *height })
151 .await?
152 .into_inner()
153 .compact_block
154 .ok_or_else(|| anyhow!("compact block missing from response"))?;
155 let json = serde_json::to_string_pretty(&compact_block)?;
156
157 println!("{}", json.to_colored_json_auto()?);
158 return Ok(());
159 }
160
161 let (key, storage_backend) = match self {
162 QueryCmd::Tx(_)
163 | QueryCmd::Chain(_)
164 | QueryCmd::Validator(_)
165 | QueryCmd::Dex(_)
166 | QueryCmd::Governance(_)
167 | QueryCmd::CommunityPool(_)
168 | QueryCmd::Watch { .. }
169 | QueryCmd::Auction { .. }
170 | QueryCmd::Ibc(_) => {
171 unreachable!("query handled in guard");
172 }
173 QueryCmd::ShieldedPool(p) => (p.key().clone(), "jmt".to_string()),
174 QueryCmd::Key {
175 key,
176 storage_backend,
177 } => (key.clone(), storage_backend.clone()),
178 };
179
180 use cnidarium::proto::v1::query_service_client::QueryServiceClient;
181 let mut client = QueryServiceClient::new(app.pd_channel().await?);
182
183 match storage_backend.as_str() {
185 "nonverifiable" => {
186 let key_bytes = BASE64_STANDARD
187 .decode(&key)
188 .map_err(|e| anyhow::anyhow!(format!("invalid base64: {}", e)))?;
189
190 let req = cnidarium::proto::v1::NonVerifiableKeyValueRequest {
191 key: Some(NVKey { inner: key_bytes }),
192 ..Default::default()
193 };
194
195 tracing::debug!(?req);
196
197 let value = client
198 .non_verifiable_key_value(req)
199 .await?
200 .into_inner()
201 .value
202 .context(format!("key not found! key={}", key))?;
203
204 self.display_value(&value.value)?;
205 }
206 "jmt" | _ => {
208 let req = cnidarium::proto::v1::KeyValueRequest {
209 key: key.clone(),
210 proof: false,
212 ..Default::default()
213 };
214
215 tracing::debug!(?req);
216
217 let value = client
218 .key_value(req)
219 .await?
220 .into_inner()
221 .value
222 .context(format!("key not found! key={}", key))?;
223
224 self.display_value(&value.value)?;
225 }
226 };
227
228 Ok(())
229 }
230
231 pub fn offline(&self) -> bool {
232 match self {
233 QueryCmd::Dex { .. } | QueryCmd::CommunityPool { .. } => false,
234 QueryCmd::Tx { .. }
235 | QueryCmd::Chain { .. }
236 | QueryCmd::Validator { .. }
237 | QueryCmd::ShieldedPool { .. }
238 | QueryCmd::Governance { .. }
239 | QueryCmd::Key { .. }
240 | QueryCmd::Watch { .. }
241 | QueryCmd::Auction { .. }
242 | QueryCmd::Ibc(_) => true,
243 }
244 }
245
246 fn display_value(&self, bytes: &[u8]) -> Result<()> {
247 match self {
248 QueryCmd::Key { .. } => {
249 println!("{}", hex::encode(bytes));
250 }
251 QueryCmd::ShieldedPool(sp) => sp.display_value(bytes)?,
252 QueryCmd::Tx { .. }
253 | QueryCmd::Chain { .. }
254 | QueryCmd::Validator { .. }
255 | QueryCmd::Dex { .. }
256 | QueryCmd::Governance { .. }
257 | QueryCmd::CommunityPool { .. }
258 | QueryCmd::Watch { .. }
259 | QueryCmd::Auction { .. }
260 | QueryCmd::Ibc(_) => {
261 unreachable!("query is special cased")
262 }
263 }
264
265 Ok(())
266 }
267}
268
269async fn watch(key_regex: String, nv_key_regex: String, app: &mut App) -> Result<()> {
272 use cnidarium::proto::v1::{query_service_client::QueryServiceClient, watch_response as wr};
273 let mut client = QueryServiceClient::new(app.pd_channel().await?);
274
275 let req = cnidarium::proto::v1::WatchRequest {
276 key_regex,
277 nv_key_regex,
278 };
279
280 tracing::debug!(?req);
281
282 let mut stream = client.watch(req).await?.into_inner();
283
284 while let Some(rsp) = stream.message().await? {
285 match rsp.entry {
286 Some(wr::Entry::Kv(kv)) => {
287 if kv.deleted {
288 println!("{} KV {} -> DELETED", rsp.version, kv.key);
289 } else {
290 println!(
291 "{} KV {} -> {}",
292 rsp.version,
293 kv.key,
294 simple_base64::encode(&kv.value)
295 );
296 }
297 }
298 Some(wr::Entry::NvKv(nv_kv)) => {
299 let key = simple_base64::encode(&nv_kv.key);
300
301 if nv_kv.deleted {
302 println!("{} NVKV {} -> DELETED", rsp.version, key);
303 } else {
304 println!(
305 "{} NVKV {} -> {}",
306 rsp.version,
307 key,
308 simple_base64::encode(&nv_kv.value)
309 );
310 }
311 }
312 None => {
313 return Err(anyhow!("server returned None event"));
314 }
315 }
316 }
317
318 Ok(())
319}