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

Implement query for client connections #169

Merged
merged 11 commits into from
Jul 29, 2020
22 changes: 22 additions & 0 deletions modules/src/ics02_client/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::ics03_connection::error::Kind;
use crate::try_from_raw::TryFromRaw;

//TODO: This might need to be migrated to ibc-proto crate. But ClientConnections (as array of strings)
// might not be part of an official proto file
#[derive(::prost::Message)]
pub struct RawClientConnections {
#[prost(string, repeated, tag = "1")]
pub connections: ::std::vec::Vec<String>,
}

impl TryFromRaw for Vec<String> {
type RawType = RawClientConnections;
type Error = anomaly::Error<Kind>;
fn try_from(value: RawClientConnections) -> Result<Self, Self::Error> {
if !value.connections.is_empty() {
Ok(value.connections)
} else {
Err(Kind::ConnectionNotFound.into())
}
}
}
1 change: 1 addition & 0 deletions modules/src/ics02_client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! ICS 02: IBC Client implementation

pub mod client;
pub mod client_type;
pub mod error;
pub mod events;
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 @@ -32,6 +32,10 @@ pub enum QueryClientCmds {
/// The `query client consensus` subcommand
#[options(help = "query client consensus")]
Consensus(client::QueryClientConsensusCmd),

/// The `query client connections` subcommand
#[options(help = "query client connections")]
Connections(client::QueryClientConnectionsCmd),
}

#[derive(Command, Debug, Options, Runnable)]
Expand Down
185 changes: 178 additions & 7 deletions relayer/cli/src/commands/query/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ use crate::prelude::*;

use abscissa_core::{Command, Options, Runnable};
use relayer::config::{ChainConfig, Config};
//use relayer::query::client::{query_client_consensus_state, query_client_full_state};
//use relayer::query::{query, Request};

//use relayer_modules::ics02_client::query::QueryClientFullState;
use relayer_modules::ics24_host::identifier::ClientId;

//use crate::commands::utils::block_on;
use relayer::chain::Chain;
use relayer::chain::CosmosSDKChain;
use relayer_modules::ics24_host::error::ValidationError;
use relayer_modules::ics24_host::identifier::ClientId;
use relayer_modules::ics24_host::Path::ClientConnections;
use tendermint::chain::Id as ChainId;

/// Query client state command
#[derive(Clone, Command, Debug, Options)]
pub struct QueryClientStateCmd {
#[options(free, help = "identifier of the chain to query")]
Expand Down Expand Up @@ -95,6 +93,7 @@ impl Runnable for QueryClientStateCmd {
}
}

/// Query client consensus command
#[derive(Clone, Command, Debug, Options)]
pub struct QueryClientConsensusCmd {
#[options(free, help = "identifier of the chain to query")]
Expand Down Expand Up @@ -214,9 +213,95 @@ fn validate_common_options(
Ok((chain_config.clone(), client_id))
}

/// Query client connections command
#[derive(Clone, Command, Debug, Options)]
pub struct QueryClientConnectionsCmd {
#[options(free, help = "identifier of the chain to query")]
chain_id: Option<ChainId>,

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

#[options(help = "height of the state to query", short = "h")]
height: Option<u64>,

#[options(help = "whether proof is required", short = "p")]
proof: Option<bool>,
}

#[derive(Debug)]
struct QueryClientConnectionsOptions {
client_id: ClientId,
height: u64,
proof: bool,
}

impl QueryClientConnectionsCmd {
fn validate_options(
&self,
config: &Config,
) -> Result<(ChainConfig, QueryClientConnectionsOptions), String> {
let chain_id = self
.chain_id
.ok_or_else(|| "missing chain identifier".to_string())?;
let chain_config = config
.chains
.iter()
.find(|c| c.id == chain_id)
.ok_or_else(|| "missing chain configuration".to_string())?;

let client_id = self
.client_id
.as_ref()
.ok_or_else(|| "missing client identifier".to_string())?
.parse()
.map_err(|err: ValidationError| err.to_string())?;

let opts = QueryClientConnectionsOptions {
client_id,
height: match self.height {
Some(h) => h,
None => 0 as u64,
},
proof: match self.proof {
Some(proof) => proof,
None => true,
},
};
Ok((chain_config.clone(), opts))
}
}

/// Command to handle query for client connections
/// To run without proof:
/// cargo run --bin relayer -- -c relayer/relay/tests/config/fixtures/relayer_conf_example.toml query client connections chain_A clientidone -h 4 -p false
impl Runnable for QueryClientConnectionsCmd {
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 chain = CosmosSDKChain::from_config(chain_config).unwrap();
let res =
chain.query::<Vec<String>>(ClientConnections(opts.client_id), opts.height, opts.proof);
match res {
Ok(cs) => status_info!("client connections query result: ", "{:?}", cs),
Err(e) => status_info!("client connections query error", "{}", e),
}
}
}

/// Tests
#[cfg(test)]
mod tests {
use crate::commands::query::client::QueryClientStateCmd;
use crate::commands::query::client::{QueryClientConnectionsCmd, QueryClientStateCmd};
use relayer::config::parse;

#[test]
Expand Down Expand Up @@ -304,4 +389,90 @@ mod tests {
}
}
}

#[test]
fn parse_query_client_connections_parameters() {
let default_params = QueryClientConnectionsCmd {
chain_id: Some("ibc0".to_string().parse().unwrap()),
client_id: Some("clientidone".to_string().parse().unwrap()),
height: Some(4),
proof: Some(false),
};

struct Test {
name: String,
params: QueryClientConnectionsCmd,
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: QueryClientConnectionsCmd {
chain_id: None,
..default_params.clone()
},
want_pass: false,
},
Test {
name: "Chain not configured".to_string(),
params: QueryClientConnectionsCmd {
chain_id: Some("notibc0oribc1".to_string().parse().unwrap()),
..default_params.clone()
},
want_pass: false,
},
Test {
name: "No client id specified".to_string(),
params: QueryClientConnectionsCmd {
client_id: None,
..default_params.clone()
},
want_pass: false,
},
Test {
name: "Bad client id, non-alpha".to_string(),
params: QueryClientConnectionsCmd {
client_id: Some("p34".to_string()),
..default_params.clone()
},
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
);
}
}
}
}
}