Skip to content

Commit

Permalink
feat(pcli): support new ibc query logic
Browse files Browse the repository at this point in the history
Previously pcli was only capable of looking up info on IBC clients,
connections, and channels if the user provided a unique identifier for
the resource in question. To facilitate IBC in practice, we want to
enable querying for lists of these values, so the end user (or
developer) can inspect the current options in a legible format.

All but the `channel` query have been updated. Submitting as-is
to ensure it lands in next testnet.

Closes #2937.

Co-authored-by: Ava Howell <[email protected]>
  • Loading branch information
conorsch and Ava Howell committed Oct 6, 2023
1 parent 0cdabf9 commit 47da4a5
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 34 deletions.
112 changes: 81 additions & 31 deletions crates/bin/pcli/src/command/query/ibc_query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use anyhow::{Context, Result};
use colored_json::ToColoredJson;
use ibc_proto::ibc::core::channel::v1::query_client::QueryClient as ChannelQueryClient;
use ibc_proto::ibc::core::channel::v1::{QueryChannelRequest, QueryChannelsRequest};
use ibc_proto::ibc::core::client::v1::query_client::QueryClient as ClientQueryClient;
use ibc_proto::ibc::core::client::v1::{QueryClientStateRequest, QueryClientStatesRequest};
use ibc_proto::ibc::core::connection::v1::query_client::QueryClient as ConnectionQueryClient;
use ibc_proto::ibc::core::connection::v1::{QueryConnectionRequest, QueryConnectionsRequest};
use ibc_types::core::channel::ChannelEnd;
use ibc_types::core::connection::ConnectionEnd;
use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;

