Skip to content

Commit

Permalink
config auto now generates a config file even when it encounters an …
Browse files Browse the repository at this point in the history
…error (#3466)

* Stub out code flow

* Stub out code flow

* Change return type of `hermes_cofig` fn

* Define ConfigAutoError type

* Add some printlns

* Change `get_configs` return type

* Change AutoCmd::run

* Get it to compile

* Fix false reporting of missing chain configs

* Change get_data_from_handles

* Get it working

* Remove some debugging code

* Cargo fmt

* Update `get_configs` doc comment

* Update gas price warning in guide

* Cargo fmt
  • Loading branch information
seanchen1991 authored and romac committed Jul 14, 2023
1 parent 71d5f30 commit c0ed9a3
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 117 deletions.
189 changes: 108 additions & 81 deletions crates/relayer-cli/src/chain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ where
))
}

/// Fetches the specified resources from the Cosmos chain registry, using the specified commit hash
/// if it is provided. Fetching is done in a concurrent fashion by spawning a task for each resource.
/// Returns a vector of handles that need to be awaited in order to access the fetched data, or the
/// error that occurred while fetching.
async fn get_handles<T: Fetchable + Send + 'static>(
resources: &[String],
commit: &Option<String>,
Expand All @@ -211,21 +215,23 @@ async fn get_handles<T: Fetchable + Send + 'static>(
handles
}

/// Given a vector of handles, awaits them and returns a vector of results. Any errors
/// that occurred are mapped to a `RegistryError`.
async fn get_data_from_handles<T>(
handles: Vec<JoinHandle<Result<T, RegistryError>>>,
error_task: &str,
) -> Result<Vec<T>, RegistryError> {
let data_array: Result<Vec<_>, JoinError> = join_all(handles).await.into_iter().collect();
let data_array: Result<Vec<T>, RegistryError> = data_array
.map_err(|e| RegistryError::join_error(error_task.to_string(), e))?
) -> Result<Vec<Result<T, RegistryError>>, RegistryError> {
join_all(handles)
.await
.into_iter()
.collect();

data_array
.collect::<Result<Vec<_>, JoinError>>()
.map_err(|e| RegistryError::join_error(error_task.to_string(), e))
}

