Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI Command to query connection channels #506

Merged
merged 14 commits into from
Jan 13, 2021
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@
### FEATURES

- Add support for streamlining releases ([#507])
- [relayer-cli]
- Implement command to query the channels associated with a connection ([#505])

### IMPROVEMENTS

- Update to `tendermint-rs` v0.17.1 ([#517])


[#505]: https://github.com/informalsystems/ibc-rs/issues/505
[#507]: https://github.com/informalsystems/ibc-rs/issues/507
[#511]: https://github.com/informalsystems/ibc-rs/pull/511
[#517]: https://github.com/informalsystems/ibc-rs/issues/517


## v0.0.6
*December 23, 2020*

Expand Down
4 changes: 4 additions & 0 deletions relayer-cli/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ pub enum QueryConnectionCmds {
/// The `query connection end` subcommand
#[options(help = "query connection end")]
End(connection::QueryConnectionEndCmd),

/// The `query connection channels` subcommand
#[options(help = "query connection channels")]
Channels(connection::QueryConnectionChannelsCmd),
}

#[derive(Command, Debug, Options, Runnable)]
Expand Down
172 changes: 170 additions & 2 deletions relayer-cli/src/commands/query/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ibc::ics03_connection::connection::ConnectionEnd;
use ibc::ics24_host::error::ValidationError;
use ibc::ics24_host::identifier::ChainId;
use ibc::ics24_host::identifier::ConnectionId;

use ibc_proto::ibc::core::channel::v1::QueryConnectionChannelsRequest;
use relayer::chain::{Chain, CosmosSDKChain};
use relayer::config::{ChainConfig, Config};

Expand Down Expand Up @@ -96,11 +96,87 @@ impl Runnable for QueryConnectionEndCmd {
}
}

/// Command for querying the channel identifiers associated with a connection.
/// Sample invocation:
/// `cargo run --bin relayer -- -c simple_config.toml query connection channels ibc-0 connection-0`
#[derive(Clone, Command, Debug, Options)]
pub struct QueryConnectionChannelsCmd {
#[options(free, help = "identifier of the chain to query")]
chain_id: Option<ChainId>,

#[options(free, help = "identifier of the connection to query")]
connection_id: Option<String>,
}

#[derive(Debug)]
struct QueryConnectionChannelsOptions {
connection_id: ConnectionId,
}

impl QueryConnectionChannelsCmd {
fn validate_options(
&self,
config: &Config,
) -> Result<(ChainConfig, QueryConnectionChannelsOptions), String> {
let chain_id = self
.chain_id
.clone()
.ok_or_else(|| "no chain chain identifier provided".to_string())?;
let chain_config = config
.find_chain(&chain_id)
.ok_or_else(|| "missing chain configuration for the given chain id".to_string())?;

let connection_id = self
.connection_id
.as_ref()
.ok_or_else(|| "no connection identifier was provided".to_string())?
.parse()
.map_err(|err: ValidationError| err.to_string())?;

let opts = QueryConnectionChannelsOptions { connection_id };

Ok((chain_config.clone(), opts))
}
}

impl Runnable for QueryConnectionChannelsCmd {
fn run(&self) {
let config = app_config();

let (chain_config, opts) = match self.validate_options(&config) {
Err(err) => {
status_err!("invalid options: {}", err);
return;
}
Ok(result) => result,
};
status_info!("Options", "{:?}", opts);

let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap()));
let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap();

let req = QueryConnectionChannelsRequest {
connection: opts.connection_id.to_string(),
pagination: None,
};

let res: Result<_, Error> = chain
.query_connection_channels(req)
.map_err(|e| Kind::Query.context(e).into());

match res {
Ok(cs) => status_info!("connection channels query result: ", "{:?}", cs),
Err(e) => status_info!("connection channels query error", "{}", e),
}
}
}

#[cfg(test)]
mod tests {
use crate::commands::query::connection::QueryConnectionEndCmd;
use relayer::config::parse;

use crate::commands::query::connection::{QueryConnectionChannelsCmd, QueryConnectionEndCmd};

#[test]
fn parse_connection_query_end_parameters() {
let default_params = QueryConnectionEndCmd {
Expand Down Expand Up @@ -194,4 +270,96 @@ mod tests {
}
}
}

#[test]
fn parse_query_connection_channels_parameters() {
let default_params = QueryConnectionChannelsCmd {
chain_id: Some("ibc-0".to_string().parse().unwrap()),
connection_id: Some("ibconeconnection".to_string().parse().unwrap()),
};

struct Test {
name: String,
params: QueryConnectionChannelsCmd,
want_pass: bool,
}

let tests: Vec<Test> = vec![
Test {
name: "Good parameters".to_string(),
params: default_params.clone(),
want_pass: true,
},
Test {
name: "No chain specified".to_string(),
params: QueryConnectionChannelsCmd {
chain_id: None,
..default_params.clone()
},
want_pass: false,
},
Test {
name: "Chain not configured".to_string(),
params: QueryConnectionChannelsCmd {
chain_id: Some("ibc007".to_string().parse().unwrap()),
..default_params.clone()
},
want_pass: false,
},
Test {
name: "No connection id specified".to_string(),
params: QueryConnectionChannelsCmd {
connection_id: None,
..default_params.clone()
},
want_pass: false,
},
Test {
name: "Bad connection, non-alpha".to_string(),
params: QueryConnectionChannelsCmd {
connection_id: Some("connection-0^".to_string()),
..default_params.clone()
},
want_pass: false,
},
Test {
name: "Bad connection, name too short".to_string(),
params: QueryConnectionChannelsCmd {
connection_id: Some("connshort".to_string()),
..default_params
},
want_pass: false,
},
]
.into_iter()
.collect();

let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/two_chains.toml"
);

let config = parse(path).unwrap();

for test in tests {
let res = test.params.validate_options(&config);

match res {
Ok(_res) => {
assert!(
test.want_pass,
"validate_options should have failed for test {}",
test.name
);
}
Err(err) => {
assert!(
!test.want_pass,
"validate_options failed for test {}, \nerr {}",
test.name, err
);
}
}
}
}
}
10 changes: 8 additions & 2 deletions relayer/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use tendermint::account::Id as AccountId;
use tendermint::block::Height;

use ibc_proto::ibc::core::channel::v1::{
PacketState, QueryPacketAcknowledgementsRequest, QueryPacketCommitmentsRequest,
QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
PacketState, QueryConnectionChannelsRequest, QueryPacketAcknowledgementsRequest,
QueryPacketCommitmentsRequest, QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
};
use ibc_proto::ibc::core::commitment::v1::MerkleProof;

Expand Down Expand Up @@ -337,6 +337,12 @@ pub trait Chain: Sized {
request: QueryUnreceivedAcksRequest,
) -> Result<Vec<u64>, Error>;

/// Performs a query to retrieve the identifiers of all channels associated with a connection.
fn query_connection_channels(
&self,
request: QueryConnectionChannelsRequest,
) -> Result<Vec<ChannelId>, Error>;

fn build_packet_proofs(
&self,
packet_type: PacketMsgType,
Expand Down
41 changes: 34 additions & 7 deletions relayer/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInf
// Support for GRPC
use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest};
use ibc_proto::ibc::core::channel::v1::{
PacketState, QueryPacketAcknowledgementsRequest, QueryPacketCommitmentsRequest,
QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
PacketState, QueryConnectionChannelsRequest, QueryPacketAcknowledgementsRequest,
QueryPacketCommitmentsRequest, QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
};
use ibc_proto::ibc::core::commitment::v1::MerkleProof;

Expand All @@ -51,7 +51,7 @@ use ibc::ics07_tendermint::header::Header as TMHeader;

use ibc::ics23_commitment::commitment::CommitmentPrefix;
use ibc::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof;
use ibc::ics24_host::identifier::{ChainId, ClientId};
use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId};
use ibc::ics24_host::Path::ClientConsensusState as ClientConsensusPath;
use ibc::ics24_host::Path::ClientState as ClientStatePath;
use ibc::ics24_host::{Path, IBC_QUERY_PATH};
Expand Down Expand Up @@ -516,7 +516,6 @@ impl Chain for CosmosSDKChain {
Ok(key)
}
/// Queries the packet commitment hashes associated with a channel.
/// TODO - move to the chain trait
fn query_packet_commitments(
&self,
request: QueryPacketCommitmentsRequest,
Expand Down Expand Up @@ -548,7 +547,6 @@ impl Chain for CosmosSDKChain {
}

/// Queries the packet commitment hashes associated with a channel.
/// TODO - move the chain trait
fn query_unreceived_packets(
&self,
request: QueryUnreceivedPacketsRequest,
Expand All @@ -572,7 +570,6 @@ impl Chain for CosmosSDKChain {
}

/// Queries the packet acknowledgment hashes associated with a channel.
/// TODO - move to the chain trait
fn query_packet_acknowledgements(
&self,
request: QueryPacketAcknowledgementsRequest,
Expand Down Expand Up @@ -604,7 +601,6 @@ impl Chain for CosmosSDKChain {
}

/// Queries the packet commitment hashes associated with a channel.
/// TODO - move the chain trait
fn query_unreceived_acknowledgements(
&self,
request: QueryUnreceivedAcksRequest,
Expand Down Expand Up @@ -655,6 +651,37 @@ impl Chain for CosmosSDKChain {
}
Ok(result)
}

fn query_connection_channels(
&self,
request: QueryConnectionChannelsRequest,
) -> Result<Vec<ChannelId>, Error> {
let grpc_addr =
Uri::from_str(&self.config().grpc_addr).map_err(|e| Kind::Grpc.context(e))?;
let mut client = self
.block_on(
ibc_proto::ibc::core::channel::v1::query_client::QueryClient::connect(grpc_addr),
)?
.map_err(|e| Kind::Grpc.context(e))?;

let request = tonic::Request::new(request);

let response = self
.block_on(client.connection_channels(request))?
.map_err(|e| Kind::Grpc.context(e))?
.into_inner();

// TODO: add warnings for any identifiers that fail to parse (below).
// https://github.com/informalsystems/ibc-rs/pull/506#discussion_r555945560

let vec_ids = response
.channels
.iter()
.filter_map(|ic| ChannelId::from_str(ic.channel_id.as_str()).ok())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are any errors here, they will be silently ignored. Maybe we can instead do something like this: https://doc.rust-lang.org/rust-by-example/error/iter_result.html#fail-the-entire-operation-with-collect

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this but the question is do we want to fail the entire operation if we cannot extract one ChannelId?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Between the option of failing the operation because an identifier fails to parse, and ignoring the offending identifier(s), I would rather go with the latter.

One middle-road solution is to have warnings in case some identifiers fail to parse. We're not (yet) principled with how we use warnings, if at all, so I'm adding a TODO here to keep an eye out in the future. c51b814

.collect();

Ok(vec_ids)
}
}

fn packet_query(request: &QueryPacketEventDataRequest, seq: &Sequence) -> Result<Query, Error> {
Expand Down
13 changes: 10 additions & 3 deletions relayer/src/chain/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use tendermint::account::Id;
use tendermint_testgen::light_block::TMLightBlock;

use ibc_proto::ibc::core::channel::v1::{
PacketState, QueryPacketAcknowledgementsRequest, QueryPacketCommitmentsRequest,
QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
PacketState, QueryConnectionChannelsRequest, QueryPacketAcknowledgementsRequest,
QueryPacketCommitmentsRequest, QueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest,
};
use ibc_proto::ibc::core::commitment::v1::MerkleProof;

Expand All @@ -25,7 +25,7 @@ use ibc::ics07_tendermint::consensus_state::ConsensusState as TendermintConsensu
use ibc::ics07_tendermint::header::Header as TendermintHeader;
use ibc::ics18_relayer::context::ICS18Context;
use ibc::ics23_commitment::commitment::CommitmentPrefix;
use ibc::ics24_host::identifier::{ChainId, ClientId};
use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId};
use ibc::ics24_host::Path;
use ibc::mock::context::MockContext;
use ibc::mock::host::HostType;
Expand Down Expand Up @@ -228,6 +228,13 @@ impl Chain for MockChain {
unimplemented!()
}

fn query_connection_channels(
&self,
_request: QueryConnectionChannelsRequest,
) -> Result<Vec<ChannelId>, Error> {
unimplemented!()
}

fn query_txs(&self, _request: QueryPacketEventDataRequest) -> Result<Vec<IBCEvent>, Error> {
unimplemented!()
}
Expand Down