use penumbra_proto::core::app::v1alpha1::{
Expand All @@ -11,14 +16,23 @@ use penumbra_proto::DomainType;

use crate::App;

/// Queries the chain for IBC data
/// Queries the chain for IBC data. Results will be printed in JSON.
/// The singular subcommands require identifiers, whereas the plural subcommands
/// return all results.
#[derive(Debug, clap::Subcommand)]
pub enum IbcCmd {
/// Queries for client info
/// Queries for info on a specific IBC client.
/// Requires client identifier string, e.g. "07-tendermint-0".
Client { client_id: String },
/// Queries for connection info
Connection { connection_id: String },
/// Queries for channel info
/// Queries for info on all IBC clients.
Clients {},
/// Queries for info on a specific IBC connection.
/// Requires the numeric identifier for the connection, e.g. "0".
Connection { connection_id: u64 },
/// Queries for info on all IBC connections.
Connections {},
/// Queries for info on a specific IBC channel.
/// Requires the numeric identifier for the channel, e.g. "0".
Channel {
/// The designation of the ICS port used for channel.
/// In the context of IBC, this is usually "transfer".
Expand All @@ -29,50 +43,75 @@ pub enum IbcCmd {
/// during channel creation by a relaying client. Refer to the documentation
/// for the relayer provider to understand which counterparty chain the
/// channel id refers to.
#[clap(long)]
channel_id: u64,
},
/// Queries for info on all IBC channels.
Channels {},
}

impl IbcCmd {
pub async fn exec(&self, app: &mut App) -> Result<()> {
let mut client = StorageQueryServiceClient::new(app.pd_channel().await?);
match self {
IbcCmd::Client { client_id } => {
let key = format!("clients/{client_id}/clientState");
let value = client
.key_value(KeyValueRequest {
key,
..Default::default()
})
.await
.context(format!("Error finding client {client_id}"))?
let mut ibc_client = ClientQueryClient::new(app.pd_channel().await?);
let req = QueryClientStateRequest {
client_id: client_id.to_string(),
};
let client_state = match ibc_client
.client_state(req)
.await?
.into_inner()
.value
.context("Client {client_id} not found")?;

let client_state = TendermintClientState::decode(value.value.as_ref())?;
.client_state
{
Some(c) => TendermintClientState::try_from(c)?,
None => {
anyhow::bail!("Client id not found: {}", client_id);
}
};
let client_state_json = serde_json::to_string_pretty(&client_state)?;
println!("{}", client_state_json.to_colored_json_auto()?);
}
IbcCmd::Connection { connection_id } => {
let key = format!("connections/{connection_id}");
let value = client
.key_value(KeyValueRequest {
key,
..Default::default()
})
.await
.context(format!("error finding {connection_id}"))?
IbcCmd::Clients {} => {
let mut ibc_client = ClientQueryClient::new(app.pd_channel().await?);
let req = QueryClientStatesRequest {
// TODO: support pagination
pagination: None,
};
let client_states: Vec<_> = ibc_client
.client_states(req)
.await?
.into_inner()
.value
.context(format!("Connection {connection_id} not found"))?;
.client_states
.into_iter()
.filter_map(|s| s.client_state)
.map(TendermintClientState::try_from)
.collect::<Result<Vec<_>, _>>()?;

let connection = ConnectionEnd::decode(value.value.as_ref())?;
let clients_json = serde_json::to_string_pretty(&client_states)?;
println!("{}", clients_json.to_colored_json_auto()?);
}
IbcCmd::Connection { connection_id } => {
let mut ibc_client = ConnectionQueryClient::new(app.pd_channel().await?);
let c = format!("connection-{}", connection_id);
let req = QueryConnectionRequest { connection_id: c };
let connection = ibc_client.connection(req).await?.into_inner().connection;
let connection_json = serde_json::to_string_pretty(&connection)?;
println!("{}", connection_json.to_colored_json_auto()?);
}
IbcCmd::Connections {} => {
let mut ibc_client = ConnectionQueryClient::new(app.pd_channel().await?);
let req = QueryConnectionsRequest {
// TODO: support pagination
pagination: None,
};
let connections = ibc_client.connections(req).await?.into_inner().connections;
let connections_json = serde_json::to_string_pretty(&connections)?;
println!("{}", connections_json.to_colored_json_auto()?);
}
IbcCmd::Channel { port, channel_id } => {
// TODO channel lookup should be updated to use the ibc query logic.
// https://docs.rs/ibc-proto/0.36.1/ibc_proto/ibc/core/channel/v1/query_client/struct.QueryClient.html#method.channel
let key = format!("channelEnds/ports/{port}/channels/channel-{channel_id}");
let value = client
.key_value(KeyValueRequest {
Expand All @@ -88,9 +127,20 @@ impl IbcCmd {
.context(format!("Channel {port}:channel-{channel_id} not found"))?;

let channel = ChannelEnd::decode(value.value.as_ref())?;

let channel_json = serde_json::to_string_pretty(&channel)?;
println!("{}", channel_json.to_colored_json_auto()?);
}
IbcCmd::Channels {} => {
let mut ibc_client = ChannelQueryClient::new(app.pd_channel().await?);
let req = QueryChannelsRequest {
// TODO: support pagination
pagination: None,
};
let channels = ibc_client.channels(req).await?.into_inner().channels;
let channels_json = serde_json::to_string_pretty(&channels)?;
println!("{}", channels_json.to_colored_json_auto()?);
}
}

Ok(())
Expand Down
44 changes: 41 additions & 3 deletions crates/core/component/ibc/src/component/rpc/client_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;

use ibc_proto::ibc::core::client::v1::query_server::Query as ClientQuery;
use ibc_proto::ibc::core::client::v1::{
IdentifiedClientState, QueryClientParamsRequest, QueryClientParamsResponse,
Height, IdentifiedClientState, QueryClientParamsRequest, QueryClientParamsResponse,
QueryClientStateRequest, QueryClientStateResponse, QueryClientStatesRequest,
QueryClientStatesResponse, QueryClientStatusRequest, QueryClientStatusResponse,
QueryConsensusStateHeightsRequest, QueryConsensusStateHeightsResponse,
Expand All @@ -13,21 +13,59 @@ use ibc_proto::ibc::core::client::v1::{
};

use ibc_types::core::client::ClientId;
use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
use ibc_types::TypeUrl;

use std::str::FromStr;
use tonic::{Response, Status};

use crate::component::ClientStateReadExt;
use prost::Message;

use super::IbcQuery;

#[async_trait]
impl ClientQuery for IbcQuery {
async fn client_state(
&self,
_request: tonic::Request<QueryClientStateRequest>,
request: tonic::Request<QueryClientStateRequest>,
) -> std::result::Result<Response<QueryClientStateResponse>, Status> {
Err(tonic::Status::unimplemented("not implemented"))
let snapshot = self.0.latest_snapshot();
let client_id = ClientId::from_str(&request.get_ref().client_id)
.map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
let height = Height {
revision_number: 0,
revision_height: snapshot.version(),
};

// Query for client_state and associated proof.
let (cs_opt, proof) = snapshot
.get_with_proof(client_id.to_string().as_bytes().to_vec())
.await
.map_err(|e| tonic::Status::aborted(format!("couldn't get client: {e}")))?;

// Client state may be None, which we'll convert to a NotFound response.
let client_state = match cs_opt {
// If found, convert to a suitable type to match
// https://docs.rs/ibc-proto/0.36.1/ibc_proto/ibc/core/client/v1/struct.QueryClientStateResponse.html
Some(c) => ibc_proto::google::protobuf::Any {
type_url: TendermintClientState::TYPE_URL.to_string(),
value: c,
},
None => {
return Err(tonic::Status::not_found(format!(
"couldn't find client: {client_id}"
)))
}
};

let res = QueryClientStateResponse {
client_state: Some(client_state),
proof: proof.encode_to_vec(),
proof_height: Some(height),
};

Ok(tonic::Response::new(res))
}
/// ClientStates queries all the IBC light clients of a chain.
async fn client_states(
Expand Down

0 comments on commit 47da4a5

Please sign in to comment.