/// Generates a `Vec<ChainConfig>` for a slice of chain names by fetching data from
/// <https://github.com/cosmos/chain-registry>. Gas settings are set to default values.
/// Fetches a list of ChainConfigs specified by the given slice of chain names. These
/// configs are fetched from <https://github.com/cosmos/chain-registry>. The `default_gas`
/// and `max_gas` parameters set to default values. The `gas_price` parameter is set to
/// the average gas price for the chain listed in the chain registry.
///
/// # Arguments
///
Expand All @@ -242,7 +248,7 @@ async fn get_data_from_handles<T>(
pub async fn get_configs(
chains: &[String],
commit: Option<String>,
) -> Result<Vec<ChainConfig>, RegistryError> {
) -> Result<Vec<Result<ChainConfig, RegistryError>>, RegistryError> {
let n = chains.len();

if n == 0 {
Expand All @@ -267,11 +273,20 @@ pub async fn get_configs(
}

// Collect data from the spawned tasks
let chain_data_array =
let chain_data_results =
get_data_from_handles::<ChainData>(chain_data_handle, "chain_data_join").await?;
let asset_lists =
let asset_list_results =
get_data_from_handles::<AssetList>(asset_lists_handle, "asset_handle_join").await?;

let chain_data_array: Vec<ChainData> = chain_data_results
.into_iter()
.filter_map(|chain_data| chain_data.ok())
.collect();
let asset_lists: Vec<AssetList> = asset_list_results
.into_iter()
.filter_map(|asset_list| asset_list.ok())
.collect();

let path_data: Result<Vec<_>, JoinError> = join_all(path_handles).await.into_iter().collect();
let path_data: Vec<IBCPath> = path_data
.map_err(|e| RegistryError::join_error("path_handle_join".to_string(), e))?
Expand Down Expand Up @@ -322,10 +337,16 @@ mod tests {
// chain-registry repository: https://github.com/cosmos/chain-registry/tree/master/_IBC
async fn should_have_no_filter(test_chains: &[String]) -> Result<(), RegistryError> {
let configs = get_configs(test_chains, Some(TEST_COMMIT.to_owned())).await?;

for config in configs {
match config.packet_filter.channel_policy {
ChannelPolicy::AllowAll => {}
_ => panic!("PacketFilter not allowed"),
match config {
Ok(config) => {
assert_eq!(config.packet_filter.channel_policy, ChannelPolicy::AllowAll);
}
Err(e) => panic!(
"Encountered an unexpected error in chain registry test: {}",
e
),
}
}

Expand All @@ -345,73 +366,79 @@ mod tests {
let configs = get_configs(test_chains, Some(TEST_COMMIT.to_owned())).await?;

for config in configs {
match config.packet_filter.channel_policy {
ChannelPolicy::Allow(channel_filter) => {
if config.id.as_str().contains("cosmoshub") {
assert!(channel_filter.is_exact());

let cosmoshub_juno = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-207").unwrap(),
);

let cosmoshub_osmosis = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-141").unwrap(),
);

assert!(channel_filter.matches(cosmoshub_juno));
assert!(channel_filter.matches(cosmoshub_osmosis));
assert!(channel_filter.len() == 2);
} else if config.id.as_str().contains("juno") {
assert!(channel_filter.is_exact());

let juno_cosmoshub = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-1").unwrap(),
);

let juno_osmosis_1 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-0").unwrap(),
);

let juno_osmosis_2 = (
&PortId::from_str("wasm.juno1v4887y83d6g28puzvt8cl0f3cdhd3y6y9mpysnsp3k8krdm7l6jqgm0rkn").unwrap(),
&ChannelId::from_str("channel-47").unwrap()
);

assert!(channel_filter.matches(juno_cosmoshub));
assert!(channel_filter.matches(juno_osmosis_1));
assert!(channel_filter.matches(juno_osmosis_2));
assert!(channel_filter.len() == 3);
} else if config.id.as_str().contains("osmosis") {
assert!(channel_filter.is_exact());

let osmosis_cosmoshub = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-0").unwrap(),
);

let osmosis_juno_1 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-42").unwrap(),
);

let osmosis_juno_2 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-169").unwrap(),
);

assert!(channel_filter.matches(osmosis_cosmoshub));
assert!(channel_filter.matches(osmosis_juno_1));
assert!(channel_filter.matches(osmosis_juno_2));
assert!(channel_filter.len() == 3);
} else {
panic!("Unknown chain");
match config {
Ok(config) => match config.packet_filter.channel_policy {
ChannelPolicy::Allow(channel_filter) => {
if config.id.as_str().contains("cosmoshub") {
assert!(channel_filter.is_exact());

let cosmoshub_juno = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-207").unwrap(),
);

let cosmoshub_osmosis = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-141").unwrap(),
);

assert!(channel_filter.matches(cosmoshub_juno));
assert!(channel_filter.matches(cosmoshub_osmosis));
assert!(channel_filter.len() == 2);
} else if config.id.as_str().contains("juno") {
assert!(channel_filter.is_exact());

let juno_cosmoshub = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-1").unwrap(),
);

let juno_osmosis_1 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-0").unwrap(),
);

let juno_osmosis_2 = (
&PortId::from_str("wasm.juno1v4887y83d6g28puzvt8cl0f3cdhd3y6y9mpysnsp3k8krdm7l6jqgm0rkn").unwrap(),
&ChannelId::from_str("channel-47").unwrap()
);

assert!(channel_filter.matches(juno_cosmoshub));
assert!(channel_filter.matches(juno_osmosis_1));
assert!(channel_filter.matches(juno_osmosis_2));
assert!(channel_filter.len() == 3);
} else if config.id.as_str().contains("osmosis") {
assert!(channel_filter.is_exact());

let osmosis_cosmoshub = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-0").unwrap(),
);

let osmosis_juno_1 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-42").unwrap(),
);

let osmosis_juno_2 = (
&PortId::from_str("transfer").unwrap(),
&ChannelId::from_str("channel-169").unwrap(),
);

assert!(channel_filter.matches(osmosis_cosmoshub));
assert!(channel_filter.matches(osmosis_juno_1));
assert!(channel_filter.matches(osmosis_juno_2));
assert!(channel_filter.len() == 3);
} else {
panic!("Unknown chain");
}
}
}
_ => panic!("PacketFilter not allowed"),
_ => panic!("PacketFilter not allowed"),
},
Err(e) => panic!(
"Encountered an unexpected error in chain registry test: {}",
e
),
}
}

