Skip to content

Commit

Permalink
Introduce 'create connection' cmd (informalsystems#688)
Browse files Browse the repository at this point in the history
* Partial implementation of 'create connection' cmd

* More slick approach for exiting the program.

* Picking up work on this

* Split create connection into the two basic sub-cases

* Refactored create_reusing_clients

* Fixed handshake with delay period option.

* Typos, finishd impl for create_reusing_clients

* Aligned with ADR 006

* Changelog

* Refactored cli_utils. Improved error output.

* More decent doc for Unrecoverable

* fmt fix

* review based on Anca's suggestions

* Removed delay option.

* Update relayer-cli/src/conclude.rs

Co-authored-by: Romain Ruetschi <[email protected]>

* rm Unrecoverable, replaced with unreachable

Co-authored-by: Romain Ruetschi <[email protected]>
  • Loading branch information
adizere and romac authored Mar 1, 2021
1 parent 541f96e commit bc23dba
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [nothing yet]

- [ibc-relayer-cli]
- Added `create connection` CLI ([#630])
- Proposed ADR 006 to describe Hermes v0.2.0 use-cases ([#637])

### IMPROVEMENTS
Expand Down Expand Up @@ -56,6 +57,7 @@

[#561]: https://github.com/informalsystems/ibc-rs/issues/561
[#599]: https://github.com/informalsystems/ibc-rs/issues/599
[#630]: https://github.com/informalsystems/ibc-rs/issues/630
[#672]: https://github.com/informalsystems/ibc-rs/issues/672
[#685]: https://github.com/informalsystems/ibc-rs/issues/685
[#689]: https://github.com/informalsystems/ibc-rs/issues/689
Expand Down
9 changes: 7 additions & 2 deletions relayer-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ use crate::commands::channel::ChannelCmds;
use crate::config::Config;

use self::{
keys::KeysCmd, light::LightCmd, listen::ListenCmd, query::QueryCmd, start::StartCmd, tx::TxCmd,
version::VersionCmd,
create::CreateCmds, keys::KeysCmd, light::LightCmd, listen::ListenCmd, query::QueryCmd,
start::StartCmd, tx::TxCmd, version::VersionCmd,
};

mod channel;
mod cli_utils;
mod config;
mod create;
mod keys;
mod light;
mod listen;
Expand Down Expand Up @@ -53,6 +54,10 @@ pub enum CliCmd {
#[options(help = "Basic functionality for managing the light clients")]
Light(LightCmd),

/// The `create` subcommand
#[options(help = "Create objects on chains")]
Create(CreateCmds),

/// The `start` subcommand
#[options(help = "Start the relayer")]
Start(StartCmd),
Expand Down
57 changes: 19 additions & 38 deletions relayer-cli/src/commands/cli_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ibc_relayer::{chain::runtime::ChainRuntime, config::ChainConfig};
use crate::application::CliApp;
use crate::error::{Error, Kind};

/// Pair of chain handlers that are used by most CLIs.
/// Pair of chain handles that are used by most CLIs.
pub struct ChainHandlePair {
/// Source chain handle
pub src: Box<dyn ChainHandle>,
Expand Down Expand Up @@ -36,45 +36,34 @@ impl ChainHandlePair {
src_chain_id: &ChainId,
dst_chain_id: &ChainId,
) -> Result<Self, Error> {
spawn_chain_runtimes(options, config, src_chain_id, dst_chain_id)
let src = spawn_chain_runtime(options.clone(), config, src_chain_id)?;
let dst = spawn_chain_runtime(options, config, dst_chain_id)?;

Ok(ChainHandlePair { src, dst })
}
}

/// Spawn the source and destination chain runtime from the configuration and chain identifiers,
/// and return the pair of associated handles.
fn spawn_chain_runtimes(
/// Spawns a chain runtime from the configuration and given a chain identifier.
/// Returns the corresponding handle if successful.
pub fn spawn_chain_runtime(
spawn_options: SpawnOptions,
config: &config::Reader<CliApp>,
src_chain_id: &ChainId,
dst_chain_id: &ChainId,
) -> Result<ChainHandlePair, Error> {
let src_config = config
.find_chain(src_chain_id)
.cloned()
.ok_or_else(|| "missing source chain in configuration file".to_string());

let dst_config = config
.find_chain(dst_chain_id)
chain_id: &ChainId,
) -> Result<Box<dyn ChainHandle>, Error> {
let mut chain_config = config
.find_chain(chain_id)
.cloned()
.ok_or_else(|| "missing destination chain configuration file".to_string());

let (mut src_chain_config, mut dst_chain_config) =
zip_result(src_config, dst_config).map_err(|e| Kind::Config.context(e))?;

spawn_options.apply(&mut src_chain_config);
spawn_options.apply(&mut dst_chain_config);
.ok_or_else(|| format!("missing chain for id ({}) in configuration file", chain_id))
.map_err(|e| Kind::Config.context(e))?;

let src_chain_res = ChainRuntime::<CosmosSdkChain>::spawn(src_chain_config)
.map_err(|e| Kind::Runtime.context(e));
spawn_options.apply(&mut chain_config);

let src = src_chain_res.map(|(handle, _)| handle)?;
let chain_res =
ChainRuntime::<CosmosSdkChain>::spawn(chain_config).map_err(|e| Kind::Runtime.context(e));

let dst_chain_res = ChainRuntime::<CosmosSdkChain>::spawn(dst_chain_config)
.map_err(|e| Kind::Runtime.context(e));
let handle = chain_res.map(|(handle, _)| handle)?;

let dst = dst_chain_res.map(|(handle, _)| handle)?;

Ok(ChainHandlePair { src, dst })
Ok(handle)
}

/// Allows override the chain configuration just before
Expand Down Expand Up @@ -111,11 +100,3 @@ impl SpawnOptions {
}
}
}

/// Zip two results together.
fn zip_result<A, B, E>(a: Result<A, E>, b: Result<B, E>) -> Result<(A, B), E> {
match (a, b) {
(Ok(a), Ok(b)) => Ok((a, b)),
(Err(e), _) | (_, Err(e)) => Err(e),
}
}
22 changes: 22 additions & 0 deletions relayer-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! `create` subcommand
use abscissa_core::{Command, Help, Options, Runnable};

use crate::commands::create::connection::CreateConnectionCommand;

mod channel;
mod connection;

/// `create` subcommands
#[derive(Command, Debug, Options, Runnable)]
pub enum CreateCmds {
/// Generic `help`
#[options(help = "Get usage information")]
Help(Help<Self>),

/// Subcommand for creating a `connection`
#[options(help = "Create a new connection between two chains")]
Connection(CreateConnectionCommand),
// /// Subcommand for creating a `channel` with various
// #[options(help = "create a new channel between two chains")]
// Channel(CreateChannelCommand),
}
File renamed without changes.
165 changes: 165 additions & 0 deletions relayer-cli/src/commands/create/connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use abscissa_core::{Command, Options, Runnable};

use ibc::ics02_client::state::ClientState;
use ibc::ics24_host::identifier::{ChainId, ClientId};
use ibc::Height;
use ibc_relayer::config::StoreConfig;
use ibc_relayer::connection::Connection;
use ibc_relayer::foreign_client::ForeignClient;

use crate::commands::cli_utils::{spawn_chain_runtime, ChainHandlePair, SpawnOptions};
use crate::conclude::{exit_with_unrecoverable_error, Output};
use crate::prelude::*;

#[derive(Clone, Command, Debug, Options)]
pub struct CreateConnectionCommand {
#[options(
free,
required,
help = "identifier of the side `a` chain for the new connection"
)]
chain_a_id: ChainId,

#[options(free, help = "identifier of the side `b` chain for the new connection")]
chain_b_id: Option<ChainId>,

#[options(
help = "identifier of client hosted on chain `a`; default: None (creates a new client)",
no_short
)]
client_a: Option<ClientId>,

#[options(
help = "identifier of client hosted on chain `b`; default: None (creates a new client)",
no_short
)]
client_b: Option<ClientId>,
// TODO: Packet delay feature is not implemented, so we disallow specifying this option.
// https://github.com/informalsystems/ibc-rs/issues/640
// #[options(
// help = "delay period parameter for the new connection; default: `0`.",
// no_short
// )]
// delay: Option<u64>,
}

// cargo run --bin hermes -- create connection ibc-0 ibc-1
// cargo run --bin hermes -- create connection ibc-0 ibc-1
// cargo run --bin hermes -- create connection ibc-0 --client-a-id 07-tendermint-0 --client-b-id 07-tendermint-0
impl Runnable for CreateConnectionCommand {
fn run(&self) {
match &self.chain_b_id {
Some(side_b) => self.run_using_new_clients(side_b),
None => self.run_reusing_clients(),
}
}
}

impl CreateConnectionCommand {
/// Creates a connection that uses newly created clients on each side.
fn run_using_new_clients(&self, chain_b_id: &ChainId) {
let config = app_config();

let spawn_options = SpawnOptions::override_store_config(StoreConfig::memory());

let chains =
ChainHandlePair::spawn_with(spawn_options, &config, &self.chain_a_id, chain_b_id)
.unwrap_or_else(exit_with_unrecoverable_error);

// Validate the other options. Bail if the CLI was invoked with incompatible options.
if self.client_a.is_some() {
return Output::error(
"Option `<chain-b-id>` is incompatible with `--client-a`".to_string(),
)
.exit();
}
if self.client_b.is_some() {
return Output::error(
"Option `<chain-b-id>` is incompatible with `--client-b`".to_string(),
)
.exit();
}

info!(
"Creating new clients hosted on chains {} and {}",
self.chain_a_id, chain_b_id
);

let client_a = ForeignClient::new(chains.src.clone(), chains.dst.clone())
.unwrap_or_else(exit_with_unrecoverable_error);
let client_b = ForeignClient::new(chains.dst.clone(), chains.src.clone())
.unwrap_or_else(exit_with_unrecoverable_error);

// Finally, execute the connection handshake.
// TODO: pass the `delay` parameter here.
let c = Connection::new(client_a, client_b, 0);
}

/// Create a connection reusing pre-existing clients on both chains.
fn run_reusing_clients(&self) {
let config = app_config();

let spawn_options = SpawnOptions::override_store_config(StoreConfig::memory());
// Validate & spawn runtime for chain_a.
let chain_a = match spawn_chain_runtime(spawn_options.clone(), &config, &self.chain_a_id) {
Ok(handle) => handle,
Err(e) => return Output::error(format!("{}", e)).exit(),
};

// Unwrap the identifier of the client on chain_a.
let client_a_id = match &self.client_a {
Some(c) => c,
None => {
return Output::error(
"Option `--client-a` is necessary when <chain-b-id> is missing".to_string(),
)
.exit()
}
};

// Query client state. Extract the target chain (chain_id which this client is verifying).
let height = Height::new(chain_a.id().version(), 0);
let chain_b_id = match chain_a.query_client_state(&client_a_id, height) {
Ok(cs) => cs.chain_id(),
Err(e) => {
return Output::error(format!(
"Failed while querying client '{}' on chain '{}' with error: {}",
client_a_id, self.chain_a_id, e
))
.exit()
}
};

// Validate & spawn runtime for chain_b.
let chain_b = match spawn_chain_runtime(spawn_options, &config, &chain_b_id) {
Ok(handle) => handle,
Err(e) => return Output::error(format!("{}", e)).exit(),
};

// Unwrap the identifier of the client on chain_b.
let client_b_id = match &self.client_b {
Some(c) => c,
None => {
return Output::error(
"Option `--client-b` is necessary when <chain-b-id> is missing".to_string(),
)
.exit()
}
};

info!(
"Creating a new connection with pre-existing clients {} and {}",
client_a_id, client_b_id
);

// Get the two ForeignClient objects.
let client_a = ForeignClient::find(chain_b.clone(), chain_a.clone(), &client_a_id)
.unwrap_or_else(exit_with_unrecoverable_error);
let client_b = ForeignClient::find(chain_a.clone(), chain_b.clone(), &client_b_id)
.unwrap_or_else(exit_with_unrecoverable_error);

// All verification passed. Create the Connection object & do the handshake.
// TODO: pass the `delay` parameter here.
let c = Connection::new(client_a, client_b, 0);
}
}
4 changes: 4 additions & 0 deletions relayer-cli/src/commands/tx/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ impl Runnable for TxRawConnInitCmd {
self,
|chains: ChainHandlePair| {
Connection {
delay_period: 0,
a_side: ConnectionSide::new(
chains.src,
self.src_client_id.clone(),
Expand Down Expand Up @@ -109,6 +110,7 @@ impl Runnable for TxRawConnTryCmd {
self,
|chains: ChainHandlePair| {
Connection {
delay_period: 0,
a_side: ConnectionSide::new(
chains.src,
self.src_client_id.clone(),
Expand Down Expand Up @@ -164,6 +166,7 @@ impl Runnable for TxRawConnAckCmd {
self,
|chains: ChainHandlePair| {
Connection {
delay_period: 0,
a_side: ConnectionSide::new(
chains.src,
self.src_client_id.clone(),
Expand Down Expand Up @@ -219,6 +222,7 @@ impl Runnable for TxRawConnConfirmCmd {
self,
|chains: ChainHandlePair| {
Connection {
delay_period: 0,
a_side: ConnectionSide::new(
chains.src,
self.src_client_id.clone(),
Expand Down
26 changes: 26 additions & 0 deletions relayer-cli/src/conclude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
//! Output::success(h).with_result(end).exit();
//! ```
use std::fmt::Display;

use serde::Serialize;
use tracing::error;

Expand All @@ -72,6 +74,30 @@ pub fn exit_with(out: Output) {
}
}

/// Exits the program. Useful when a type produces an error which can no longer be propagated, and
/// the program must exit instead.
///
/// ## Example of use
/// - Without this function:
/// ```ignore
/// let res = ForeignClient::new(chains.src.clone(), chains.dst.clone());
/// let client = match res {
/// Ok(client) => client,
/// Err(e) => return Output::error(format!("{}", e)).exit(),
/// };
/// ```
/// - With support from `exit_with_unrecoverable_error`:
/// ```ignore
/// let client_a = ForeignClient::new(chains.src.clone(), chains.dst.clone())
/// .unwrap_or_else(exit_with_unrecoverable_error);
/// ```
pub fn exit_with_unrecoverable_error<T, E: Display>(err: E) -> T {
// TODO(@romac): Once never (!) stabilizes, adapt `Output::exit` to return !
// https://github.com/informalsystems/ibc-rs/pull/688#discussion_r583758439
Output::error(format!("{}", err)).exit();
unreachable!()
}

/// A CLI output with support for JSON serialization. The only mandatory field is the `status`,
/// which typically signals a success (UNIX process return code `0`) or an error (code `1`). An
/// optional `result` can be added to an output.
Expand Down
1 change: 0 additions & 1 deletion relayer/src/auth.rs

This file was deleted.

Loading

0 comments on commit bc23dba

Please sign in to comment.