diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ccd774cb..cd84a98acb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [relayer-cli] - Replace `ChannelConfig` in `Channel::new` ([#511]) - Add `packet-send` CLI ([#470]) + - UX improvements for relayer txs ([#536, 540]) - [relayer] - Performance improvements ([#514], [#537]) @@ -44,7 +45,9 @@ [#517]: https://github.com/informalsystems/ibc-rs/issues/517 [#525]: https://github.com/informalsystems/ibc-rs/issues/525 [#527]: https://github.com/informalsystems/ibc-rs/issues/527 +[#536]: https://github.com/informalsystems/ibc-rs/issues/536 [#537]: https://github.com/informalsystems/ibc-rs/issues/537 +[#540]: https://github.com/informalsystems/ibc-rs/issues/540 ## v0.0.6 diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index e28ba50506..88812355d8 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -195,7 +195,6 @@ impl Protobuf for AnyClientState {} impl TryFrom for AnyClientState { type Error = Error; - // TODO Fix type urls: avoid having hardcoded values sprinkled around the whole codebase. fn try_from(raw: Any) -> Result { match raw.type_url.as_str() { "" => Err(Kind::EmptyClientState.into()), @@ -286,6 +285,8 @@ impl TryFrom for AnyConsensusState { fn try_from(value: Any) -> Result { match value.type_url.as_str() { + "" => Err(Kind::EmptyConsensusState.into()), + TENDERMINT_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Tendermint( TendermintConsensusState::decode_vec(&value.value) .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index 6599aae95f..7cb20edd3f 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -36,6 +36,9 @@ pub enum Kind { #[error("unknown client consensus state type: {0}")] UnknownConsensusStateType(String), + #[error("empty client consensus state")] + EmptyConsensusState, + #[error("unknown header type: {0}")] UnknownHeaderType(String), diff --git a/relayer-cli/relayer_operation_instructions.md b/relayer-cli/relayer_operation_instructions.md index ca672ff795..ee726f4aed 100644 --- a/relayer-cli/relayer_operation_instructions.md +++ b/relayer-cli/relayer_operation_instructions.md @@ -279,3 +279,44 @@ Jan 20 11:28:47.842 INFO relayer::macros::profiling: ⏳ inner operation - s Jan 20 11:28:49.846 INFO relayer::macros::profiling: ⏳ inner operation - elapsed: 2004ms Jan 20 11:28:49.847 INFO relayer::macros::profiling: ⏳ myfunction: x=42 - elapsed: 3005ms ``` + +## Parametrizing the log output level + +The relayer configuration file, called `loop_config.toml` in the examples above +permits parametrization of output verbosity via the knob called `log_level`. +Relevant snippet: + +```toml +[global] +timeout = '10s' +strategy = 'naive' +log_level = 'error' +``` + +Valid options for `log_level` are: 'error', 'warn', 'info', 'debug', 'trace'. +These levels correspond to the tracing sub-component of the relayer-cli, [see +here](https://docs.rs/tracing-core/0.1.17/tracing_core/struct.Level.html). + +The relayer will _always_ print a last line summarizing the result of its +operation for queries of transactions. In addition to this last line, +arbitrary debug, info, or other outputs may be produced. Example, with +`log_level = 'debug'`: + +```bash +Running `target/debug/relayer -c loop_config.toml query client consensus ibc-0 07-tendermint-X 0 1` +{"timestamp":"Jan 20 19:21:52.070","level":"DEBUG","fields":{"message":"registered component: abscissa_core::terminal::component::Terminal (v0.5.2)"},"target":"abscissa_core::component::registry"} +{"timestamp":"Jan 20 19:21:52.071","level":"DEBUG","fields":{"message":"registered component: relayer_cli::components::Tracing (v0.0.6)"},"target":"abscissa_core::component::registry"} +{"timestamp":"Jan 20 19:21:52.078","level":"INFO","fields":{"message":"Options QueryClientConsensusOptions { client_id: ClientId(\"07-tendermint-X\"), revision_number: 0, revision_height: 1, height: 0, proof: true }"},"target":"relayer_cli::commands::query::client"} +{"timestamp":"Jan 20 19:21:52.080","level":"DEBUG","fields":{"message":"resolving host=\"localhost\""},"target":"hyper::client::connect::dns"} +{"timestamp":"Jan 20 19:21:52.083","level":"DEBUG","fields":{"message":"connecting to [::1]:26657"},"target":"hyper::client::connect::http"} +{"timestamp":"Jan 20 19:21:52.083","level":"DEBUG","fields":{"message":"connecting to 127.0.0.1:26657"},"target":"hyper::client::connect::http"} +{"status":"error","result":["query error: RPC error to endpoint tcp://localhost:26657: error trying to connect: tcp connect error: Connection refused (os error 61) (code: 0)"]} +``` + +For the same command, with `log_level = 'error'`, just the last line will be +produced: + +```bash + Running `target/debug/relayer -c loop_config.toml query client consensus ibc-0 07-tendermint-X 0 1` +{"status":"error","result":["query error: RPC error to endpoint tcp://localhost:26657: error trying to connect: tcp connect error: Connection refused (os error 61) (code: 0)"]} +``` \ No newline at end of file diff --git a/relayer-cli/src/application.rs b/relayer-cli/src/application.rs index 25cbc278e8..29989f54a5 100644 --- a/relayer-cli/src/application.rs +++ b/relayer-cli/src/application.rs @@ -1,9 +1,12 @@ //! Cli Abscissa Application +use crate::components::Tracing; use crate::{commands::CliCmd, config::Config}; +use abscissa_core::terminal::component::Terminal; use abscissa_core::{ application::{self, AppCell}, - config, trace, Application, EntryPoint, FrameworkError, StandardPaths, + component::Component, + config, trace, Application, Configurable, EntryPoint, FrameworkError, StandardPaths, }; /// Application state @@ -98,6 +101,24 @@ impl Application for CliApp { Ok(()) } + /// Overrides the default abscissa components, so that we can setup tracing on our own. See + /// also `register_components`. + fn framework_components( + &mut self, + command: &Self::Cmd, + ) -> Result>>, FrameworkError> { + let terminal = Terminal::new(self.term_colors(command)); + + let config = command + .config_path() + .map(|path| self.load_config(&path)) + .transpose()? + .unwrap_or_default(); + let tracing = Tracing::new(config.global)?; + + Ok(vec![Box::new(terminal), Box::new(tracing)]) + } + /// Get tracing configuration from command-line options fn tracing_config(&self, command: &EntryPoint) -> trace::Config { if command.verbose { diff --git a/relayer-cli/src/commands.rs b/relayer-cli/src/commands.rs index b703a14606..9e696b3698 100644 --- a/relayer-cli/src/commands.rs +++ b/relayer-cli/src/commands.rs @@ -12,12 +12,11 @@ mod listen; mod query; mod start; mod tx; -mod v0; mod version; use self::{ config::ConfigCmd, keys::KeysCmd, light::LightCmd, listen::ListenCmd, query::QueryCmd, - start::StartCmd, tx::TxCmd, v0::V0Cmd, version::VersionCmd, + start::StartCmd, tx::TxCmd, version::VersionCmd, }; use crate::config::Config; @@ -34,12 +33,8 @@ pub enum CliCmd { #[options(help = "get usage information")] Help(Help), - /// The `v0` subcommand - #[options(help = "start the v0 relayer")] - V0(V0Cmd), - /// The `start` subcommand - #[options(help = "start the relayer")] + #[options(help = "start the relayer (currently this refers to the v0 relayer)")] Start(StartCmd), /// The `listen` subcommand diff --git a/relayer-cli/src/commands/config/validate.rs b/relayer-cli/src/commands/config/validate.rs index 03166eacf3..c642beeaae 100644 --- a/relayer-cli/src/commands/config/validate.rs +++ b/relayer-cli/src/commands/config/validate.rs @@ -1,7 +1,8 @@ -use crate::prelude::*; - use abscissa_core::{Command, Options, Runnable}; +use crate::conclude::Output; +use crate::prelude::*; + #[derive(Command, Debug, Options)] pub struct ValidateCmd {} @@ -9,8 +10,10 @@ impl Runnable for ValidateCmd { /// Validate the loaded configuration. fn run(&self) { let config = app_config(); - status_ok!("Loaded configuration:", "{:#?}", *config); + info!("Loaded configuration: {:?}", *config); // TODO: Validate configuration + + Output::with_success().exit(); } } diff --git a/relayer-cli/src/commands/keys/add.rs b/relayer-cli/src/commands/keys/add.rs index 084d467971..f4248d0a73 100644 --- a/relayer-cli/src/commands/keys/add.rs +++ b/relayer-cli/src/commands/keys/add.rs @@ -1,10 +1,11 @@ -use crate::application::app_config; use abscissa_core::{Command, Options, Runnable}; + use relayer::config::Config; +use relayer::keys::add::{add_key, KeysAddOptions}; +use crate::application::app_config; +use crate::conclude::Output; use crate::error::{Error, Kind}; -use crate::prelude::*; -use relayer::keys::add::{add_key, KeysAddOptions}; #[derive(Clone, Command, Debug, Options)] pub struct KeysAddCmd { @@ -47,8 +48,7 @@ impl Runnable for KeysAddCmd { let opts = match self.validate_options(&config) { Err(err) => { - status_err!("invalid options: {}", err); - return; + return Output::error(err).exit(); } Ok(result) => result, }; @@ -56,8 +56,8 @@ impl Runnable for KeysAddCmd { let res: Result = add_key(opts).map_err(|e| Kind::Keys.context(e).into()); match res { - Ok(r) => status_info!("key add result: ", "{:?}", r), - Err(e) => status_info!("key add failed: ", "{}", e), + Ok(r) => Output::success(r).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/keys/list.rs b/relayer-cli/src/commands/keys/list.rs index 5dd1528cfe..0b0af841ab 100644 --- a/relayer-cli/src/commands/keys/list.rs +++ b/relayer-cli/src/commands/keys/list.rs @@ -1,10 +1,11 @@ -use crate::application::app_config; use abscissa_core::{Command, Options, Runnable}; + use relayer::config::Config; +use relayer::keys::list::{list_keys, KeysListOptions}; +use crate::application::app_config; +use crate::conclude::Output; use crate::error::{Error, Kind}; -use crate::prelude::*; -use relayer::keys::list::{list_keys, KeysListOptions}; #[derive(Clone, Command, Debug, Options)] pub struct KeysListCmd { @@ -37,8 +38,7 @@ impl Runnable for KeysListCmd { let opts = match self.validate_options(&config) { Err(err) => { - status_err!("invalid options: {}", err); - return; + return Output::error(err).exit(); } Ok(result) => result, }; @@ -46,8 +46,8 @@ impl Runnable for KeysListCmd { let res: Result = list_keys(opts).map_err(|e| Kind::Keys.context(e).into()); match res { - Ok(r) => status_info!("keys list result: ", "{:?}", r), - Err(e) => status_info!("keys list failed: ", "{}", e), + Ok(r) => Output::success(r).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/keys/restore.rs b/relayer-cli/src/commands/keys/restore.rs index 562fe803fb..7ace497f6a 100644 --- a/relayer-cli/src/commands/keys/restore.rs +++ b/relayer-cli/src/commands/keys/restore.rs @@ -1,10 +1,11 @@ -use crate::application::app_config; use abscissa_core::{Command, Options, Runnable}; + use relayer::config::Config; +use relayer::keys::restore::{restore_key, KeysRestoreOptions}; +use crate::application::app_config; +use crate::conclude::Output; use crate::error::{Error, Kind}; -use crate::prelude::*; -use relayer::keys::restore::{restore_key, KeysRestoreOptions}; #[derive(Clone, Command, Debug, Options)] pub struct KeyRestoreCmd { @@ -55,8 +56,7 @@ impl Runnable for KeyRestoreCmd { let opts = match self.validate_options(&config) { Err(err) => { - status_err!("invalid options: {}", err); - return; + return Output::error(err).exit(); } Ok(result) => result, }; @@ -65,8 +65,8 @@ impl Runnable for KeyRestoreCmd { restore_key(opts).map_err(|e| Kind::Keys.context(e).into()); match res { - Ok(r) => status_info!("key restore result: ", "{:?}", hex::encode(r)), - Err(e) => status_info!("key restore failed: ", "{}", e), + Ok(r) => Output::success(r).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/light/rm.rs b/relayer-cli/src/commands/light/rm.rs index 72f5631a3a..d0fe40f41f 100644 --- a/relayer-cli/src/commands/light/rm.rs +++ b/relayer-cli/src/commands/light/rm.rs @@ -1,12 +1,13 @@ use std::{io, io::Write, ops::Deref}; -use crate::prelude::*; - use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, Runnable}; +use tendermint_light_client::types::PeerId; use ibc::ics24_host::identifier::ChainId; use relayer::config::PeersConfig; -use tendermint_light_client::types::PeerId; + +use crate::conclude::Output; +use crate::prelude::*; #[derive(Command, Debug, Options)] pub struct RmCmd { @@ -88,20 +89,26 @@ impl RmCmd { .as_mut() .ok_or_else(|| format!("no peers configured for chain: {}", options.chain_id))?; - if options.all && (options.yes || confirm(&options.chain_id)?) { + let rmd_peers = if options.all && (options.yes || confirm(&options.chain_id)?) { let removed_peers = get_all_peer_ids(&peers_config); chain_config.peers = None; - status_ok!("Removed", "light client peers {:?}", removed_peers); + + removed_peers } else { + let mut res: Vec = vec![]; for peer_id in options.peer_ids { let removed_peer = remove_peer(&mut peers_config, peer_id, options.force)?; - status_ok!("Removed", "light client peer '{}'", removed_peer); + res.push(removed_peer.to_string()); } + + res }; let config_path = crate::config::config_path()?; relayer::config::store(&config, config_path)?; + Output::success(format!("Removed light client peer(s) '{:?}'", rmd_peers)).exit(); + Ok(()) } } diff --git a/relayer-cli/src/commands/query/channel.rs b/relayer-cli/src/commands/query/channel.rs index 63eb0995f6..31db1dfe84 100644 --- a/relayer-cli/src/commands/query/channel.rs +++ b/relayer-cli/src/commands/query/channel.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use tendermint_proto::Protobuf; use tokio::runtime::Runtime as TokioRuntime; @@ -86,11 +85,11 @@ impl Runnable for QueryChannelEndCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); // run without proof: // cargo run --bin relayer -- -c relayer/tests/config/fixtures/simple_config.toml query channel end ibc-test firstport firstchannel --height 3 -p false @@ -111,10 +110,8 @@ impl Runnable for QueryChannelEndCmd { }); match res { - Ok(ce) => Output::with_success().with_result(json!(ce)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(ce) => Output::success(ce).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/query/client.rs b/relayer-cli/src/commands/query/client.rs index 2083847786..36c23a6b42 100644 --- a/relayer-cli/src/commands/query/client.rs +++ b/relayer-cli/src/commands/query/client.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use tendermint_proto::Protobuf; use tokio::runtime::Runtime as TokioRuntime; +use tracing::info; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use ibc::ics02_client::raw::ConnectionIds as ConnectionIDs; @@ -71,11 +71,11 @@ impl Runnable for QueryClientStateCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -88,10 +88,8 @@ impl Runnable for QueryClientStateCmd { AnyClientState::decode_vec(&v.value).map_err(|e| Kind::Query.context(e).into()) }); match res { - Ok(cs) => Output::with_success().with_result(json!(cs)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(cs) => Output::success(cs).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -165,11 +163,11 @@ impl Runnable for QueryClientConsensusCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -191,10 +189,8 @@ impl Runnable for QueryClientConsensusCmd { }); match res { - Ok(cs) => Output::with_success().with_result(json!(cs)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(cs) => Output::success(cs).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -276,11 +272,11 @@ impl Runnable for QueryClientConnectionsCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -294,10 +290,8 @@ impl Runnable for QueryClientConnectionsCmd { }); match res { - Ok(cs) => Output::with_success().with_result(json!(cs)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(cs) => Output::success(cs).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index 9c2521cf35..77b099b396 100644 --- a/relayer-cli/src/commands/query/connection.rs +++ b/relayer-cli/src/commands/query/connection.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use tokio::runtime::Runtime as TokioRuntime; use ibc::ics03_connection::connection::ConnectionEnd; @@ -75,11 +74,11 @@ impl Runnable for QueryConnectionEndCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -91,10 +90,8 @@ impl Runnable for QueryConnectionEndCmd { .map_err(|e| Kind::Query.context(e).into()); match res { - Ok(ce) => Output::with_success().with_result(json!(ce)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(ce) => Output::success(ce).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -148,11 +145,11 @@ impl Runnable for QueryConnectionChannelsCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -167,10 +164,8 @@ impl Runnable for QueryConnectionChannelsCmd { .map_err(|e| Kind::Query.context(e).into()); match res { - Ok(cids) => Output::with_success().with_result(json!(cids)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(cids) => Output::success(cids).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/query/packet.rs b/relayer-cli/src/commands/query/packet.rs index 2f78f15d6f..d22a71a6e5 100644 --- a/relayer-cli/src/commands/query/packet.rs +++ b/relayer-cli/src/commands/query/packet.rs @@ -59,11 +59,11 @@ impl Runnable for QueryPacketCommitmentsCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -83,14 +83,9 @@ impl Runnable for QueryPacketCommitmentsCmd { // Transform the raw packet commitm. state into the list of sequence numbers let seqs: Vec = cs.0.iter().map(|ps| ps.sequence).collect(); - Output::with_success() - .with_result(json!(seqs)) - .with_result(json!(cs.1)) - .exit(); + Output::success(seqs).with_result(json!(cs.1)).exit(); } - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -138,11 +133,11 @@ impl Runnable for QueryPacketCommitmentCmd { let (chain_config, opts, sequence) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); // run without proof: // cargo run --bin relayer -- -c relayer/tests/config/fixtures/simple_config.toml query packet commitment ibc-0 transfer ibconexfer 3 --height 3 @@ -158,10 +153,8 @@ impl Runnable for QueryPacketCommitmentCmd { ); match res { - Ok(cs) => Output::with_success().with_result(json!(cs.1)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(cs) => Output::success(cs.1).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -220,11 +213,11 @@ impl Runnable for QueryUnreceivedPacketsCmd { let (dst_chain_config, src_chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let src_chain = CosmosSDKChain::bootstrap(src_chain_config, rt.clone()).unwrap(); @@ -260,10 +253,8 @@ impl Runnable for QueryUnreceivedPacketsCmd { let res = dst_chain.query_unreceived_packets(request); match res { - Ok(seqs) => Output::with_success().with_result(json!(seqs)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(seqs) => Output::success(seqs).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -309,11 +300,11 @@ impl Runnable for QueryPacketAcknowledgementsCmd { let (chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); @@ -333,14 +324,9 @@ impl Runnable for QueryPacketAcknowledgementsCmd { // Transform the raw packet state into the list of acks. sequence numbers let seqs: Vec = ps.0.iter().map(|ps| ps.sequence).collect(); - Output::with_success() - .with_result(json!(seqs)) - .with_result(json!(ps.1)) - .exit(); + Output::success(seqs).with_result(json!(ps.1)).exit(); } - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -388,11 +374,11 @@ impl Runnable for QueryPacketAcknowledgmentCmd { let (chain_config, opts, sequence) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); // run without proof: // cargo run --bin relayer -- -c relayer/tests/config/fixtures/simple_config.toml query packet acknowledgment ibc-0 transfer ibconexfer --height 3 @@ -408,10 +394,8 @@ impl Runnable for QueryPacketAcknowledgmentCmd { ); match res { - Ok(out) => Output::with_success().with_result(json!(out)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(out) => Output::success(out).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -467,11 +451,11 @@ impl Runnable for QueryUnreceivedAcknowledgementCmd { let (dst_chain_config, src_chain_config, opts) = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Options", "{:?}", opts); + info!("Options {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); let src_chain = CosmosSDKChain::bootstrap(src_chain_config, rt.clone()).unwrap(); @@ -507,10 +491,8 @@ impl Runnable for QueryUnreceivedAcknowledgementCmd { let res = dst_chain.query_unreceived_acknowledgements(request); match res { - Ok(seqs) => Output::with_success().with_result(json!(seqs)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(seqs) => Output::success(seqs).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/start.rs b/relayer-cli/src/commands/start.rs index 8b516c65a9..8519f81704 100644 --- a/relayer-cli/src/commands/start.rs +++ b/relayer-cli/src/commands/start.rs @@ -2,35 +2,59 @@ use std::ops::Deref; use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, Runnable}; +use ibc::ics04_channel::channel::Order; +use relayer::chain::runtime::ChainRuntime; +use relayer::chain::CosmosSDKChain; use relayer::config::Config; +use relayer::relay::channel_relay; use crate::prelude::*; #[derive(Command, Debug, Options)] -pub struct StartCmd { - #[options(help = "reset state from trust options", short = "r")] - reset: bool, -} +pub struct StartCmd {} impl StartCmd { - async fn cmd(&self) -> Result<(), BoxError> { + fn cmd(&self) -> Result<(), BoxError> { let config = app_config().clone(); - start(config, self.reset).await + debug!("launching 'v0' command"); + v0_task(&config) } } impl Runnable for StartCmd { fn run(&self) { - let rt = tokio::runtime::Runtime::new().unwrap(); - - rt.block_on(async move { - self.cmd() - .await - .unwrap_or_else(|e| fatal_error(app_reader().deref(), &*e)); - }); + self.cmd() + .unwrap_or_else(|e| fatal_error(app_reader().deref(), &*e)) } } -async fn start(config: Config, reset: bool) -> Result<(), BoxError> { - todo!() // TODO: Move v0 command here +pub fn v0_task(config: &Config) -> Result<(), BoxError> { + // Relay for a single channel, first on the first connection in configuration + let conn = &config + .connections + .clone() + .ok_or("No connections configured")?[0]; + + let ordering = Order::default(); // TODO - add to config + let path = conn.paths.clone().ok_or("No paths configured")?[0].clone(); + + let src_chain_config = config + .find_chain(&conn.a_chain) + .cloned() + .ok_or("Configuration for source chain not found")?; + + let dst_chain_config = config + .find_chain(&conn.b_chain) + .cloned() + .ok_or("Configuration for source chain not found")?; + + let (src_chain_handle, _) = ChainRuntime::::spawn(src_chain_config)?; + let (dst_chain_handle, _) = ChainRuntime::::spawn(dst_chain_config)?; + + Ok(channel_relay( + src_chain_handle, + dst_chain_handle, + ordering, + path, + )?) } diff --git a/relayer-cli/src/commands/tx/channel.rs b/relayer-cli/src/commands/tx/channel.rs index c1d0273055..82046969ac 100644 --- a/relayer-cli/src/commands/tx/channel.rs +++ b/relayer-cli/src/commands/tx/channel.rs @@ -59,11 +59,10 @@ macro_rules! chan_open_cmd { let (src_chain_config, dst_chain_config) = match (src_config, dst_config) { (Ok(s), Ok(d)) => (s, d), (_, _) => { - return Output::with_error() - .with_result(json!( - "error occurred in finding the chains' configuration" - )) - .exit(); + return Output::error(json!( + "error occurred in finding the chains' configuration" + )) + .exit(); } }; @@ -85,21 +84,32 @@ macro_rules! chan_open_cmd { ), }; - status_info!("Message ", "{}: {:?}", $dbg_string, opts); + info!("Message {}: {:?}", $dbg_string, opts); - let (src_chain, _) = - ChainRuntime::::spawn(src_chain_config.clone()).unwrap(); - let (dst_chain, _) = - ChainRuntime::::spawn(dst_chain_config.clone()).unwrap(); + let src_chain_res = ChainRuntime::::spawn(src_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; + + let dst_chain_res = ChainRuntime::::spawn(dst_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result = $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(receipt) => Output::with_success().with_result(json!(receipt)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index 70266ef7cf..e0c921ec57 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -1,5 +1,4 @@ use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use ibc::events::IBCEvent; use ibc::ics24_host::identifier::ClientId; @@ -28,28 +27,38 @@ impl Runnable for TxCreateClientCmd { match validate_common_options(&self.dst_chain_id, &self.src_chain_id) { Ok(result) => result, Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } }; - - status_info!( - "Message CreateClient", - "for source chain: {:?}, on destination chain: {:?}", - src_chain_config.id, - dst_chain_config.id + info!( + "Message CreateClient for source chain: {:?}, on destination chain: {:?}", + src_chain_config.id, dst_chain_config.id ); - let (src_chain, _) = ChainRuntime::::spawn(src_chain_config).unwrap(); - let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); + let src_chain_res = ChainRuntime::::spawn(src_chain_config) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; + + let dst_chain_res = ChainRuntime::::spawn(dst_chain_config) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result = build_create_client_and_send(dst_chain, src_chain) .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(receipt) => Output::with_success().with_result(json!(receipt)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -76,30 +85,40 @@ impl Runnable for TxUpdateClientCmd { let (dst_chain_config, src_chain_config) = match opts { Ok(result) => result, Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } }; - status_info!( - "Message UpdateClient", - "id: {:?}, for chain: {:?}, on chain: {:?}", - self.dst_client_id, - src_chain_config.id, - dst_chain_config.id + info!( + "Message UpdateClient id: {:?}, for chain: {:?}, on chain: {:?}", + self.dst_client_id, src_chain_config.id, dst_chain_config.id ); - let (src_chain, _) = ChainRuntime::::spawn(src_chain_config).unwrap(); - let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); + let src_chain_res = ChainRuntime::::spawn(src_chain_config) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; + + let dst_chain_res = ChainRuntime::::spawn(dst_chain_config) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result = build_update_client_and_send(dst_chain, src_chain, &self.dst_client_id) .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(receipt) => Output::with_success().with_result(json!(receipt)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -113,20 +132,20 @@ fn validate_common_options( // Validate parameters let dst_chain_id = dst_chain_id .parse() - .map_err(|_| "bad destination chain identifier".to_string())?; + .map_err(|_| format!("bad destination chain ({}) identifier", dst_chain_id))?; let src_chain_id = src_chain_id .parse() - .map_err(|_| "bad source chain identifier".to_string())?; + .map_err(|_| format!("bad source chain ({}) identifier", src_chain_id))?; // Get the source and destination chain configuration let dst_chain_config = config .find_chain(&dst_chain_id) - .ok_or_else(|| "missing destination chain configuration".to_string())?; + .ok_or_else(|| format!("missing destination chain ({}) configuration", dst_chain_id))?; let src_chain_config = config .find_chain(&src_chain_id) - .ok_or_else(|| "missing source chain configuration".to_string())?; + .ok_or_else(|| format!("missing source chain ({}) configuration", src_chain_id))?; Ok((dst_chain_config.clone(), src_chain_config.clone())) } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index b2f5e226c7..315aca24d7 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -52,9 +52,7 @@ macro_rules! conn_open_cmd { let (src_chain_config, dst_chain_config) = match (src_config, dst_config) { (Ok(s), Ok(d)) => (s, d), (_, _) => { - return Output::with_error() - .with_result(json!("invalid options")) - .exit(); + return Output::error(json!("invalid options")).exit(); } }; @@ -71,21 +69,32 @@ macro_rules! conn_open_cmd { ), }; - status_info!("Message ", "{}: {:?}", $dbg_string, opts); + info!("Message {}: {:?}", $dbg_string, opts); - let (src_chain, _) = - ChainRuntime::::spawn(src_chain_config.clone()).unwrap(); - let (dst_chain, _) = - ChainRuntime::::spawn(dst_chain_config.clone()).unwrap(); + let src_chain_res = ChainRuntime::::spawn(src_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; + + let dst_chain_res = ChainRuntime::::spawn(dst_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result = $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(receipt) => Output::with_success().with_result(json!(receipt)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/tx/packet.rs b/relayer-cli/src/commands/tx/packet.rs index 75792484bb..997c9d1100 100644 --- a/relayer-cli/src/commands/tx/packet.rs +++ b/relayer-cli/src/commands/tx/packet.rs @@ -1,5 +1,4 @@ use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use ibc::events::IBCEvent; use ibc::ics24_host::identifier::{ChannelId, ClientId, PortId}; @@ -75,26 +74,39 @@ impl Runnable for TxRawPacketRecvCmd { let opts = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Message", "{:?}", opts); + info!("Message {:?}", opts); + + let src_chain_res = + ChainRuntime::::spawn(opts.packet_src_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; - let (src_chain, _) = - ChainRuntime::::spawn(opts.packet_src_chain_config.clone()).unwrap(); - let (dst_chain, _) = - ChainRuntime::::spawn(opts.packet_dst_chain_config.clone()).unwrap(); + let dst_chain_res = + ChainRuntime::::spawn(opts.packet_dst_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result, Error> = build_and_send_recv_packet_messages(src_chain, dst_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(ev) => Output::with_success().with_result(json!(ev)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(ev) => Output::success(ev).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } @@ -159,26 +171,39 @@ impl Runnable for TxRawPacketAckCmd { let opts = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Message", "{:?}", opts); + info!("Message {:?}", opts); + + let src_chain_res = + ChainRuntime::::spawn(opts.packet_src_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; - let (src_chain, _) = - ChainRuntime::::spawn(opts.packet_src_chain_config.clone()).unwrap(); - let (dst_chain, _) = - ChainRuntime::::spawn(opts.packet_dst_chain_config.clone()).unwrap(); + let dst_chain_res = + ChainRuntime::::spawn(opts.packet_dst_chain_config.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok((handle, _)) => handle, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result, Error> = build_and_send_ack_packet_messages(src_chain, dst_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(ev) => Output::with_success().with_result(json!(ev)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(ev) => Output::success(ev).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/tx/transfer.rs b/relayer-cli/src/commands/tx/transfer.rs index d97ff21871..8d4eeb44c2 100644 --- a/relayer-cli/src/commands/tx/transfer.rs +++ b/relayer-cli/src/commands/tx/transfer.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use abscissa_core::{Command, Options, Runnable}; -use serde_json::json; use tokio::runtime::Runtime as TokioRuntime; use ibc::events::IBCEvent; @@ -85,27 +84,40 @@ impl Runnable for TxRawSendPacketCmd { let opts = match self.validate_options(&config) { Err(err) => { - return Output::with_error().with_result(json!(err)).exit(); + return Output::error(err).exit(); } Ok(result) => result, }; - status_info!("Message", "{:?}", opts); + info!("Message {:?}", opts); let rt = Arc::new(TokioRuntime::new().unwrap()); - let src_chain = - CosmosSDKChain::bootstrap(opts.packet_src_chain_config.clone(), rt.clone()).unwrap(); - let dst_chain = - CosmosSDKChain::bootstrap(opts.packet_dst_chain_config.clone(), rt).unwrap(); + + let src_chain_res = + CosmosSDKChain::bootstrap(opts.packet_src_chain_config.clone(), rt.clone()) + .map_err(|e| Kind::Runtime.context(e)); + let src_chain = match src_chain_res { + Ok(chain) => chain, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; + + let dst_chain_res = CosmosSDKChain::bootstrap(opts.packet_dst_chain_config.clone(), rt) + .map_err(|e| Kind::Runtime.context(e)); + let dst_chain = match dst_chain_res { + Ok(chain) => chain, + Err(e) => { + return Output::error(format!("{}", e)).exit(); + } + }; let res: Result, Error> = build_and_send_transfer_messages(src_chain, dst_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(ev) => Output::with_success().with_result(json!(ev)).exit(), - Err(e) => Output::with_error() - .with_result(json!(format!("{}", e))) - .exit(), + Ok(ev) => Output::success(ev).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), } } } diff --git a/relayer-cli/src/commands/v0.rs b/relayer-cli/src/commands/v0.rs deleted file mode 100644 index c617acf973..0000000000 --- a/relayer-cli/src/commands/v0.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::ops::Deref; - -use abscissa_core::{ - application::fatal_error, error::BoxError, tracing::debug, Command, Options, Runnable, -}; - -use ibc::ics04_channel::channel::Order; - -use relayer::chain::runtime::ChainRuntime; -use relayer::relay::channel_relay; - -use crate::config::Config; -use crate::prelude::*; -use relayer::chain::CosmosSDKChain; - -#[derive(Command, Debug, Options)] -pub struct V0Cmd {} - -impl V0Cmd { - fn cmd(&self) -> Result<(), BoxError> { - let config = app_config().clone(); - debug!("launching 'v0' command"); - v0_task(&config) - } -} - -impl Runnable for V0Cmd { - fn run(&self) { - self.cmd() - .unwrap_or_else(|e| fatal_error(app_reader().deref(), &*e)) - } -} - -pub fn v0_task(config: &Config) -> Result<(), BoxError> { - // Relay for a single channel, first on the first connection in configuration - let conn = &config - .connections - .clone() - .ok_or("No connections configured")?[0]; - - let ordering = Order::default(); // TODO - add to config - let path = conn.paths.clone().ok_or("No paths configured")?[0].clone(); - - let src_chain_config = config - .find_chain(&conn.a_chain) - .cloned() - .ok_or("Configuration for source chain not found")?; - - let dst_chain_config = config - .find_chain(&conn.b_chain) - .cloned() - .ok_or("Configuration for source chain not found")?; - - let (src_chain_handle, _) = ChainRuntime::::spawn(src_chain_config)?; - let (dst_chain_handle, _) = ChainRuntime::::spawn(dst_chain_config)?; - - Ok(channel_relay( - src_chain_handle, - dst_chain_handle, - ordering, - path, - )?) -} diff --git a/relayer-cli/src/components.rs b/relayer-cli/src/components.rs new file mode 100644 index 0000000000..193769a053 --- /dev/null +++ b/relayer-cli/src/components.rs @@ -0,0 +1,40 @@ +use abscissa_core::{Component, FrameworkError}; +use relayer::config::GlobalConfig; +use tracing_subscriber::fmt::{ + format::{Format, Json, JsonFields}, + time::SystemTime, + Formatter, +}; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{reload::Handle, EnvFilter, FmtSubscriber}; + +/// Abscissa component for initializing the `tracing` subsystem +#[derive(Component, Debug)] +pub struct Tracing { + filter_handle: Handle>>, +} + +/// A custom component for parametrizing `tracing` in the relayer. Primarily used for: +/// - customizes the log output level, for filtering the output produced via tracing macros +/// (`debug!`, `info!`, etc.) or abscissa macros (`status_err`, `status_info`, etc.). +/// - enables JSON-formatted output +impl Tracing { + /// Creates a new [`Tracing`] component + pub fn new(cfg: GlobalConfig) -> Result { + let filter = cfg.log_level; + let use_color = false; + + // Construct a tracing subscriber with the supplied filter and enable reloading. + let builder = FmtSubscriber::builder() + .with_env_filter(filter) + .with_ansi(use_color) + .json() + .with_filter_reloading(); + let filter_handle = builder.reload_handle(); + + let subscriber = builder.finish(); + subscriber.init(); + + Ok(Self { filter_handle }) + } +} diff --git a/relayer-cli/src/conclude.rs b/relayer-cli/src/conclude.rs index 17762e12f0..900fa79ddb 100644 --- a/relayer-cli/src/conclude.rs +++ b/relayer-cli/src/conclude.rs @@ -2,18 +2,18 @@ //! from a CLI command. The main use-case for this module is to provide a consistent output for //! queries and transactions. //! -//! The examples below rely on crate-private methods (for this reason, doctests do not compile.) +//! The examples below rely on crate-private methods (for this reason, doctests do not compile). //! They are intended for contributors to crate `relayer-cli`, and _not_ for users of this binary. //! -//! ## Examples: +//! ## Examples on how to use the quick-access constructors: //! -//! - Exit from a query/tx with a string error: +//! - Exit from a query/tx with a `String` error: //! //! ```compile_fail //! let e = String::from("error message"); -//! Output::with_error().with_result(json!(e)).exit(); +//! Output::error(e).exit(); //! // or as an alternative: -//! Output::with_error().with_result(json!("error occurred")).exit(); +//! Output::error(json!("error occurred")).exit(); //! ``` //! //! - Exit from a query/tx with an error of type `anomaly`: @@ -23,14 +23,14 @@ //! //! ```compile_fail //! let e: Error = Kind::Query.into(); -//! Output::with_success().with_result(json!(format!("{}", e))).exit(); +//! Output::error(format!("{}", e)).exit(); //! ``` //! //! - Exit from a query/tx with success: //! //! ```compile_fail //! let cs = ChannelEnd::default(); -//! Output::with_success().with_result(json!(cs)).exit(); +//! Output::success(cs).exit(); //! ``` //! //! - Exit from a query/tx with success and multiple objects in the result: @@ -38,16 +38,18 @@ //! ```compile_fail //! let h = Height::default(); //! let end = ConnectionEnd::default(); -//! Output::with_success().with_result(json!(h)).with_result(end).exit(); +//! Output::success(h).with_result(end).exit(); //! ``` use serde::Serialize; +use tracing::error; /// Functional-style method to exit a program. /// -/// ## Note: See `Output::exit()` for the preferred method of exiting a command. +/// ## Note: See `Output::exit()` for the preferred method of exiting a relayer command. pub fn exit_with(out: Output) { // Handle the output message + // TODO: To unify all relayer output, consider replacing `println` with a `tracing` macro below. println!("{}", serde_json::to_string(&out).unwrap()); // The return code @@ -59,7 +61,9 @@ pub fn exit_with(out: Output) { } /// A CLI output with support for JSON serialization. The only mandatory field is the `status`, -/// which typically signals a success or an error. An optional `result` can be added to an output. +/// which typically signals a success (UNIX process return code `0`) or an error (code `1`). An +/// optional `result` can be added to an output. +/// #[derive(Serialize, Debug)] pub struct Output { /// The return status @@ -71,7 +75,7 @@ pub struct Output { } impl Output { - /// Constructs a new `Output`. + /// Constructs a new `Output` with the provided `status` and an empty `result`. pub fn new(status: Status) -> Self { Output { status, @@ -79,12 +83,12 @@ impl Output { } } - /// Quick-access to a constructor that returns a new `Output` having a `Success` status. + /// Constructor that returns a new `Output` having a `Success` status and empty `result`. pub fn with_success() -> Self { Output::new(Status::Success) } - /// Quick-access to a constructor that returns a new `Output` having an `Error` status. + /// Constructor that returns a new `Output` having an `Error` status and empty `result`. pub fn with_error() -> Self { Output::new(Status::Error) } @@ -96,6 +100,40 @@ impl Output { self } + /// Quick-access constructor for an output signalling a success `status` and tagged with the + /// input `res`. + pub fn success(res: impl Serialize + std::fmt::Debug) -> Self { + let mut out = Output::with_success(); + out.result.push(Self::serialize_result(res)); + out + } + + /// Quick-access constructor for an output signalling a error `status` and tagged with the + /// input `res`. + pub fn error(res: impl Serialize + std::fmt::Debug) -> Self { + let mut out = Output::with_error(); + out.result.push(Self::serialize_result(res)); + out + } + + // Helper to serialize a result into a `serde_json::Value`. + fn serialize_result(res: impl Serialize + std::fmt::Debug) -> serde_json::Value { + let last_resort = format!("{:?}", res); + + match serde_json::to_value(res) { + Ok(json_val) => json_val, + Err(e) => { + // Signal the serialization error + error!( + "Output constructor failed with non-recoverable error {} for input {}", + e, last_resort + ); + // Package the result with the infallible `Debug` instead of `JSON` + serde_json::Value::String(last_resort) + } + } + } + /// Exits from the process with the current output. Convenience wrapper over `exit_with`. pub fn exit(self) { exit_with(self); diff --git a/relayer-cli/src/error.rs b/relayer-cli/src/error.rs index f77b0e2324..1630d5697d 100644 --- a/relayer-cli/src/error.rs +++ b/relayer-cli/src/error.rs @@ -21,6 +21,10 @@ pub enum Kind { #[error("query error")] Query, + /// Error while spawning the runtime + #[error("chain runtime/handle error")] + Runtime, + /// Error during transaction submission #[error("tx error")] Tx, diff --git a/relayer-cli/src/lib.rs b/relayer-cli/src/lib.rs index 5a1a4f76f3..58e9f0ac04 100644 --- a/relayer-cli/src/lib.rs +++ b/relayer-cli/src/lib.rs @@ -18,6 +18,7 @@ pub mod application; pub mod commands; +mod components; pub(crate) mod conclude; pub mod config; pub mod error; diff --git a/relayer-cli/tests/fixtures/two_chains.toml b/relayer-cli/tests/fixtures/two_chains.toml index 0daf7ce18c..b3bd13fa66 100644 --- a/relayer-cli/tests/fixtures/two_chains.toml +++ b/relayer-cli/tests/fixtures/two_chains.toml @@ -1,6 +1,7 @@ [global] timeout = '10s' strategy = 'naive' +log_level = 'error' # valid options: 'error', 'warn', 'info', 'debug', 'trace' [[chains]] id = 'ibc-0' diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 3dbb05cbe2..89f0c31299 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -123,7 +123,7 @@ impl CosmosSDKChain { Ok(self .block_on(self.rpc_client().genesis()) - .map_err(|e| Kind::Rpc.context(e))? + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))? .consensus_params) } @@ -223,7 +223,7 @@ impl CosmosSDKChain { let response = self .block_on(broadcast_tx_commit(self, txraw_buf)) - .map_err(|e| Kind::Rpc.context(e))?; + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; let res = tx_result_to_event(response)?; @@ -250,8 +250,8 @@ impl Chain for CosmosSDKChain { type ClientState = ClientState; fn bootstrap(config: ChainConfig, rt: Arc) -> Result { - let rpc_client = - HttpClient::new(config.rpc_addr.clone()).map_err(|e| Kind::Rpc.context(e))?; + let rpc_client = HttpClient::new(config.rpc_addr.clone()) + .map_err(|e| Kind::Rpc(config.rpc_addr.clone()).context(e))?; // Initialize key store and load key let key_store = KeyRing::init(StoreBackend::Test, config.clone()) @@ -364,17 +364,34 @@ impl Chain for CosmosSDKChain { Ok(res) } + fn build_client_state(&self, height: ICSHeight) -> Result { + // Build the client state. + Ok(ibc::ics07_tendermint::client_state::ClientState::new( + self.id().to_string(), + self.config.trust_threshold, + self.config.trusting_period, + self.unbonding_period()?, + Duration::from_millis(3000), // TODO - get it from src config when avail + height, + ICSHeight::zero(), + vec!["upgrade".to_string(), "upgradedIBCState".to_string()], + false, + false, + ) + .map_err(|e| Kind::BuildClientStateFailure.context(e))?) + } + /// Query the latest height the chain is at via a RPC query fn query_latest_height(&self) -> Result { crate::time!("query_latest_height"); let status = self .block_on(self.rpc_client().status()) - .map_err(|e| Kind::Rpc.context(e))?; + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; if status.sync_info.catching_up { fail!( - Kind::LightClient, + Kind::LightClientSupervisor(self.config.id.clone()), "node at {} running chain {} not caught up", self.config().rpc_addr, self.config().id, @@ -480,23 +497,6 @@ impl Chain for CosmosSDKChain { )) } - fn build_client_state(&self, height: ICSHeight) -> Result { - // Build the client state. - Ok(ibc::ics07_tendermint::client_state::ClientState::new( - self.id().to_string(), - self.config.trust_threshold, - self.config.trusting_period, - self.unbonding_period()?, - Duration::from_millis(3000), // TODO - get it from src config when avail - height, - ICSHeight::zero(), - vec!["upgrade".to_string(), "upgradedIBCState".to_string()], - false, - false, - ) - .map_err(|e| Kind::BuildClientStateFailure.context(e))?) - } - fn build_consensus_state( &self, light_block: Self::LightBlock, @@ -552,6 +552,7 @@ impl Chain for CosmosSDKChain { Ok(key) } + /// Queries the packet commitment hashes associated with a channel. fn query_packet_commitments( &self, @@ -816,11 +817,13 @@ async fn abci_query( .rpc_client() .abci_query(Some(path), data.into_bytes(), height, prove) .await - .map_err(|e| Kind::Rpc.context(e))?; + .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; if !response.code.is_ok() { // Fail with response log. - return Err(Kind::Rpc.context(response.log.to_string()).into()); + return Err(Kind::Rpc(chain.config.rpc_addr.clone()) + .context(response.log.to_string()) + .into()); } if prove && response.proof.is_none() { @@ -848,7 +851,7 @@ async fn broadcast_tx_commit( .rpc_client() .broadcast_tx_commit(data.into()) .await - .map_err(|e| Kind::Rpc.context(e))?; + .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; Ok(response) } diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 07927aded0..f438395fc3 100644 --- a/relayer/src/chain/mock.rs +++ b/relayer/src/chain/mock.rs @@ -105,7 +105,7 @@ impl Chain for MockChain { // Use the ICS18Context interface to submit the set of messages. self.context .send(proto_msgs) - .map_err(|e| Kind::Rpc.context(e))?; + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; // TODO FIX tests with this Ok(vec![]) @@ -255,7 +255,7 @@ pub mod test_utils { pub fn get_basic_chain_config(id: &str) -> ChainConfig { ChainConfig { id: ChainId::from_str(id).unwrap(), - rpc_addr: "35.192.61.41:26656".parse().unwrap(), + rpc_addr: "127.0.0.1:26656".parse().unwrap(), grpc_addr: "".to_string(), account_prefix: "".to_string(), key_name: "".to_string(), diff --git a/relayer/src/config.rs b/relayer/src/config.rs index a998ea2177..54483f41eb 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -10,11 +10,11 @@ use std::{ }; use serde_derive::{Deserialize, Serialize}; - -use ibc::ics24_host::identifier::{ChainId, PortId}; use tendermint::{net, Hash}; use tendermint_light_client::types::{Height, PeerId, TrustThreshold}; +use ibc::ics24_host::identifier::{ChainId, PortId}; + use crate::error; /// Defaults for various fields @@ -77,8 +77,13 @@ impl Default for Strategy { pub struct GlobalConfig { #[serde(default = "default::timeout", with = "humantime_serde")] pub timeout: Duration, + #[serde(default)] pub strategy: Strategy, + + /// All valid log levels, as defined in tracing: + /// https://docs.rs/tracing-core/0.1.17/tracing_core/struct.Level.html + pub log_level: String, } impl Default for GlobalConfig { @@ -86,6 +91,7 @@ impl Default for GlobalConfig { Self { timeout: default::timeout(), strategy: Strategy::default(), + log_level: "info".to_string(), } } } diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 8c93fb7ba2..10e7136219 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -1,9 +1,11 @@ //! This module defines the various errors that be raised in the relayer. use anomaly::{BoxError, Context}; -use ibc::ics24_host::identifier::{ChannelId, ConnectionId}; +use tendermint::net; use thiserror::Error; +use ibc::ics24_host::identifier::{ChainId, ChannelId, ConnectionId}; + /// An error that can be raised by the relayer. pub type Error = anomaly::Error; @@ -23,16 +25,20 @@ pub enum Kind { Config, /// RPC error (typically raised by the RPC client or the RPC requester) - #[error("RPC error")] - Rpc, + #[error("RPC error to endpoint {0}")] + Rpc(net::Address), /// GRPC error (typically raised by the GRPC client or the GRPC requester) #[error("GRPC error")] Grpc, - /// Light client error, typically raised by a `Client` - #[error("Light client error")] - LightClient, + /// Light client supervisor error + #[error("Light client supervisor error for chain id {0}")] + LightClientSupervisor(ChainId), + + /// Light client instance error, typically raised by a `Client` + #[error("Light client instance error for rpc address {0}")] + LightClientInstance(String), /// Trusted store error, raised by instances of `Store` #[error("Store error")] diff --git a/relayer/src/event/monitor.rs b/relayer/src/event/monitor.rs index 0735d5ac71..f8999452aa 100644 --- a/relayer/src/event/monitor.rs +++ b/relayer/src/event/monitor.rs @@ -5,14 +5,13 @@ use crossbeam_channel as channel; use futures::stream::StreamExt; use futures::{stream::select_all, Stream}; use itertools::Itertools; +use tendermint::{block::Height, net}; +use tendermint_rpc::{query::EventType, query::Query, SubscriptionClient, WebSocketClient}; use tokio::runtime::Runtime as TokioRuntime; - use tokio::task::JoinHandle; use tracing::{debug, error, info}; use ibc::{events::IBCEvent, ics24_host::identifier::ChainId}; -use tendermint::{block::Height, net}; -use tendermint_rpc::{query::EventType, query::Query, SubscriptionClient, WebSocketClient}; use crate::error::{Error, Kind}; @@ -58,9 +57,9 @@ impl EventMonitor { let websocket_addr = rpc_addr.clone(); let (websocket_client, websocket_driver) = rt.block_on(async move { - WebSocketClient::new(websocket_addr) + WebSocketClient::new(websocket_addr.clone()) .await - .map_err(|e| Kind::Rpc.context(e)) + .map_err(|e| Kind::Rpc(websocket_addr).context(e)) })?; let websocket_driver_handle = rt.spawn(websocket_driver.run()); diff --git a/relayer/src/light_client/tendermint.rs b/relayer/src/light_client/tendermint.rs index 6ce555a64b..487cb766e2 100644 --- a/relayer/src/light_client/tendermint.rs +++ b/relayer/src/light_client/tendermint.rs @@ -11,31 +11,39 @@ use crate::{ config::{ChainConfig, LightClientConfig, StoreConfig}, error, }; +use ibc::ics24_host::identifier::ChainId; pub struct LightClient { handle: Box, + chain_id: ChainId, } impl super::LightClient for LightClient { fn latest_trusted(&self) -> Result, error::Error> { - self.handle - .latest_trusted() - .map_err(|e| error::Kind::LightClient.context(e).into()) + self.handle.latest_trusted().map_err(|e| { + error::Kind::LightClientSupervisor(self.chain_id.clone()) + .context(e) + .into() + }) } fn verify_to_latest(&self) -> Result { - self.handle - .verify_to_highest() - .map_err(|e| error::Kind::LightClient.context(e).into()) + self.handle.verify_to_highest().map_err(|e| { + error::Kind::LightClientSupervisor(self.chain_id.clone()) + .context(e) + .into() + }) } fn verify_to_target(&self, height: ibc::Height) -> Result { let height = TMHeight::try_from(height.revision_height) .map_err(|e| error::Kind::InvalidHeight.context(e))?; - self.handle - .verify_to_target(height) - .map_err(|e| error::Kind::LightClient.context(e).into()) + self.handle.verify_to_target(height).map_err(|e| { + error::Kind::LightClientSupervisor(self.chain_id.clone()) + .context(e) + .into() + }) } fn get_minimal_set( @@ -48,9 +56,10 @@ impl super::LightClient for LightClient { } impl LightClient { - pub fn new(handle: impl Handle + 'static) -> Self { + fn new(handle: impl Handle + 'static, chain_id: ChainId) -> Self { Self { handle: Box::new(handle), + chain_id, } } @@ -61,7 +70,7 @@ impl LightClient { let supervisor = build_supervisor(&chain_config, reset)?; let handle = supervisor.handle(); - Ok((Self::new(handle), supervisor)) + Ok((Self::new(handle, chain_config.id.clone()), supervisor)) } } @@ -71,11 +80,13 @@ fn build_instance( reset: bool, ) -> Result { let rpc_client = rpc::HttpClient::new(config.address.clone()) - .map_err(|e| error::Kind::LightClient.context(e))?; + .map_err(|e| error::Kind::LightClientInstance(config.address.to_string()).context(e))?; let store: Box = match &config.store { StoreConfig::Disk { path } => { - let db = sled::open(path).map_err(|e| error::Kind::LightClient.context(e))?; + let db = sled::open(path).map_err(|e| { + error::Kind::LightClientInstance(config.address.to_string()).context(e) + })?; Box::new(store::sled::SledStore::new(db)) } StoreConfig::Memory { .. } => Box::new(store::memory::MemoryStore::new()), @@ -94,7 +105,7 @@ fn build_instance( } else { builder.trust_from_store() } - .map_err(|e| error::Kind::LightClient.context(e))?; + .map_err(|e| error::Kind::LightClientInstance(config.address.to_string()).context(e))?; Ok(builder.build()) } @@ -107,11 +118,13 @@ fn build_supervisor(config: &ChainConfig, reset: bool) -> Result Result