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

config auto now generates a config file even when it encounters an error #3466

Merged
merged 19 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: {:?}",
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

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