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

Add bulk upgrade CLI #1077

Merged
merged 16 commits into from
Jul 19, 2021
Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

- [ibc]
- Enable `pub` access to verification methods of ICS 03 & 04 ([#1198])

- [ibc-relayer-cli]
- Added `upgrade-clients` CLI ([#763])

hu55a1n1 marked this conversation as resolved.
Show resolved Hide resolved
### IMPROVEMENTS

Expand All @@ -31,6 +34,7 @@
## v0.6.0
*July 12th, 2021*


Many thanks to Fraccaroli Gianmarco (@Fraccaman) for helping us improve the
reliability of Hermes ([#697]).

Expand Down Expand Up @@ -111,6 +115,7 @@ The full list of changes is described below.
[#69]: https://github.com/informalsystems/ibc-rs/issues/69
[#600]: https://github.com/informalsystems/ibc-rs/issues/600
[#697]: https://github.com/informalsystems/ibc-rs/issues/697
[#763]: https://github.com/informalsystems/ibc-rs/issues/763
[#1062]: https://github.com/informalsystems/ibc-rs/issues/1062
[#1117]: https://github.com/informalsystems/ibc-rs/issues/1117
[#1057]: https://github.com/informalsystems/ibc-rs/issues/1057
Expand Down
22 changes: 22 additions & 0 deletions docs/architecture/adr-006-hermes-v0.2-usecases.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ identifier `<client-id>` with new consensus state from up-to-date headers.
Hermes will automatically infer the target chain of this client from
the [client state][client-state].

- Upgrade a client:

```
upgrade client <host-chain-id> <client-id>
```

**Details:**
Submits a transaction to chain id `<host-chain-id>` to upgrade the client having
identifier `<client-id>`.
Hermes will automatically infer the target chain of this client from
the [client state][client-state].

- Upgrade all clients that target a specific chain:

```
upgrade clients <target-chain-id>
hu55a1n1 marked this conversation as resolved.
Show resolved Hide resolved
```

**Details:**
Submits a transaction to upgrade clients of all chains in the config that target
chain id `<target-chain-id>`.

##### Create New Connection

- Minimal invocation: this will create the connection from scratch, using
Expand Down
8 changes: 7 additions & 1 deletion relayer-cli/src/commands/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
use abscissa_core::{config::Override, Command, Help, Options, Runnable};
use ibc_relayer::config::Config;

use crate::commands::tx::client::{TxCreateClientCmd, TxUpdateClientCmd, TxUpgradeClientCmd};
use crate::commands::tx::client::{
TxCreateClientCmd, TxUpdateClientCmd, TxUpgradeClientCmd, TxUpgradeClientsCmd,
};

mod channel;
pub(crate) mod client;
Expand Down Expand Up @@ -42,6 +44,10 @@ pub enum TxRawCommands {
#[options(help = "Upgrade the specified client on destination chain")]
UpgradeClient(TxUpgradeClientCmd),

/// The `tx raw upgrade-clients` subcommand. Submits a MsgUpgradeClient in a transaction to multiple chains.
#[options(help = "Upgrade all IBC clients that target a specific chain")]
UpgradeClients(TxUpgradeClientsCmd),

/// The `tx raw conn-init` subcommand
#[options(help = "Initialize a connection (ConnectionOpenInit)")]
ConnInit(connection::TxRawConnInitCmd),
Expand Down
153 changes: 149 additions & 4 deletions relayer-cli/src/commands/tx/client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use abscissa_core::{Command, Options, Runnable};
use std::fmt;

use abscissa_core::{config, Command, Options, Runnable};

use ibc::events::IbcEvent;
use ibc::ics02_client::client_state::ClientState;
use ibc::ics24_host::identifier::{ChainId, ClientId};
use ibc_proto::ibc::core::client::v1::QueryClientStatesRequest;
use ibc_relayer::chain::handle::ChainHandle;
use ibc_relayer::foreign_client::ForeignClient;

use crate::application::app_config;
use crate::application::{app_config, CliApp};
use crate::cli_utils::{spawn_chain_runtime, ChainHandlePair};
use crate::conclude::{exit_with_unrecoverable_error, Output};
use crate::error::{Error, Kind};
Expand Down Expand Up @@ -84,7 +88,7 @@ impl Runnable for TxUpdateClientCmd {
"Query of client '{}' on chain '{}' failed with error: {}",
self.dst_client_id, self.dst_chain_id, e
))
.exit()
.exit();
}
};

Expand Down Expand Up @@ -143,7 +147,7 @@ impl Runnable for TxUpgradeClientCmd {
"Query of client '{}' on chain '{}' failed with error: {}",
self.client_id, self.chain_id, e
))
.exit()
.exit();
}
};

Expand All @@ -163,3 +167,144 @@ impl Runnable for TxUpgradeClientCmd {
}
}
}

#[derive(Clone, Command, Debug, Options)]
pub struct TxUpgradeClientsCmd {
#[options(
free,
required,
help = "identifier of the chain that underwent an upgrade; all clients targeting this chain will be upgraded"
)]
src_chain_id: ChainId,
}

impl Runnable for TxUpgradeClientsCmd {
fn run(&self) {
let config = app_config();
let src_chain = match spawn_chain_runtime(&config, &self.src_chain_id) {
Ok(handle) => handle,
Err(e) => return Output::error(format!("{}", e)).exit(),
};

let results = config
.chains
.iter()
.filter_map(|chain| {
(self.src_chain_id != chain.id)
.then(|| self.upgrade_clients_for_chain(&config, src_chain.clone(), &chain.id))
})
.collect();

let output = OutputBuffer(results);
match output.into_result() {
Ok(events) => Output::success(events).exit(),
Err(e) => Output::error(e).exit(),
}
}
}

impl TxUpgradeClientsCmd {
fn upgrade_clients_for_chain(
&self,
config: &config::Reader<CliApp>,
src_chain: Box<dyn ChainHandle>,
dst_chain_id: &ChainId,
) -> UpgradeClientsForChainResult {
let dst_chain = spawn_chain_runtime(&config, dst_chain_id)?;

let req = QueryClientStatesRequest {
pagination: ibc_proto::cosmos::base::query::pagination::all(),
};
let outputs = dst_chain
.query_clients(req)
.map_err(|e| Kind::Query.context(e))?
.into_iter()
.filter_map(|c| (self.src_chain_id == c.client_state.chain_id()).then(|| c.client_id))
.map(|id| TxUpgradeClientsCmd::upgrade_client(id, dst_chain.clone(), src_chain.clone()))
.collect();

Ok(outputs)
}

fn upgrade_client(
client_id: ClientId,
dst_chain: Box<dyn ChainHandle>,
src_chain: Box<dyn ChainHandle>,
) -> Result<Vec<IbcEvent>, Error> {
let client = ForeignClient::restore(client_id, dst_chain.clone(), src_chain.clone());
client.upgrade().map_err(|e| Kind::Query.context(e).into())
}
}

type UpgradeClientResult = Result<Vec<IbcEvent>, Error>;
type UpgradeClientsForChainResult = Result<Vec<UpgradeClientResult>, Error>;

struct OutputBuffer(Vec<UpgradeClientsForChainResult>);

impl fmt::Display for OutputBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn sep<'a>(pos: usize, len: usize, other: &'a str, last: &'a str) -> &'a str {
if pos != len - 1 {
other
} else {
last
}
}

let outer_results = &self.0;
writeln!(f, ".")?;
for (o, outer_result) in outer_results.iter().enumerate() {
write!(f, "{}", sep(o, outer_results.len(), "├─", "└─"))?;
match outer_result {
Ok(inner_results) => {
writeln!(f, ".")?;
for (i, inner_result) in inner_results.iter().enumerate() {
write!(
f,
"{} {} ",
sep(o, outer_results.len(), "│", " "),
sep(i, inner_results.len(), "├─", "└─"),
)?;
match inner_result {
Ok(events) => writeln!(f, "{:#?}", events)?,
Err(e) => writeln!(f, "{}", e.to_string())?,
}
}
}
Err(e) => writeln!(f, " {}", e.to_string())?,
}
}
Ok(())
}
}

impl OutputBuffer {
fn into_result(self) -> Result<Vec<Vec<IbcEvent>>, Self> {
let mut all_events = vec![];
let mut has_err = false;
'outer: for outer_result in &self.0 {
match outer_result {
Ok(inner_results) => {
for inner_result in inner_results {
match inner_result {
Ok(events) => all_events.push(events.clone()),
Err(_) => {
has_err = true;
break 'outer;
}
}
}
}
Err(_) => {
has_err = true;
break 'outer;
}
}
}
if has_err {
Err(self)
} else {
Ok(all_events)
}
}
}
6 changes: 5 additions & 1 deletion relayer-cli/src/commands/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use abscissa_core::{Command, Help, Options, Runnable};

use crate::commands::tx::client::TxUpgradeClientCmd;
use crate::commands::tx::client::{TxUpgradeClientCmd, TxUpgradeClientsCmd};

#[derive(Command, Debug, Options, Runnable)]
pub enum UpgradeCmds {
Expand All @@ -13,4 +13,8 @@ pub enum UpgradeCmds {
/// Subcommand for upgrading a `client`
#[options(help = "Upgrade an IBC client")]
Client(TxUpgradeClientCmd),

/// Subcommand for upgrading all `client`s that target specified chain
#[options(help = "Upgrade all IBC clients that target a specific chain")]
Clients(TxUpgradeClientsCmd),
}
28 changes: 15 additions & 13 deletions relayer/src/foreign_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ pub enum ForeignClientError {
#[error("cannot run misbehaviour: {0}")]
MisbehaviourExit(String),

#[error("failed while trying to upgrade client id {0} with error: {1}")]
ClientUpgrade(ClientId, String),
#[error("failed while trying to upgrade client id {0} hosted on chain id {1} with error: {2}")]
ClientUpgrade(ClientId, ChainId, String),
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -179,6 +179,7 @@ impl ForeignClient {
let src_height = self.src_chain.query_latest_height().map_err(|e| {
ForeignClientError::ClientUpgrade(
self.id.clone(),
self.dst_chain.id(),
format!(
"failed while querying src chain ({}) for latest height: {}",
self.src_chain.id(),
Expand All @@ -198,6 +199,7 @@ impl ForeignClient {
.map_err(|e| {
ForeignClientError::ClientUpgrade(
self.id.clone(),
self.dst_chain.id(),
format!(
"failed while fetching from chain {} the upgraded client state: {}",
self.src_chain.id(),
Expand All @@ -211,9 +213,12 @@ impl ForeignClient {
let (consensus_state, proof_upgrade_consensus_state) = self
.src_chain
.query_upgraded_consensus_state(src_height)
.map_err(|e| ForeignClientError::ClientUpgrade(self.id.clone(), format!(
"failed while fetching from chain {} the upgraded client consensus state: {}", self.src_chain.id(), e)))
?;
.map_err(|e| ForeignClientError::ClientUpgrade(self.id.clone(),
hu55a1n1 marked this conversation as resolved.
Show resolved Hide resolved
self.dst_chain.id(),
format!(
"failed while fetching from chain {} \
the upgraded client consensus state: {}",
self.src_chain.id(), e)))?;

debug!(
"[{}] upgraded client consensus state {:?}",
Expand All @@ -224,11 +229,8 @@ impl ForeignClient {
let signer = self.dst_chain.get_signer().map_err(|e| {
ForeignClientError::ClientUpgrade(
self.id.clone(),
format!(
"failed while fetching the destination chain ({}) signer: {}",
self.dst_chain.id(),
e
),
self.dst_chain.id(),
format!("failed while fetching the destination chain signer: {}", e),
)
})?;

Expand All @@ -247,9 +249,9 @@ impl ForeignClient {
let res = self.dst_chain.send_msgs(msgs).map_err(|e| {
ForeignClientError::ClientUpgrade(
self.id.clone(),
self.dst_chain.id(),
format!(
"failed while sending message to destination chain {} with err: {}",
self.dst_chain.id(),
"failed while sending message to destination chain with err: {}",
e
),
)
Expand Down Expand Up @@ -305,7 +307,7 @@ impl ForeignClient {
.wrap_any();

let consensus_state = self.src_chain
.build_consensus_state(client_state.latest_height(), latest_height, client_state.clone())
.build_consensus_state(client_state.latest_height(), latest_height, client_state.clone())
.map_err(|e| ForeignClientError::ClientCreate(format!("failed while building client consensus state from src chain ({}) with error: {}", self.src_chain.id(), e)))?
.wrap_any();

Expand Down