Expand Down
114 changes: 79 additions & 35 deletions crates/relayer-cli/src/commands/config/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::conclude::Output;
use ibc_relayer::config::{store, ChainConfig, Config};
use ibc_relayer::keyring::list_keys;

use std::collections::HashSet;
use std::path::PathBuf;
use tracing::{info, warn};

Expand Down Expand Up @@ -75,59 +76,102 @@ impl Runnable for AutoCmd {
// Assert that for every chain, a key name is provided
let runtime = tokio::runtime::Runtime::new().unwrap();

// Extract keys and sort chains by name
let names_and_keys = extract_chains_and_keys(&self.chain_names);
let sorted_names = names_and_keys
.iter()
.map(|n| &n.0)
.cloned()
.collect::<Vec<_>>();

let sorted_names_set: HashSet<String> = HashSet::from_iter(sorted_names.iter().cloned());

let commit = self.commit.clone();

// Extract keys and sort chains by name
// Fetch chain configs from the chain registry
info!("Fetching configuration for chains: {sorted_names:?}");

match runtime.block_on(get_configs(&sorted_names, commit)) {
Ok(mut chain_configs) => {
let configs_and_keys = chain_configs
.iter_mut()
.zip(names_and_keys.iter().map(|n| &n.1).cloned());

for (chain_config, key_option) in configs_and_keys {
// If a key is provided, use it
if let Some(key_name) = key_option {
info!("{}: uses key \"{}\"", &chain_config.id, &key_name);
chain_config.key_name = key_name;
} else {
// Otherwise, find the key in the keystore
let chain_id = &chain_config.id;
let key = find_key(chain_config);
if let Some(key) = key {
info!("{}: uses key '{}'", &chain_id, &key);
chain_config.key_name = key;
} else {
// If no key is found, warn the user and continue
warn!("No key found for chain: {}", chain_id);
}
}
let config_results = runtime.block_on(get_configs(&sorted_names, commit));

if let Err(e) = config_results {
let config = Config::default();

match store(&config, &self.path) {
Ok(_) => Output::error(format!(
"An error occurred while generating the chain config file: {}
A default config file has been written at '{}'",
e,
self.path.display(),
))
.exit(),
Err(e) => Output::error(format!(
"An error occurred while attempting to write the config file: {}",
e
))
.exit(),
}
};

let mut chain_configs: Vec<ChainConfig> = config_results
.unwrap()
.into_iter()
.filter_map(|r| r.ok())
.collect();

// Determine which chains were not fetched
let fetched_chains_set = HashSet::from_iter(chain_configs.iter().map(|c| c.id.name()));
let missing_chains_set: HashSet<_> =
sorted_names_set.difference(&fetched_chains_set).collect();

let configs_and_keys = chain_configs
.iter_mut()
.zip(names_and_keys.iter().map(|n| &n.1).cloned());

for (chain_config, key_option) in configs_and_keys {
// If a key is provided, use it
if let Some(key_name) = key_option {
info!("{}: uses key \"{}\"", &chain_config.id, &key_name);
chain_config.key_name = key_name;
} else {
// Otherwise, find the key in the keystore
let chain_id = &chain_config.id;
let key = find_key(chain_config);
if let Some(key) = key {
info!("{}: uses key '{}'", &chain_id, &key);
chain_config.key_name = key;
} else {
// If no key is found, warn the user and continue
warn!("No key found for chain: {}", chain_id);
}
}
}

let config = Config {
chains: chain_configs,
..Config::default()
};
let config = Config {
chains: chain_configs,
..Config::default()
};

match store(&config, &self.path) {
Ok(_) => Output::success_msg(format!(
match store(&config, &self.path) {
Ok(_) => {
if missing_chains_set.is_empty() {
Output::success_msg(format!(
"Config file written successfully at '{}'",
self.path.display()
))
.exit(),
Err(e) => Output::error(e).exit(),
.exit()
} else {
Output::success_msg(format!(
"Config file written successfully at '{}'.
However, configurations for the following chains were not able to be generated: {:?}",
self.path.display(),
missing_chains_set,
))
.exit()
}
}
Err(e) => Output::error(e).exit(),
Err(e) => Output::error(format!(
"An error occurred while attempting to write the config file: {}",
e
))
.exit(),
}
}
}
Expand Down
Loading

0 comments on commit c0ed9a3

Please sign in to comment.