diff --git a/CHANGELOG.md b/CHANGELOG.md index 40aa2552a7..afb5cf1979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [ibc-relayer-cli] - Added `create connection` CLI ([#630]) - Proposed ADR 006 to describe Hermes v0.2.0 use-cases ([#637]) + - Added `client-upgrade` CLI ([#357]) - Update gaia to version 4.1.0 for e2e tests on CI ([#702]) ### IMPROVEMENTS @@ -33,7 +34,7 @@ - [nothing yet] - [ibc-relayer-cli] - - [nothing yet] + - Clarified success path for updating a client that is already up-to-date ([#734]) ### BUG FIXES @@ -61,6 +62,7 @@ - [nothing yet] [#352]: https://github.com/informalsystems/ibc-rs/issues/352 +[#357]: https://github.com/informalsystems/ibc-rs/issues/357 [#416]: https://github.com/informalsystems/ibc-rs/issues/416 [#561]: https://github.com/informalsystems/ibc-rs/issues/561 [#599]: https://github.com/informalsystems/ibc-rs/issues/599 @@ -72,6 +74,7 @@ [#699]: https://github.com/informalsystems/ibc-rs/issues/699 [#700]: https://github.com/informalsystems/ibc-rs/pull/700 [#702]: https://github.com/informalsystems/ibc-rs/issues/702 +[#734]: https://github.com/informalsystems/ibc-rs/issues/734 [#736]: https://github.com/informalsystems/ibc-rs/issues/736 [#740]: https://github.com/informalsystems/ibc-rs/issues/740 [#752]: https://github.com/informalsystems/ibc-rs/issues/752 diff --git a/guide/src/upgrade_test.md b/guide/src/upgrade_test.md new file mode 100644 index 0000000000..aaf06fbb50 --- /dev/null +++ b/guide/src/upgrade_test.md @@ -0,0 +1,166 @@ +## Prerequisites + +- gaiad `(v4.1.*)`, for example: + +```shell +$ gaiad version --long | head -n4 +name: gaia +server_name: gaiad +version: 4.1.2 +commit: 95b07e641d1f69ee12dd911e92b1679f2c64d385 +``` + +## Testing procedure + +1. Start two gaia instances and initialize hermes: + + ```shell + $ ./scripts/dev-env ~/.hermes/config.toml ibc-0 ibc-1 + ``` + The `one-chain` script is invoked for each chain and modifies the `genesis.json` file to use a short window for governance proposals (`200s` for `max_deposit_period` and `voting_period`). Therefore, an upgrade proposal can be submitted, voted on and accepted within a short time. + +2. Create one client on `ibc-1` for `ibc-0`: + + ```shell + $ hermes tx raw create-client ibc-1 ibc-0 + ``` + +3. Create and submit an upgrade plan for chain `ibc-0`: + + Use the hermes test command to make an upgrade proposal. In the example below a software upgrade proposal is made for `ibc-0`, for the height `300` blocks from latest height. `10000000stake` is deposited. + The proposal includes the upgraded client state constructed from the state of `07-tendermint-0` client on `ibc-1` that was created in the previous step. In addition, the `unbonding_period` of the client is set to some new value (`400h`) + + ```shell + $ hermes tx raw upgrade-chain ibc-0 ibc-1 07-tendermint-0 10000000 300 + ``` + + Note that the height offset should be picked such that the proposal plan height is reached after the `200s` voting period. + + 4. Verify that the proposal was accepted: + + Query the upgrade plan to check that it was submitted correctly. Note the `height` at which the proposal will take effect (chain halts). Also `status: PROPOSAL_STATUS_VOTING_PERIOD`. + + ```shell + $ gaiad query gov proposal 1 --home data/ibc-0/ + + content: + '@type': /cosmos.upgrade.v1beta1.SoftwareUpgradeProposal + description: upgrade the chain software and unbonding period + plan: + height: "382" + info: upgrade the chain software and unbonding period + name: test + time: "0001-01-01T00:00:00Z" + upgraded_client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: false + allow_update_after_misbehaviour: false + chain_id: ibc-0 + frozen_height: + revision_height: "0" + revision_number: "0" + latest_height: + revision_height: "383" + revision_number: "0" + max_clock_drift: 0s + proof_specs: + ... + trust_level: + denominator: "0" + numerator: "0" + trusting_period: 0s + unbonding_period: 1440000s + upgrade_path: + - upgrade + - upgradedIBCState + title: upgrade_ibc_clients + deposit_end_time: "2021-03-23T17:25:42.543572Z" + final_tally_result: + abstain: "0" + "no": "0" + no_with_veto: "0" + "yes": "0" + proposal_id: "1" + status: PROPOSAL_STATUS_VOTING_PERIOD + submit_time: "2021-03-23T17:22:22.543572Z" + total_deposit: + - amount: "10000000" + denom: stake + voting_end_time: "2021-03-23T17:25:42.543572Z" + voting_start_time: "2021-03-23T17:22:22.543572Z" + ``` + + 5. Vote on the proposal + + The parameter `1` should match the `proposal_id:` from the upgrade proposal submitted at step 3. This command must be issued while the proposal status is `PROPOSAL_STATUS_VOTING_PERIOD`. + + ```shell + gaiad tx gov vote 1 yes --home data/ibc-0/data/ --keyring-backend test --keyring-dir data/ibc-0/ --chain-id ibc-0 --from validator + ``` + + Wait approximately 200 seconds until the proposal changes status to `PROPOSAL_STATUS_PASSED`. Note the `final tally_result` that includes the vote submitted in previous step. + + ```shell + $ gaiad query gov proposal 1 --home data/ibc-0/ + + content: + '@type': /cosmos.upgrade.v1beta1.SoftwareUpgradeProposal + description: upgrade the chain software and unbonding period + plan: + ... + final_tally_result: + abstain: "0" + "no": "0" + no_with_veto: "0" + "yes": "100000000000" + proposal_id: "1" + status: PROPOSAL_STATUS_PASSED + submit_time: "2021-03-23T17:22:22.543572Z" + total_deposit: + - amount: "10000000" + denom: stake + voting_end_time: "2021-03-23T17:25:42.543572Z" + voting_start_time: "2021-03-23T17:22:22.543572Z" + ``` + +6. Test the `upgrade-client` CLI + + The following command performs the upgrade for client `07-tendermint-0`. It outputs two events, one for the updated client state, and another for the upgraded state. + + ```shell + $ hermes tx raw upgrade-client ibc-1 ibc-0 07-tendermint-0 + + { + "status": "success", + "result": [ + { + "UpdateClient": { + "client_id": "07-tendermint-0", + "client_type": "Tendermint", + "consensus_height": { + "revision_height": 332, + "revision_number": 0 + }, + "height": { + "revision_height": 404, + "revision_number": 1 + } + } + }, + { + "UpgradeClient": { + "client_id": "07-tendermint-0", + "client_type": "Tendermint", + "consensus_height": { + "revision_height": 333, + "revision_number": 0 + }, + "height": { + "revision_height": 404, + "revision_number": 1 + } + } + } + ] + } + ``` diff --git a/modules/src/events.rs b/modules/src/events.rs index 0d66b8ee5a..d0e41aed51 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -34,6 +34,7 @@ pub enum IbcEvent { CreateClient(ClientEvents::CreateClient), UpdateClient(ClientEvents::UpdateClient), + UpgradeClient(ClientEvents::UpgradeClient), ClientMisbehavior(ClientEvents::ClientMisbehavior), OpenInitConnection(ConnectionEvents::OpenInit), @@ -111,6 +112,7 @@ impl IbcEvent { IbcEvent::NewBlock(ev) => ev.set_height(height), IbcEvent::CreateClient(ev) => ev.set_height(height), IbcEvent::UpdateClient(ev) => ev.set_height(height), + IbcEvent::UpgradeClient(ev) => ev.set_height(height), IbcEvent::ClientMisbehavior(ev) => ev.set_height(height), IbcEvent::OpenInitConnection(ev) => ev.set_height(height), IbcEvent::OpenTryConnection(ev) => ev.set_height(height), diff --git a/modules/src/ics02_client/context.rs b/modules/src/ics02_client/context.rs index e8a8d93ef1..73ae3887cc 100644 --- a/modules/src/ics02_client/context.rs +++ b/modules/src/ics02_client/context.rs @@ -6,7 +6,7 @@ use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_state::AnyClientState; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::error::Error; -use crate::ics02_client::handler::ClientResult::{self, Create, Update}; +use crate::ics02_client::handler::ClientResult::{self, Create, Update, Upgrade}; use crate::ics24_host::identifier::ClientId; use crate::Height; @@ -47,6 +47,9 @@ pub trait ClientKeeper { )?; Ok(()) } + Upgrade(_) => { + unimplemented!() + } } } diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index 850520fe11..7fca139325 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -2,6 +2,8 @@ use anomaly::{BoxError, Context}; use thiserror::Error; use crate::ics02_client::client_type::ClientType; +use crate::ics23_commitment::error::Kind as Ics23Kind; +use crate::ics24_host::error::ValidationKind; use crate::ics24_host::identifier::ClientId; use crate::Height; @@ -21,6 +23,9 @@ pub enum Kind { #[error("client not found: {0}")] ClientNotFound(ClientId), + #[error("client is frozen: {0}")] + ClientFrozen(ClientId), + #[error("consensus state not found at: {0} at height {1}")] ConsensusStateNotFound(ClientId, Height), @@ -54,8 +59,8 @@ pub enum Kind { #[error("invalid raw client consensus state")] InvalidRawConsensusState, - #[error("invalid identifer")] - InvalidIdentifier, + #[error("invalid client identifier: validation error: {0}")] + InvalidClientIdentifier(ValidationKind), #[error("invalid raw header")] InvalidRawHeader, @@ -66,6 +71,12 @@ pub enum Kind { #[error("invalid address")] InvalidAddress, + #[error("invalid proof for the upgraded client state")] + InvalidUpgradeClientProof(Ics23Kind), + + #[error("invalid proof for the upgraded consensus state")] + InvalidUpgradeConsensusStateProof(Ics23Kind), + #[error("mismatch between client and arguments types, expected: {0:?}")] ClientArgsTypeMismatch(ClientType), diff --git a/modules/src/ics02_client/events.rs b/modules/src/ics02_client/events.rs index 4ff14174fb..9fede49bf4 100644 --- a/modules/src/ics02_client/events.rs +++ b/modules/src/ics02_client/events.rs @@ -12,6 +12,7 @@ use std::convert::{TryFrom, TryInto}; /// The content of the `type` field for the event that a chain produces upon executing the create client transaction. const CREATE_EVENT_TYPE: &str = "create_client"; const UPDATE_EVENT_TYPE: &str = "update_client"; +const UPGRADE_EVENT_TYPE: &str = "upgrade_client"; /// The content of the `key` field for the attribute containing the client identifier. const CLIENT_ID_ATTRIBUTE_KEY: &str = "client_id"; @@ -30,6 +31,9 @@ pub fn try_from_tx(event: &tendermint::abci::Event) -> Option { UPDATE_EVENT_TYPE => Some(IbcEvent::UpdateClient(UpdateClient( extract_attributes_from_tx(event), ))), + UPGRADE_EVENT_TYPE => Some(IbcEvent::UpgradeClient(UpgradeClient( + extract_attributes_from_tx(event), + ))), _ => None, } } @@ -77,7 +81,7 @@ impl From for IbcEvent { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attributes { pub height: Height, pub client_id: ClientId, @@ -214,3 +218,19 @@ impl From for IbcEvent { IbcEvent::ClientMisbehavior(v) } } + +/// Signals a recent upgrade of an on-chain client (IBC Client). +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +pub struct UpgradeClient(Attributes); + +impl UpgradeClient { + pub fn set_height(&mut self, height: Height) { + self.0.height = height; + } +} + +impl From for UpgradeClient { + fn from(attrs: Attributes) -> Self { + UpgradeClient(attrs) + } +} diff --git a/modules/src/ics02_client/handler.rs b/modules/src/ics02_client/handler.rs index e2410cf960..481d621985 100644 --- a/modules/src/ics02_client/handler.rs +++ b/modules/src/ics02_client/handler.rs @@ -7,11 +7,13 @@ use crate::ics02_client::msgs::ClientMsg; pub mod create_client; pub mod update_client; +pub mod upgrade_client; #[derive(Clone, Debug, PartialEq, Eq)] pub enum ClientResult { Create(create_client::Result), Update(update_client::Result), + Upgrade(upgrade_client::Result), } /// General entry point for processing any message related to ICS2 (client functions) protocols. @@ -22,5 +24,6 @@ where match msg { ClientMsg::CreateClient(msg) => create_client::process(ctx, msg), ClientMsg::UpdateClient(msg) => update_client::process(ctx, msg), + ClientMsg::UpgradeClient(msg) => upgrade_client::process(ctx, msg), } } diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index c6554c44f1..49ae367e26 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -80,7 +80,7 @@ mod tests { use crate::ics02_client::client_state::AnyClientState; use crate::ics02_client::error::Kind; use crate::ics02_client::handler::dispatch; - use crate::ics02_client::handler::ClientResult::{Create, Update}; + use crate::ics02_client::handler::ClientResult::Update; use crate::ics02_client::header::Header; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; use crate::ics02_client::msgs::ClientMsg; @@ -128,7 +128,7 @@ mod tests { ))) ) } - Create(_) => panic!("update handler result has type CreateResult"), + _ => panic!("update handler result has incorrect type"), } } Err(err) => { diff --git a/modules/src/ics02_client/handler/upgrade_client.rs b/modules/src/ics02_client/handler/upgrade_client.rs new file mode 100644 index 0000000000..5579fe14fb --- /dev/null +++ b/modules/src/ics02_client/handler/upgrade_client.rs @@ -0,0 +1,53 @@ +//! Protocol logic specific to processing ICS2 messages of type `MsgUpgradeAnyClient`. +//! +use crate::handler::HandlerResult; +use crate::ics02_client::client_consensus::AnyConsensusState; +use crate::ics02_client::client_state::AnyClientState; +use crate::ics02_client::client_state::ClientState; +use crate::ics02_client::context::ClientReader; +use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::handler::ClientResult; +use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; +use crate::ics24_host::identifier::ClientId; + +/// The result following the successful processing of a `MsgUpgradeAnyClient` message. +/// This data type should be used with a qualified name `upgrade_client::Result` to avoid ambiguity. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Result { + pub client_id: ClientId, + pub client_state: AnyClientState, + pub consensus_state: AnyConsensusState, +} + +pub fn process( + ctx: &dyn ClientReader, + msg: MsgUpgradeAnyClient, +) -> HandlerResult { + let MsgUpgradeAnyClient { client_id, .. } = msg; + + // Read client state from the host chain store. + let client_state = ctx + .client_state(&client_id) + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + + if client_state.is_frozen() { + return Err(Kind::ClientFrozen(client_id).into()); + } + + // Not implemented yet: https://github.com/informalsystems/ibc-rs/issues/722 + todo!() + + // let result = ClientResult::Upgrade(Result { + // client_id: client_id.clone(), + // client_state: new_client_state, + // consensus_state: new_consensus_state, + // }); + // + // let event_attributes = Attributes { + // client_id, + // ..Default::default() + // }; + // output.emit(IbcEvent::UpgradeClient(event_attributes.into())); + // + // Ok(output.with_result(result)) +} diff --git a/modules/src/ics02_client/msgs.rs b/modules/src/ics02_client/msgs.rs index 4390a4489f..9d27533507 100644 --- a/modules/src/ics02_client/msgs.rs +++ b/modules/src/ics02_client/msgs.rs @@ -6,13 +6,16 @@ use crate::ics02_client::msgs::create_client::MsgCreateAnyClient; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; pub mod create_client; pub mod update_client; +pub mod upgrade_client; #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum ClientMsg { CreateClient(MsgCreateAnyClient), UpdateClient(MsgUpdateAnyClient), + UpgradeClient(MsgUpgradeAnyClient), } diff --git a/modules/src/ics02_client/msgs/create_client.rs b/modules/src/ics02_client/msgs/create_client.rs index dc8d5cffcd..9baac4f763 100644 --- a/modules/src/ics02_client/msgs/create_client.rs +++ b/modules/src/ics02_client/msgs/create_client.rs @@ -1,8 +1,4 @@ -//! These are definitions of messages that a relayer submits to a chain. Specific implementations of -//! these messages can be found, for instance, in ICS 07 for Tendermint-specific chains. A chain -//! handles these messages in two layers: first with the general ICS 02 client handler, which -//! subsequently calls into the chain-specific (e.g., ICS 07) client handler. See: -//! https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#create. +//! Definition of domain type message `MsgCreateAnyClient`. use std::convert::TryFrom; @@ -17,7 +13,7 @@ use crate::ics02_client::error::{Error, Kind}; use crate::signer::Signer; use crate::tx_msg::Msg; -pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; +pub(crate) const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; /// A type of message that triggers the creation of a new on-chain (IBC) client. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/modules/src/ics02_client/msgs/update_client.rs b/modules/src/ics02_client/msgs/update_client.rs index 05c7c5953f..a0ffedbaae 100644 --- a/modules/src/ics02_client/msgs/update_client.rs +++ b/modules/src/ics02_client/msgs/update_client.rs @@ -1,8 +1,4 @@ -//! These are definitions of messages that a relayer submits to a chain. Specific implementations of -//! these messages can be found, for instance, in ICS 07 for Tendermint-specific chains. A chain -//! handles these messages in two layers: first with the general ICS 02 client handler, which -//! subsequently calls into the chain-specific (e.g., ICS 07) client handler. See: -//! https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#create. +//! Definition of domain type message `MsgUpdateAnyClient`. use std::convert::TryFrom; @@ -16,7 +12,7 @@ use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; use crate::tx_msg::Msg; -pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgUpdateClient"; +pub(crate) const TYPE_URL: &str = "/ibc.core.client.v1.MsgUpdateClient"; /// A type of message that triggers the update of an on-chain (IBC) client with new headers. #[derive(Clone, Debug, PartialEq)] // TODO: Add Eq bound when possible @@ -83,9 +79,8 @@ mod tests { use crate::ics02_client::header::AnyHeader; use crate::ics02_client::msgs::MsgUpdateAnyClient; - use crate::ics24_host::identifier::ClientId; - use crate::ics07_tendermint::header::test_util::get_dummy_ics07_header; + use crate::ics24_host::identifier::ClientId; use crate::test_utils::get_dummy_account_id; #[test] diff --git a/modules/src/ics02_client/msgs/upgrade_client.rs b/modules/src/ics02_client/msgs/upgrade_client.rs new file mode 100644 index 0000000000..4379b8f4ad --- /dev/null +++ b/modules/src/ics02_client/msgs/upgrade_client.rs @@ -0,0 +1,89 @@ +//! Definition of domain type msg `MsgUpgradeAnyClient`. + +use std::convert::TryFrom; +use std::str::FromStr; + +use tendermint_proto::Protobuf; + +use ibc_proto::ibc::core::client::v1::MsgUpgradeClient as RawMsgUpgradeClient; +use ibc_proto::ibc::core::commitment::v1::MerkleProof; + +use crate::ics02_client::client_consensus::AnyConsensusState; +use crate::ics02_client::client_state::AnyClientState; +use crate::ics02_client::error::Kind; +use crate::ics23_commitment::commitment::CommitmentProofBytes; +use crate::ics24_host::identifier::ClientId; +use crate::signer::Signer; +use crate::tx_msg::Msg; + +pub(crate) const TYPE_URL: &str = "/ibc.core.client.v1.MsgUpgradeClient"; + +/// A type of message that triggers the upgrade of an on-chain (IBC) client. +#[derive(Clone, Debug, PartialEq)] +pub struct MsgUpgradeAnyClient { + pub client_id: ClientId, + pub client_state: AnyClientState, + pub consensus_state: AnyConsensusState, + pub proof_upgrade_client: MerkleProof, + pub proof_upgrade_consensus_state: MerkleProof, + pub signer: Signer, +} + +impl Msg for MsgUpgradeAnyClient { + type ValidationError = crate::ics24_host::error::ValidationError; + type Raw = RawMsgUpgradeClient; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn type_url(&self) -> String { + TYPE_URL.to_string() + } +} + +impl Protobuf for MsgUpgradeAnyClient {} + +impl From for RawMsgUpgradeClient { + fn from(dm_msg: MsgUpgradeAnyClient) -> RawMsgUpgradeClient { + let c_bytes: CommitmentProofBytes = dm_msg.proof_upgrade_client.into(); + let cs_bytes: CommitmentProofBytes = dm_msg.proof_upgrade_consensus_state.into(); + + RawMsgUpgradeClient { + client_id: dm_msg.client_id.to_string(), + client_state: Some(dm_msg.client_state.into()), + consensus_state: Some(dm_msg.consensus_state.into()), + proof_upgrade_client: c_bytes.into(), + proof_upgrade_consensus_state: cs_bytes.into(), + signer: dm_msg.signer.to_string(), + } + } +} + +impl TryFrom for MsgUpgradeAnyClient { + type Error = Kind; + + fn try_from(proto_msg: RawMsgUpgradeClient) -> Result { + let raw_client_state = proto_msg.client_state.ok_or(Kind::InvalidRawClientState)?; + let raw_consensus_state = proto_msg + .consensus_state + .ok_or(Kind::InvalidRawConsensusState)?; + + let c_bytes = CommitmentProofBytes::from(proto_msg.proof_upgrade_client); + let cs_bytes = CommitmentProofBytes::from(proto_msg.proof_upgrade_consensus_state); + + Ok(MsgUpgradeAnyClient { + client_id: ClientId::from_str(&proto_msg.client_id) + .map_err(|e| Kind::InvalidClientIdentifier(e.kind().clone()))?, + client_state: AnyClientState::try_from(raw_client_state) + .map_err(|_| Kind::InvalidRawClientState)?, + consensus_state: AnyConsensusState::try_from(raw_consensus_state) + .map_err(|_| Kind::InvalidRawConsensusState)?, + proof_upgrade_client: MerkleProof::try_from(c_bytes) + .map_err(|e| Kind::InvalidUpgradeClientProof(e.kind().clone()))?, + proof_upgrade_consensus_state: MerkleProof::try_from(cs_bytes) + .map_err(|e| Kind::InvalidUpgradeConsensusStateProof(e.kind().clone()))?, + signer: proto_msg.signer.into(), + }) + } +} diff --git a/modules/src/ics03_connection/events.rs b/modules/src/ics03_connection/events.rs index a4e9cb360e..da3cf99614 100644 --- a/modules/src/ics03_connection/events.rs +++ b/modules/src/ics03_connection/events.rs @@ -60,7 +60,7 @@ fn extract_attributes_from_tx(event: &tendermint::abci::Event) -> Attributes { attr } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attributes { pub height: Height, pub connection_id: Option, diff --git a/modules/src/ics04_channel/events.rs b/modules/src/ics04_channel/events.rs index 971708057a..5254287b44 100644 --- a/modules/src/ics04_channel/events.rs +++ b/modules/src/ics04_channel/events.rs @@ -151,7 +151,7 @@ fn extract_packet_and_write_ack_from_tx( (packet, write_ack) } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attributes { pub height: Height, pub port_id: PortId, diff --git a/modules/src/ics04_channel/packet.rs b/modules/src/ics04_channel/packet.rs index d441d8ff29..ebef6c0828 100644 --- a/modules/src/ics04_channel/packet.rs +++ b/modules/src/ics04_channel/packet.rs @@ -49,7 +49,7 @@ impl std::fmt::Display for PacketMsgType { } /// The sequence number of a packet enforces ordering among packets from the same source. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub struct Sequence(u64); impl Sequence { diff --git a/modules/src/ics07_tendermint/client_state.rs b/modules/src/ics07_tendermint/client_state.rs index db77540163..6a3058f3fd 100644 --- a/modules/src/ics07_tendermint/client_state.rs +++ b/modules/src/ics07_tendermint/client_state.rs @@ -2,7 +2,9 @@ use std::convert::{TryFrom, TryInto}; use std::time::Duration; use serde::Serialize; -use tendermint::trust_threshold::TrustThresholdFraction as TrustThreshold; +use tendermint::trust_threshold::{ + TrustThresholdFraction as TrustThreshold, TrustThresholdFraction, +}; use tendermint_proto::Protobuf; use ibc_proto::ibc::lightclients::tendermint::v1::{ClientState as RawClientState, Fraction}; @@ -103,6 +105,21 @@ impl ClientState { ..self } } + + /// Helper function to verify the upgrade client procedure. + /// Resets all fields except the blockchain-specific ones. + pub fn zero_custom_fields(mut client_state: Self) -> Self { + client_state.trusting_period = Duration::from_secs(0); + client_state.trust_level = TrustThresholdFraction { + numerator: 0, + denominator: 0, + }; + client_state.allow_update_after_expiry = false; + client_state.allow_update_after_misbehaviour = false; + client_state.frozen_height = Height::zero(); + client_state.max_clock_drift = Duration::from_secs(0); + client_state + } } impl crate::ics02_client::client_state::ClientState for ClientState { diff --git a/modules/src/ics18_relayer/mod.rs b/modules/src/ics18_relayer/mod.rs index 71e8e42ed9..380a73836c 100644 --- a/modules/src/ics18_relayer/mod.rs +++ b/modules/src/ics18_relayer/mod.rs @@ -1,6 +1,5 @@ -//! - ICS 18: Implementation of basic relayer functions. +//! ICS 18: Implementation of basic relayer functions. pub mod context; pub mod error; - pub mod utils; diff --git a/modules/src/ics23_commitment/error.rs b/modules/src/ics23_commitment/error.rs index 96ef1dc79a..07d4490c80 100644 --- a/modules/src/ics23_commitment/error.rs +++ b/modules/src/ics23_commitment/error.rs @@ -3,7 +3,7 @@ use thiserror::Error; pub type Error = anomaly::Error; -#[derive(Clone, Debug, Error)] +#[derive(Clone, Debug, Error, PartialEq, Eq)] pub enum Kind { #[error("invalid raw merkle proof")] InvalidRawMerkleProof, diff --git a/modules/src/ics24_host/mod.rs b/modules/src/ics24_host/mod.rs index 90c28b9033..3f3fea5298 100644 --- a/modules/src/ics24_host/mod.rs +++ b/modules/src/ics24_host/mod.rs @@ -1,7 +1,8 @@ //! ICS 24: Host Requirements +pub use path::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH}; + pub mod error; pub mod identifier; mod path; -pub use path::{Path, IBC_QUERY_PATH}; pub mod validate; diff --git a/modules/src/ics24_host/path.rs b/modules/src/ics24_host/path.rs index 69f73c94b4..2bbae20888 100644 --- a/modules/src/ics24_host/path.rs +++ b/modules/src/ics24_host/path.rs @@ -1,16 +1,29 @@ -use crate::ics04_channel::packet::Sequence; /// Path-space as listed in ICS-024 /// https://github.com/cosmos/ics/tree/master/spec/ics-024-host-requirements#path-space /// Some of these are implemented in other ICSs, but ICS-024 has a nice summary table. /// -use crate::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use std::fmt::{Display, Formatter, Result}; -/// IBC Query Path is hard-coded +use crate::ics04_channel::packet::Sequence; +use crate::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; + +/// ABCI Query path for the IBC sub-store pub const IBC_QUERY_PATH: &str = "store/ibc/key"; +/// ABCI Query path for the upgrade sub-store +/// ## Note: This is SDK/Tendermint specific! +pub const SDK_UPGRADE_QUERY_PATH: &str = "store/upgrade/key"; + +/// ABCI client upgrade keys +/// - The key identifying the upgraded IBC state within the upgrade sub-store +const UPGRADED_IBC_STATE: &str = "upgradedIBCState"; +///- The key identifying the upgraded client state +const UPGRADED_CLIENT_STATE: &str = "upgradedClient"; +/// - The key identifying the upgraded consensus state +const UPGRADED_CLIENT_CONSENSUS_STATE: &str = "upgradedConsState"; + /// The Path enum abstracts out the different sub-paths -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Path { ClientType(ClientId), ClientState(ClientId), @@ -41,6 +54,14 @@ pub enum Path { channel_id: ChannelId, sequence: Sequence, }, + Upgrade(ClientUpgradePath), +} + +/// Paths that are specific for client upgrades. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ClientUpgradePath { + UpgradedClientState(u64), + UpgradedClientConsensusState(u64), } impl Path { @@ -119,6 +140,16 @@ impl Display for Path { "receipts/ports/{}/channels/{}/sequences/{}", port_id, channel_id, sequence ), + Path::Upgrade(ClientUpgradePath::UpgradedClientState(height)) => write!( + f, + "{}/{}/{}", + UPGRADED_IBC_STATE, height, UPGRADED_CLIENT_STATE + ), + Path::Upgrade(ClientUpgradePath::UpgradedClientConsensusState(height)) => write!( + f, + "{}/{}/{}", + UPGRADED_IBC_STATE, height, UPGRADED_CLIENT_CONSENSUS_STATE + ), } } } diff --git a/modules/src/tx_msg.rs b/modules/src/tx_msg.rs index 2f1ef10402..85d30ac97a 100644 --- a/modules/src/tx_msg.rs +++ b/modules/src/tx_msg.rs @@ -1,6 +1,7 @@ -use crate::ics24_host::error::ValidationError; use prost_types::Any; +use crate::ics24_host::error::ValidationError; + pub trait Msg: Clone { type ValidationError: std::error::Error; type Raw: From + prost::Message; diff --git a/proto-compiler/README.md b/proto-compiler/README.md index 60a5355ea9..0ea210c722 100644 --- a/proto-compiler/README.md +++ b/proto-compiler/README.md @@ -6,10 +6,10 @@ The `ibc-proto-compiler` is a simple command-line tool to automate the compilati ### Clone the Cosmos SDK -Move into the the `proto-compiler` directory: +From within the `proto-compiler` directory, compile the binary using the `--locked` flag: ```bash -$ cd proto-compiler +cargo build --locked ``` Run the following command to clone the Cosmos SDK and the IBC-Go repositories, and check out a specific commit: diff --git a/proto-compiler/src/cmd/compile.rs b/proto-compiler/src/cmd/compile.rs index 7f1d0b5f38..733ee8b5ee 100644 --- a/proto-compiler/src/cmd/compile.rs +++ b/proto-compiler/src/cmd/compile.rs @@ -122,9 +122,11 @@ impl CompileCmd { let proto_paths = [ format!("{}/../proto/definitions/mock", root), format!("{}/proto/cosmos/auth", sdk_dir.display()), + format!("{}/proto/cosmos/gov", sdk_dir.display()), format!("{}/proto/cosmos/tx", sdk_dir.display()), format!("{}/proto/cosmos/base", sdk_dir.display()), format!("{}/proto/cosmos/staking", sdk_dir.display()), + format!("{}/proto/cosmos/upgrade", sdk_dir.display()), ]; let proto_includes_paths = [ diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 15b2a20bdb..43a8f37742 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -75,6 +75,11 @@ pub mod cosmos { include!("prost/cosmos.upgrade.v1beta1.rs"); } } + pub mod gov { + pub mod v1beta1 { + include!("prost/cosmos.gov.v1beta1.rs"); + } + } } pub mod ibc { diff --git a/proto/src/prost/cosmos.gov.v1beta1.rs b/proto/src/prost/cosmos.gov.v1beta1.rs new file mode 100644 index 0000000000..6bf3e94f9a --- /dev/null +++ b/proto/src/prost/cosmos.gov.v1beta1.rs @@ -0,0 +1,352 @@ +/// TextProposal defines a standard text proposal whose changes need to be +/// manually updated in case of approval. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextProposal { + #[prost(string, tag="1")] + pub title: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub description: ::prost::alloc::string::String, +} +/// Deposit defines an amount deposited by an account address to an active +/// proposal. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Deposit { + #[prost(uint64, tag="1")] + pub proposal_id: u64, + #[prost(string, tag="2")] + pub depositor: ::prost::alloc::string::String, + #[prost(message, repeated, tag="3")] + pub amount: ::prost::alloc::vec::Vec, +} +/// Proposal defines the core field members of a governance proposal. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Proposal { + #[prost(uint64, tag="1")] + pub proposal_id: u64, + #[prost(message, optional, tag="2")] + pub content: ::core::option::Option<::prost_types::Any>, + #[prost(enumeration="ProposalStatus", tag="3")] + pub status: i32, + #[prost(message, optional, tag="4")] + pub final_tally_result: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub submit_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag="6")] + pub deposit_end_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, repeated, tag="7")] + pub total_deposit: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="8")] + pub voting_start_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag="9")] + pub voting_end_time: ::core::option::Option<::prost_types::Timestamp>, +} +/// TallyResult defines a standard tally for a governance proposal. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TallyResult { + #[prost(string, tag="1")] + pub yes: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub abstain: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub no: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub no_with_veto: ::prost::alloc::string::String, +} +/// Vote defines a vote on a governance proposal. +/// A Vote consists of a proposal ID, the voter, and the vote option. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Vote { + #[prost(uint64, tag="1")] + pub proposal_id: u64, + #[prost(string, tag="2")] + pub voter: ::prost::alloc::string::String, + #[prost(enumeration="VoteOption", tag="3")] + pub option: i32, +} +/// DepositParams defines the params for deposits on governance proposals. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DepositParams { + /// Minimum deposit for a proposal to enter voting period. + #[prost(message, repeated, tag="1")] + pub min_deposit: ::prost::alloc::vec::Vec, + /// Maximum period for Atom holders to deposit on a proposal. Initial value: 2 + /// months. + #[prost(message, optional, tag="2")] + pub max_deposit_period: ::core::option::Option<::prost_types::Duration>, +} +/// VotingParams defines the params for voting on governance proposals. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VotingParams { + /// Length of the voting period. + #[prost(message, optional, tag="1")] + pub voting_period: ::core::option::Option<::prost_types::Duration>, +} +/// TallyParams defines the params for tallying votes on governance proposals. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TallyParams { + /// Minimum percentage of total stake needed to vote for a result to be + /// considered valid. + #[prost(bytes="vec", tag="1")] + pub quorum: ::prost::alloc::vec::Vec, + /// Minimum proportion of Yes votes for proposal to pass. Default value: 0.5. + #[prost(bytes="vec", tag="2")] + pub threshold: ::prost::alloc::vec::Vec, + /// Minimum value of Veto votes to Total votes ratio for proposal to be + /// vetoed. Default value: 1/3. + #[prost(bytes="vec", tag="3")] + pub veto_threshold: ::prost::alloc::vec::Vec, +} +/// VoteOption enumerates the valid vote options for a given governance proposal. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum VoteOption { + /// VOTE_OPTION_UNSPECIFIED defines a no-op vote option. + Unspecified = 0, + /// VOTE_OPTION_YES defines a yes vote option. + Yes = 1, + /// VOTE_OPTION_ABSTAIN defines an abstain vote option. + Abstain = 2, + /// VOTE_OPTION_NO defines a no vote option. + No = 3, + /// VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + NoWithVeto = 4, +} +/// ProposalStatus enumerates the valid statuses of a proposal. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ProposalStatus { + /// PROPOSAL_STATUS_UNSPECIFIED defines the default propopsal status. + Unspecified = 0, + /// PROPOSAL_STATUS_DEPOSIT_PERIOD defines a proposal status during the deposit + /// period. + DepositPeriod = 1, + /// PROPOSAL_STATUS_VOTING_PERIOD defines a proposal status during the voting + /// period. + VotingPeriod = 2, + /// PROPOSAL_STATUS_PASSED defines a proposal status of a proposal that has + /// passed. + Passed = 3, + /// PROPOSAL_STATUS_REJECTED defines a proposal status of a proposal that has + /// been rejected. + Rejected = 4, + /// PROPOSAL_STATUS_FAILED defines a proposal status of a proposal that has + /// failed. + Failed = 5, +} +/// MsgSubmitProposal defines an sdk.Msg type that supports submitting arbitrary +/// proposal Content. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgSubmitProposal { + #[prost(message, optional, tag="1")] + pub content: ::core::option::Option<::prost_types::Any>, + #[prost(message, repeated, tag="2")] + pub initial_deposit: ::prost::alloc::vec::Vec, + #[prost(string, tag="3")] + pub proposer: ::prost::alloc::string::String, +} +/// MsgSubmitProposalResponse defines the Msg/SubmitProposal response type. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgSubmitProposalResponse { + #[prost(uint64, tag="1")] + pub proposal_id: u64, +} +/// MsgVote defines a message to cast a vote. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgVote { + #[prost(uint64, tag="1")] + pub proposal_id: u64, + #[prost(string, tag="2")] + pub voter: ::prost::alloc::string::String, + #[prost(enumeration="VoteOption", tag="3")] + pub option: i32, +} +/// MsgVoteResponse defines the Msg/Vote response type. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgVoteResponse { +} +/// MsgDeposit defines a message to submit a deposit to an existing proposal. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgDeposit { + #[prost(uint64, tag="1")] + pub proposal_id: u64, + #[prost(string, tag="2")] + pub depositor: ::prost::alloc::string::String, + #[prost(message, repeated, tag="3")] + pub amount: ::prost::alloc::vec::Vec, +} +/// MsgDepositResponse defines the Msg/Deposit response type. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgDepositResponse { +} +# [doc = r" Generated client implementations."] pub mod msg_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Msg defines the bank Msg service."] pub struct MsgClient < T > { inner : tonic :: client :: Grpc < T > , } impl MsgClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > MsgClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = " SubmitProposal defines a method to create new proposal given a content."] pub async fn submit_proposal (& mut self , request : impl tonic :: IntoRequest < super :: MsgSubmitProposal > ,) -> Result < tonic :: Response < super :: MsgSubmitProposalResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Msg/SubmitProposal") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Vote defines a method to add a vote on a specific proposal."] pub async fn vote (& mut self , request : impl tonic :: IntoRequest < super :: MsgVote > ,) -> Result < tonic :: Response < super :: MsgVoteResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Msg/Vote") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Deposit defines a method to add deposit on a specific proposal."] pub async fn deposit (& mut self , request : impl tonic :: IntoRequest < super :: MsgDeposit > ,) -> Result < tonic :: Response < super :: MsgDepositResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Msg/Deposit") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for MsgClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for MsgClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "MsgClient {{ ... }}") } } }/// QueryProposalRequest is the request type for the Query/Proposal RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryProposalRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, +} +/// QueryProposalResponse is the response type for the Query/Proposal RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryProposalResponse { + #[prost(message, optional, tag="1")] + pub proposal: ::core::option::Option, +} +/// QueryProposalsRequest is the request type for the Query/Proposals RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryProposalsRequest { + /// proposal_status defines the status of the proposals. + #[prost(enumeration="ProposalStatus", tag="1")] + pub proposal_status: i32, + /// voter defines the voter address for the proposals. + #[prost(string, tag="2")] + pub voter: ::prost::alloc::string::String, + /// depositor defines the deposit addresses from the proposals. + #[prost(string, tag="3")] + pub depositor: ::prost::alloc::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="4")] + pub pagination: ::core::option::Option, +} +/// QueryProposalsResponse is the response type for the Query/Proposals RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryProposalsResponse { + #[prost(message, repeated, tag="1")] + pub proposals: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::core::option::Option, +} +/// QueryVoteRequest is the request type for the Query/Vote RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryVoteRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, + /// voter defines the oter address for the proposals. + #[prost(string, tag="2")] + pub voter: ::prost::alloc::string::String, +} +/// QueryVoteResponse is the response type for the Query/Vote RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryVoteResponse { + /// vote defined the queried vote. + #[prost(message, optional, tag="1")] + pub vote: ::core::option::Option, +} +/// QueryVotesRequest is the request type for the Query/Votes RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryVotesRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::core::option::Option, +} +/// QueryVotesResponse is the response type for the Query/Votes RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryVotesResponse { + /// votes defined the queried votes. + #[prost(message, repeated, tag="1")] + pub votes: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::core::option::Option, +} +/// QueryParamsRequest is the request type for the Query/Params RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryParamsRequest { + /// params_type defines which parameters to query for, can be one of "voting", + /// "tallying" or "deposit". + #[prost(string, tag="1")] + pub params_type: ::prost::alloc::string::String, +} +/// QueryParamsResponse is the response type for the Query/Params RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryParamsResponse { + /// voting_params defines the parameters related to voting. + #[prost(message, optional, tag="1")] + pub voting_params: ::core::option::Option, + /// deposit_params defines the parameters related to deposit. + #[prost(message, optional, tag="2")] + pub deposit_params: ::core::option::Option, + /// tally_params defines the parameters related to tally. + #[prost(message, optional, tag="3")] + pub tally_params: ::core::option::Option, +} +/// QueryDepositRequest is the request type for the Query/Deposit RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDepositRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, + /// depositor defines the deposit addresses from the proposals. + #[prost(string, tag="2")] + pub depositor: ::prost::alloc::string::String, +} +/// QueryDepositResponse is the response type for the Query/Deposit RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDepositResponse { + /// deposit defines the requested deposit. + #[prost(message, optional, tag="1")] + pub deposit: ::core::option::Option, +} +/// QueryDepositsRequest is the request type for the Query/Deposits RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDepositsRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::core::option::Option, +} +/// QueryDepositsResponse is the response type for the Query/Deposits RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDepositsResponse { + #[prost(message, repeated, tag="1")] + pub deposits: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::core::option::Option, +} +/// QueryTallyResultRequest is the request type for the Query/Tally RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryTallyResultRequest { + /// proposal_id defines the unique id of the proposal. + #[prost(uint64, tag="1")] + pub proposal_id: u64, +} +/// QueryTallyResultResponse is the response type for the Query/Tally RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryTallyResultResponse { + /// tally defines the requested tally. + #[prost(message, optional, tag="1")] + pub tally: ::core::option::Option, +} +# [doc = r" Generated client implementations."] pub mod query_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Query defines the gRPC querier service for gov module"] pub struct QueryClient < T > { inner : tonic :: client :: Grpc < T > , } impl QueryClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > QueryClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = " Proposal queries proposal details based on ProposalID."] pub async fn proposal (& mut self , request : impl tonic :: IntoRequest < super :: QueryProposalRequest > ,) -> Result < tonic :: Response < super :: QueryProposalResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Proposal") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Proposals queries all proposals based on given status."] pub async fn proposals (& mut self , request : impl tonic :: IntoRequest < super :: QueryProposalsRequest > ,) -> Result < tonic :: Response < super :: QueryProposalsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Proposals") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Vote queries voted information based on proposalID, voterAddr."] pub async fn vote (& mut self , request : impl tonic :: IntoRequest < super :: QueryVoteRequest > ,) -> Result < tonic :: Response < super :: QueryVoteResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Vote") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Votes queries votes of a given proposal."] pub async fn votes (& mut self , request : impl tonic :: IntoRequest < super :: QueryVotesRequest > ,) -> Result < tonic :: Response < super :: QueryVotesResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Votes") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Params queries all parameters of the gov module."] pub async fn params (& mut self , request : impl tonic :: IntoRequest < super :: QueryParamsRequest > ,) -> Result < tonic :: Response < super :: QueryParamsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Params") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Deposit queries single deposit information based proposalID, depositAddr."] pub async fn deposit (& mut self , request : impl tonic :: IntoRequest < super :: QueryDepositRequest > ,) -> Result < tonic :: Response < super :: QueryDepositResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Deposit") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Deposits queries all deposits of a single proposal."] pub async fn deposits (& mut self , request : impl tonic :: IntoRequest < super :: QueryDepositsRequest > ,) -> Result < tonic :: Response < super :: QueryDepositsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/Deposits") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " TallyResult queries the tally of a proposal vote."] pub async fn tally_result (& mut self , request : impl tonic :: IntoRequest < super :: QueryTallyResultRequest > ,) -> Result < tonic :: Response < super :: QueryTallyResultResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.gov.v1beta1.Query/TallyResult") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for QueryClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for QueryClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "QueryClient {{ ... }}") } } }/// GenesisState defines the gov module's genesis state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenesisState { + /// starting_proposal_id is the ID of the starting proposal. + #[prost(uint64, tag="1")] + pub starting_proposal_id: u64, + /// deposits defines all the deposits present at genesis. + #[prost(message, repeated, tag="2")] + pub deposits: ::prost::alloc::vec::Vec, + /// votes defines all the votes present at genesis. + #[prost(message, repeated, tag="3")] + pub votes: ::prost::alloc::vec::Vec, + /// proposals defines all the proposals present at genesis. + #[prost(message, repeated, tag="4")] + pub proposals: ::prost::alloc::vec::Vec, + /// params defines all the paramaters of related to deposit. + #[prost(message, optional, tag="5")] + pub deposit_params: ::core::option::Option, + /// params defines all the paramaters of related to voting. + #[prost(message, optional, tag="6")] + pub voting_params: ::core::option::Option, + /// params defines all the paramaters of related to tally. + #[prost(message, optional, tag="7")] + pub tally_params: ::core::option::Option, +} diff --git a/proto/src/prost/cosmos.upgrade.v1beta1.rs b/proto/src/prost/cosmos.upgrade.v1beta1.rs index e54c514839..3d0deec5a5 100644 --- a/proto/src/prost/cosmos.upgrade.v1beta1.rs +++ b/proto/src/prost/cosmos.upgrade.v1beta1.rs @@ -22,6 +22,13 @@ pub struct Plan { /// such as a git commit that validators could automatically upgrade to #[prost(string, tag="4")] pub info: ::prost::alloc::string::String, + /// IBC-enabled chains can opt-in to including the upgraded client state in its upgrade plan + /// This will make the chain commit to the correct upgraded (self) client state before the upgrade occurs, + /// so that connecting chains can verify that the new upgraded client is valid by verifying a proof on the + /// previous version of the chain. + /// This will allow IBC connections to persist smoothly across planned chain upgrades + #[prost(message, optional, tag="5")] + pub upgraded_client_state: ::core::option::Option<::prost_types::Any>, } /// SoftwareUpgradeProposal is a gov Content type for initiating a software /// upgrade. @@ -43,3 +50,49 @@ pub struct CancelSoftwareUpgradeProposal { #[prost(string, tag="2")] pub description: ::prost::alloc::string::String, } +/// QueryCurrentPlanRequest is the request type for the Query/CurrentPlan RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryCurrentPlanRequest { +} +/// QueryCurrentPlanResponse is the response type for the Query/CurrentPlan RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryCurrentPlanResponse { + /// plan is the current upgrade plan. + #[prost(message, optional, tag="1")] + pub plan: ::core::option::Option, +} +/// QueryCurrentPlanRequest is the request type for the Query/AppliedPlan RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryAppliedPlanRequest { + /// name is the name of the applied plan to query for. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, +} +/// QueryAppliedPlanResponse is the response type for the Query/AppliedPlan RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryAppliedPlanResponse { + /// height is the block height at which the plan was applied. + #[prost(int64, tag="1")] + pub height: i64, +} +/// QueryUpgradedConsensusStateRequest is the request type for the Query/UpgradedConsensusState +/// RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryUpgradedConsensusStateRequest { + /// last height of the current chain must be sent in request + /// as this is the height under which next consensus state is stored + #[prost(int64, tag="1")] + pub last_height: i64, +} +/// QueryUpgradedConsensusStateResponse is the response type for the Query/UpgradedConsensusState +/// RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryUpgradedConsensusStateResponse { + #[prost(message, optional, tag="1")] + pub upgraded_consensus_state: ::core::option::Option<::prost_types::Any>, +} +# [doc = r" Generated client implementations."] pub mod query_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Query defines the gRPC upgrade querier service."] pub struct QueryClient < T > { inner : tonic :: client :: Grpc < T > , } impl QueryClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > QueryClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = " CurrentPlan queries the current upgrade plan."] pub async fn current_plan (& mut self , request : impl tonic :: IntoRequest < super :: QueryCurrentPlanRequest > ,) -> Result < tonic :: Response < super :: QueryCurrentPlanResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.upgrade.v1beta1.Query/CurrentPlan") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " AppliedPlan queries a previously applied upgrade plan by its name."] pub async fn applied_plan (& mut self , request : impl tonic :: IntoRequest < super :: QueryAppliedPlanRequest > ,) -> Result < tonic :: Response < super :: QueryAppliedPlanResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.upgrade.v1beta1.Query/AppliedPlan") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " UpgradedConsensusState queries the consensus state that will serve"] # [doc = " as a trusted kernel for the next version of this chain. It will only be"] # [doc = " stored at the last height of this chain."] # [doc = " UpgradedConsensusState RPC not supported with legacy querier"] pub async fn upgraded_consensus_state (& mut self , request : impl tonic :: IntoRequest < super :: QueryUpgradedConsensusStateRequest > ,) -> Result < tonic :: Response < super :: QueryUpgradedConsensusStateResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.upgrade.v1beta1.Query/UpgradedConsensusState") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for QueryClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for QueryClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "QueryClient {{ ... }}") } } } \ No newline at end of file diff --git a/relayer-cli/src/commands/tx.rs b/relayer-cli/src/commands/tx.rs index 5f1304266f..b0008a7fe0 100644 --- a/relayer-cli/src/commands/tx.rs +++ b/relayer-cli/src/commands/tx.rs @@ -1,13 +1,14 @@ //! `tx` subcommand use abscissa_core::{Command, Help, Options, Runnable}; -use crate::commands::tx::client::{TxCreateClientCmd, TxUpdateClientCmd}; +use crate::commands::tx::client::{TxCreateClientCmd, TxUpdateClientCmd, TxUpgradeClientCmd}; mod channel; mod client; mod connection; mod packet; mod transfer; +mod upgrade; /// `tx` subcommand #[derive(Command, Debug, Options, Runnable)] @@ -35,6 +36,10 @@ pub enum TxRawCommands { #[options(help = "Update the specified client on destination chain")] UpdateClient(TxUpdateClientCmd), + /// The `tx raw upgrade-client` subcommand. Submits a MsgUpgradeClient in a transaction to a chain. + #[options(help = "Upgrade the specified client on destination chain")] + UpgradeClient(TxUpgradeClientCmd), + /// The `tx raw conn-init` subcommand #[options(help = "Initialize a connection (ConnectionOpenInit)")] ConnInit(connection::TxRawConnInitCmd), @@ -86,4 +91,8 @@ pub enum TxRawCommands { /// The `tx raw packet-ack` subcommand #[options(help = "Relay acknowledgment packets")] PacketAck(packet::TxRawPacketAckCmd), + + /// The `tx raw upgrade-chain` subcommand + #[options(help = "Send an upgrade plan")] + UpgradeChain(upgrade::TxUpgradeChainCmd), } diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index d2e3098bdf..5f8a6363a8 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -1,4 +1,5 @@ use abscissa_core::{Command, Options, Runnable}; +use tracing::info; use ibc::events::IbcEvent; use ibc::ics24_host::identifier::{ChainId, ClientId}; @@ -7,7 +8,7 @@ use ibc_relayer::foreign_client::ForeignClient; use crate::application::app_config; use crate::commands::cli_utils::{ChainHandlePair, SpawnOptions}; -use crate::conclude::Output; +use crate::conclude::{exit_with_unrecoverable_error, Output}; use crate::error::{Error, Kind}; #[derive(Clone, Command, Debug, Options)] @@ -101,3 +102,52 @@ impl Runnable for TxUpdateClientCmd { } } } + +#[derive(Clone, Command, Debug, Options)] +pub struct TxUpgradeClientCmd { + #[options(free, required, help = "identifier of the destination chain")] + dst_chain_id: ChainId, + + #[options( + free, + required, + help = "identifier of the chain which underwent upgrade (source chain)" + )] + src_chain_id: ChainId, + + #[options( + free, + required, + help = "identifier of the client to be upgraded on destination chain" + )] + dst_client_id: ClientId, +} + +impl Runnable for TxUpgradeClientCmd { + fn run(&self) { + let config = app_config(); + + let spawn_options = SpawnOptions::override_store_config(StoreConfig::memory()); + let chains = ChainHandlePair::spawn_with( + spawn_options, + &config, + &self.src_chain_id, + &self.dst_chain_id, + ) + .unwrap_or_else(exit_with_unrecoverable_error); + + info!("Started the chain runtimes"); + + // Instantiate the client hosted on the destination chain, which is targeting headers for + // the source chain. + let client = ForeignClient::find(chains.src, chains.dst, &self.dst_client_id) + .unwrap_or_else(exit_with_unrecoverable_error); + + let outcome = client.upgrade(); + + match outcome { + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), + } + } +} diff --git a/relayer-cli/src/commands/tx/upgrade.rs b/relayer-cli/src/commands/tx/upgrade.rs new file mode 100644 index 0000000000..7f2d890677 --- /dev/null +++ b/relayer-cli/src/commands/tx/upgrade.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use abscissa_core::{Command, Options, Runnable}; +use tokio::runtime::Runtime as TokioRuntime; + +use ibc::events::IbcEvent; +use ibc::ics24_host::identifier::{ChainId, ClientId}; +use ibc_relayer::upgrade_chain::{build_and_send_upgrade_chain_message, UpdatePlanOptions}; +use ibc_relayer::{ + chain::{Chain, CosmosSdkChain}, + config::Config, +}; + +use crate::conclude::Output; +use crate::error::{Error, Kind}; +use crate::prelude::*; + +#[derive(Clone, Command, Debug, Options)] +pub struct TxUpgradeChainCmd { + #[options(free, required, help = "identifier of the chain to upgrade")] + dst_chain_id: ChainId, + + #[options(free, required, help = "identifier of the source chain")] + src_chain_id: ChainId, + + #[options( + free, + required, + help = "identifier of the client on source chain from which the plan is created" + )] + src_client_id: ClientId, + + #[options(free, required, help = "amount of stake")] + amount: u64, + + #[options( + free, + required, + help = "upgrade height offset in number of blocks since current" + )] + height_offset: u64, +} + +impl TxUpgradeChainCmd { + fn validate_options(&self, config: &Config) -> Result { + let src_chain_config = config + .find_chain(&self.src_chain_id) + .ok_or_else(|| "missing src chain configuration".to_string())?; + + let dst_chain_config = config + .find_chain(&self.dst_chain_id) + .ok_or_else(|| "missing destination chain configuration".to_string())?; + + let opts = UpdatePlanOptions { + dst_chain_config: dst_chain_config.clone(), + src_chain_config: src_chain_config.clone(), + src_client_id: self.src_client_id.clone(), + amount: self.amount, + height_offset: self.height_offset, + }; + + Ok(opts) + } +} + +impl Runnable for TxUpgradeChainCmd { + fn run(&self) { + let config = app_config(); + + let opts = match self.validate_options(&config) { + Err(err) => return Output::error(err).exit(), + Ok(result) => result, + }; + info!("Message {:?}", opts); + + let rt = Arc::new(TokioRuntime::new().unwrap()); + + let src_chain_res = CosmosSdkChain::bootstrap(opts.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.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_upgrade_chain_message(dst_chain, src_chain, &opts) + .map_err(|e| Kind::Tx.context(e).into()); + + match res { + Ok(ev) => Output::success(ev).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), + } + } +} diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 545242c71a..f09d02f0a5 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -130,6 +130,16 @@ pub trait Chain: Sized { height: ICSHeight, ) -> Result; + fn query_upgraded_client_state( + &self, + height: ICSHeight, + ) -> Result<(Self::ClientState, MerkleProof), Error>; + + fn query_upgraded_consensus_state( + &self, + height: ICSHeight, + ) -> Result<(Self::ConsensusState, MerkleProof), Error>; + /// Performs a query to retrieve the identifiers of all connections. fn query_connections( &self, diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 49d267f8c8..6f8ef9d6ff 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -35,7 +35,7 @@ use ibc::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use ibc::ics24_host::Path::ClientConsensusState as ClientConsensusPath; use ibc::ics24_host::Path::ClientState as ClientStatePath; -use ibc::ics24_host::{Path, IBC_QUERY_PATH}; +use ibc::ics24_host::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH}; use ibc::signer::Signer; use ibc::Height as ICSHeight; // Support for GRPC @@ -43,6 +43,9 @@ use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, TxBody, TxRaw}; +use ibc_proto::cosmos::upgrade::v1beta1::{ + QueryCurrentPlanRequest, QueryUpgradedConsensusStateRequest, +}; use ibc_proto::ibc::core::channel::v1::{ PacketState, QueryChannelsRequest, QueryConnectionChannelsRequest, QueryNextSequenceReceiveRequest, QueryPacketAcknowledgementsRequest, @@ -269,6 +272,34 @@ impl CosmosSdkChain { Ok(response) } + + // Perform an ABCI query against the client upgrade sub-store to fetch a proof. + fn query_client_upgrade_proof( + &self, + data: ClientUpgradePath, + height: Height, + ) -> Result<(MerkleProof, ICSHeight), Error> { + let prev_height = + Height::try_from(height.value() - 1).map_err(|e| Kind::InvalidHeight.context(e))?; + + let path = TendermintABCIPath::from_str(SDK_UPGRADE_QUERY_PATH).unwrap(); + let response = self.block_on(abci_query( + &self, + path, + Path::Upgrade(data).to_string(), + prev_height, + true, + ))?; + + let proof = response.proof.ok_or(Kind::EmptyResponseProof)?; + + let height = ICSHeight::new( + self.config.id.version(), + response.height.increment().value(), + ); + + Ok((proof, height)) + } } impl Chain for CosmosSdkChain { @@ -477,6 +508,105 @@ impl Chain for CosmosSdkChain { Ok(client_state) } + fn query_upgraded_client_state( + &self, + height: ICSHeight, + ) -> Result<(Self::ClientState, MerkleProof), Error> { + crate::time!("query_upgraded_client_state"); + + let grpc_address = + Uri::from_str(&self.config.grpc_addr).map_err(|e| Kind::Grpc.context(e))?; + + let mut client = self + .block_on( + ibc_proto::cosmos::upgrade::v1beta1::query_client::QueryClient::connect( + grpc_address, + ), + ) + .map_err(|e| Kind::Grpc.context(e))?; + + let req = tonic::Request::new(QueryCurrentPlanRequest {}); + let response = self + .block_on(client.current_plan(req)) + .map_err(|e| Kind::Grpc.context(e))?; + + let upgraded_client_state_raw = response + .into_inner() + .plan + .ok_or(Kind::EmptyResponseValue)? + .upgraded_client_state + .ok_or(Kind::EmptyUpgradedClientState)?; + let client_state = AnyClientState::try_from(upgraded_client_state_raw) + .map_err(|e| Kind::Grpc.context(e))?; + + // TODO: Better error kinds here. + let tm_client_state = + downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { + Kind::Query("upgraded client state".into()).context("unexpected client state type") + })?; + + // Query for the proof. + let tm_height = + Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; + let (proof, _proof_height) = self.query_client_upgrade_proof( + ClientUpgradePath::UpgradedClientState(height.revision_height), + tm_height, + )?; + + Ok((tm_client_state, proof)) + } + + fn query_upgraded_consensus_state( + &self, + height: ICSHeight, + ) -> Result<(Self::ConsensusState, MerkleProof), Error> { + crate::time!("query_upgraded_consensus_state"); + + let tm_height = + Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; + + let grpc_address = + Uri::from_str(&self.config.grpc_addr).map_err(|e| Kind::Grpc.context(e))?; + + let mut client = self + .block_on( + ibc_proto::cosmos::upgrade::v1beta1::query_client::QueryClient::connect( + grpc_address, + ), + ) + .map_err(|e| Kind::Grpc.context(e))?; + + let req = tonic::Request::new(QueryUpgradedConsensusStateRequest { + last_height: tm_height.into(), + }); + let response = self + .block_on(client.upgraded_consensus_state(req)) + .map_err(|e| Kind::Grpc.context(e))?; + + let upgraded_consensus_state_raw = response + .into_inner() + .upgraded_consensus_state + .ok_or(Kind::EmptyResponseValue)?; + + // TODO: More explicit error kinds (should not reuse Grpc all over the place) + let consensus_state = AnyConsensusState::try_from(upgraded_consensus_state_raw) + .map_err(|e| Kind::Grpc.context(e))?; + + let tm_consensus_state = downcast!(consensus_state => AnyConsensusState::Tendermint) + .ok_or_else(|| { + Kind::Query("upgraded consensus state".into()) + .context("unexpected consensus state type") + })?; + + // Fetch the proof. + let (proof, _proof_height) = self.query_client_upgrade_proof( + ClientUpgradePath::UpgradedClientConsensusState(height.revision_height), + tm_height, + )?; + + Ok((tm_consensus_state, proof)) + } + /// Performs a query to retrieve the identifiers of all connections. fn query_client_connections( &self, diff --git a/relayer/src/chain/handle.rs b/relayer/src/chain/handle.rs index 36cc9af29d..ef5c92e6c4 100644 --- a/relayer/src/chain/handle.rs +++ b/relayer/src/chain/handle.rs @@ -114,6 +114,16 @@ pub enum ChainRequest { reply_to: ReplyTo, }, + QueryUpgradedClientState { + height: Height, + reply_to: ReplyTo<(AnyClientState, MerkleProof)>, + }, + + QueryUpgradedConsensusState { + height: Height, + reply_to: ReplyTo<(AnyConsensusState, MerkleProof)>, + }, + QueryCommitmentPrefix { reply_to: ReplyTo, }, @@ -228,6 +238,16 @@ pub trait ChainHandle: DynClone + Send + Sync + Debug { height: Height, ) -> Result; + fn query_upgraded_client_state( + &self, + height: Height, + ) -> Result<(AnyClientState, MerkleProof), Error>; + + fn query_upgraded_consensus_state( + &self, + height: Height, + ) -> Result<(AnyConsensusState, MerkleProof), Error>; + fn query_commitment_prefix(&self) -> Result; fn query_compatible_versions(&self) -> Result, Error>; diff --git a/relayer/src/chain/handle/prod.rs b/relayer/src/chain/handle/prod.rs index 231720814a..c9b047d0c3 100644 --- a/relayer/src/chain/handle/prod.rs +++ b/relayer/src/chain/handle/prod.rs @@ -117,6 +117,20 @@ impl ChainHandle for ProdChainHandle { }) } + fn query_upgraded_client_state( + &self, + height: Height, + ) -> Result<(AnyClientState, MerkleProof), Error> { + self.send(|reply_to| ChainRequest::QueryUpgradedClientState { height, reply_to }) + } + + fn query_upgraded_consensus_state( + &self, + height: Height, + ) -> Result<(AnyConsensusState, MerkleProof), Error> { + self.send(|reply_to| ChainRequest::QueryUpgradedConsensusState { height, reply_to }) + } + fn query_commitment_prefix(&self) -> Result { self.send(|reply_to| ChainRequest::QueryCommitmentPrefix { reply_to }) } diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 669d6374ba..cd53871ea7 100644 --- a/relayer/src/chain/mock.rs +++ b/relayer/src/chain/mock.rs @@ -147,6 +147,13 @@ impl Chain for MockChain { Ok(client_state) } + fn query_upgraded_client_state( + &self, + _height: Height, + ) -> Result<(Self::ClientState, MerkleProof), Error> { + unimplemented!() + } + fn query_connection( &self, _connection_id: &ConnectionId, @@ -313,6 +320,13 @@ impl Chain for MockChain { trusted_validator_set: trusted_light_block.validators, }) } + + fn query_upgraded_consensus_state( + &self, + _height: Height, + ) -> Result<(Self::ConsensusState, MerkleProof), Error> { + unimplemented!() + } } // For integration tests with the modules diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 2dbf59d9bf..69ce5f2162 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -217,6 +217,14 @@ impl ChainRuntime { self.query_client_state(client_id, height, reply_to)? }, + Ok(ChainRequest::QueryUpgradedClientState { height, reply_to }) => { + self.query_upgraded_client_state(height, reply_to)? + } + + Ok(ChainRequest::QueryUpgradedConsensusState { height, reply_to }) => { + self.query_upgraded_consensus_state(height, reply_to)? + } + Ok(ChainRequest::QueryCommitmentPrefix { reply_to }) => { self.query_commitment_prefix(reply_to)? }, @@ -466,6 +474,40 @@ impl ChainRuntime { Ok(()) } + fn query_upgraded_client_state( + &self, + height: Height, + reply_to: ReplyTo<(AnyClientState, MerkleProof)>, + ) -> Result<(), Error> { + let result = self + .chain + .query_upgraded_client_state(height) + .map(|(cl, proof)| (cl.wrap_any(), proof)); + + reply_to + .send(result) + .map_err(|e| Kind::Channel.context(e))?; + + Ok(()) + } + + fn query_upgraded_consensus_state( + &self, + height: Height, + reply_to: ReplyTo<(AnyConsensusState, MerkleProof)>, + ) -> Result<(), Error> { + let result = self + .chain + .query_upgraded_consensus_state(height) + .map(|(cs, proof)| (cs.wrap_any(), proof)); + + reply_to + .send(result) + .map_err(|e| Kind::Channel.context(e))?; + + Ok(()) + } + fn query_commitment_prefix(&self, reply_to: ReplyTo) -> Result<(), Error> { let prefix = self.chain.query_commitment_prefix(); diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 176fa619f6..f2a1b87793 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -48,6 +48,10 @@ pub enum Kind { #[error("Bad Notification")] Event, + /// Missing ClientState in the upgrade CurrentPlan + #[error("The upgrade plan specifies no upgraded client state")] + EmptyUpgradedClientState, + /// Response does not contain data #[error("Empty response value")] EmptyResponseValue, diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index 4e3b3b8393..83de334202 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -2,7 +2,7 @@ use std::{thread, time::Duration}; use prost_types::Any; use thiserror::Error; -use tracing::{error, info}; +use tracing::{debug, error, info, warn}; use ibc::events::IbcEvent; use ibc::ics02_client::client_consensus::ConsensusState; @@ -10,6 +10,7 @@ use ibc::ics02_client::client_state::ClientState; use ibc::ics02_client::header::Header; use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use ibc::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; use ibc::ics24_host::identifier::{ChainId, ClientId}; use ibc::tx_msg::Msg; use ibc::Height; @@ -29,6 +30,9 @@ pub enum ForeignClientError { #[error("failed while finding client {0}: expected chain_id in client state: {1}; actual chain_id: {2}")] ClientFind(ClientId, ChainId, ChainId), + + #[error("failed while trying to upgrade client id {0} with error: {1}")] + ClientUpgrade(ClientId, String), } #[derive(Clone, Debug)] @@ -109,6 +113,87 @@ impl ForeignClient { } } + pub fn upgrade(&self) -> Result, ForeignClientError> { + // Fetch the latest height of the source chain. + let src_height = self.src_chain.query_latest_height().map_err(|e| { + ForeignClientError::ClientUpgrade( + self.id.clone(), + format!( + "failed while querying src chain ({}) for latest height: {}", + self.src_chain.id(), + e + ), + ) + })?; + + info!("Upgrade Height: {}", src_height); + + let mut msgs = self.build_update_client(src_height)?; + + // Query the host chain for the upgraded client state, consensus state & their proofs. + let (client_state, proof_upgrade_client) = self + .src_chain + .query_upgraded_client_state(src_height) + .map_err(|e| { + ForeignClientError::ClientUpgrade( + self.id.clone(), + format!( + "failed while fetching from chain {} the upgraded client state: {}", + self.src_chain.id(), + e + ), + ) + })?; + + debug!("Upgraded client state {:?}", client_state); + + 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))) + ?; + + debug!("Upgraded client consensus state {:?}", consensus_state); + + // Get signer + 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 + ), + ) + })?; + + let msg_upgrade = MsgUpgradeAnyClient { + client_id: self.id.clone(), + client_state, + consensus_state, + proof_upgrade_client, + proof_upgrade_consensus_state, + signer, + } + .to_any(); + + msgs.push(msg_upgrade); + + let res = self.dst_chain.send_msgs(msgs).map_err(|e| { + ForeignClientError::ClientUpgrade( + self.id.clone(), + format!( + "failed while sending message to destination chain {} with err: {}", + self.dst_chain.id(), + e + ), + ) + })?; + + Ok(res) + } + /// Returns a handle to the chain hosting this client. pub fn dst_chain(&self) -> Box { self.dst_chain.clone() @@ -210,6 +295,8 @@ impl ForeignClient { Ok(()) } + /// Returns a vector with a message for updating the client to height `target_height`. + /// If the client already stores consensus states for this height, returns an empty vector. pub fn build_update_client( &self, target_height: Height, @@ -237,6 +324,14 @@ impl ForeignClient { })? .latest_height(); + if trusted_height >= target_height { + warn!( + "Client height ({}) >= chain target height ({}). Cannot build update message.", + trusted_height, target_height + ); + return Ok(vec![]); + } + let header = self .src_chain() .build_header(trusted_height, target_height) @@ -272,7 +367,16 @@ impl ForeignClient { e )) })?; + let new_msgs = self.build_update_client(h)?; + if new_msgs.is_empty() { + return Err(ForeignClientError::ClientUpdate(format!( + "Client {} is already up-to-date with chain {}@{}", + self.id, + self.src_chain.id(), + h + ))); + } let mut events = self.dst_chain().send_msgs(new_msgs).map_err(|e| { ForeignClientError::ClientUpdate(format!( diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index 472f9496fd..e14841c1fb 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -27,4 +27,5 @@ pub mod macros; pub mod relay; pub mod supervisor; pub mod transfer; +pub mod upgrade_chain; pub mod util; diff --git a/relayer/src/upgrade_chain.rs b/relayer/src/upgrade_chain.rs new file mode 100644 index 0000000000..bb214597f3 --- /dev/null +++ b/relayer/src/upgrade_chain.rs @@ -0,0 +1,120 @@ +use bitcoin::hashes::core::time::Duration; +use prost_types::Any; +use thiserror::Error; +use tracing::error; + +use ibc::ics02_client::client_state::AnyClientState; +use ibc::ics02_client::height::Height; +use ibc::ics24_host::identifier::{ChainId, ClientId}; +use ibc::{events::IbcEvent, ics07_tendermint::client_state::ClientState}; +use ibc_proto::cosmos::gov::v1beta1::MsgSubmitProposal; +use ibc_proto::cosmos::upgrade::v1beta1::{Plan, SoftwareUpgradeProposal}; + +use crate::chain::{Chain, CosmosSdkChain}; +use crate::config::ChainConfig; +use crate::error::Error; + +#[derive(Debug, Error)] +pub enum UpgradeChainError { + #[error("failed with underlying cause: {0}")] + Failed(String), + + #[error("key error with underlying cause: {0}")] + KeyError(Error), + + #[error( + "failed during a transaction submission step to chain id {0} with underlying error: {1}" + )] + SubmitError(ChainId, Error), +} + +#[derive(Clone, Debug)] +pub struct UpdatePlanOptions { + pub src_chain_config: ChainConfig, + pub dst_chain_config: ChainConfig, + pub src_client_id: ClientId, + pub amount: u64, + pub height_offset: u64, +} + +pub fn build_and_send_upgrade_chain_message( + mut dst_chain: CosmosSdkChain, // the chain whose account is debited + src_chain: CosmosSdkChain, // the chain where the transfer is sent + opts: &UpdatePlanOptions, +) -> Result, UpgradeChainError> { + // build a proposal Plan + let upgrade_height = dst_chain + .query_latest_height() + .unwrap() + .add(opts.height_offset); + + let client_state = src_chain + .query_client_state(&opts.src_client_id, Height::zero()) + .unwrap(); + + let mut upgraded_client_state = ClientState::zero_custom_fields(client_state); + upgraded_client_state.latest_height = upgrade_height.increment(); + upgraded_client_state.unbonding_period = Duration::from_secs(400 * 3600); + + let raw_client_state = AnyClientState::Tendermint(upgraded_client_state); + let plan = Plan { + name: "test".to_string(), + time: None, + height: upgrade_height.revision_height as i64, + info: "upgrade the chain software and unbonding period".to_string(), + upgraded_client_state: Some(Any::from(raw_client_state)), + }; + + // build the proposal + let proposal = SoftwareUpgradeProposal { + title: "upgrade_ibc_clients".to_string(), + description: "upgrade the chain software and unbonding period".to_string(), + plan: Some(plan), + }; + + let mut buf_proposal = Vec::new(); + prost::Message::encode(&proposal, &mut buf_proposal).unwrap(); + + let any_proposal = Any { + type_url: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal".to_string(), + value: buf_proposal, + }; + + // build the msg submit proposal + let proposer = dst_chain + .get_signer() + .map_err(UpgradeChainError::KeyError)?; + + let coins = ibc_proto::cosmos::base::v1beta1::Coin { + denom: "stake".to_string(), + amount: opts.amount.to_string(), + }; + + let msg = MsgSubmitProposal { + content: Some(any_proposal), + initial_deposit: vec![coins], + proposer: proposer.to_string(), + }; + + let mut buf_msg = Vec::new(); + prost::Message::encode(&msg, &mut buf_msg).unwrap(); + let any_msg = Any { + type_url: "/cosmos.gov.v1beta1.MsgSubmitProposal".to_string(), + value: buf_msg, + }; + + let events = dst_chain + .send_msgs(vec![any_msg]) + .map_err(|e| UpgradeChainError::SubmitError(dst_chain.id().clone(), e))?; + + // Check if the chain rejected the transaction + let result = events.iter().find_map(|event| match event { + IbcEvent::ChainError(reason) => Some(reason.clone()), + _ => None, + }); + + match result { + None => Ok(events), + Some(reason) => Err(UpgradeChainError::Failed(reason)), + } +} diff --git a/scripts/dev-env b/scripts/dev-env index a5631a1c6f..1bda9bac2e 100755 --- a/scripts/dev-env +++ b/scripts/dev-env @@ -11,8 +11,8 @@ missing() { usage } -if [ -z "$1" ]; then - missing "CONFIG_FILE" +if [ ! -r "$1" ]; then + missing "CONFIG_FILE ($1)" fi if [ -z "$2" ]; then diff --git a/scripts/init-clients b/scripts/init-clients index 6cf1a674ac..333779e9ef 100755 --- a/scripts/init-clients +++ b/scripts/init-clients @@ -66,8 +66,8 @@ cargo run --bin hermes -- -c "$CONFIG_FILE" light rm -c "$CHAIN_1_ID" --all -y & # set the primary peers for clients on each chain echo "Adding primary peers to light client configuration..." -cargo run --bin hermes -- -c "$CONFIG_FILE" light add $CHAIN_0_RPC_ADDR -c "$CHAIN_0_ID" -f -p -s "$GAIA_DATA/$CHAIN_0_ID/data" -y &>/dev/null -cargo run --bin hermes -- -c "$CONFIG_FILE" light add $CHAIN_1_RPC_ADDR -c "$CHAIN_1_ID" -f -p -s "$GAIA_DATA/$CHAIN_1_ID/data" -y &>/dev/null +cargo run --bin hermes -- -c "$CONFIG_FILE" light add $CHAIN_0_RPC_ADDR -c "$CHAIN_0_ID" -f -p -s "$GAIA_DATA/$CHAIN_0_ID/data" -y &>/dev/null || printf "\tError adding primary peer to %s\n" "$CHAIN_0_RPC_ADDR" +cargo run --bin hermes -- -c "$CONFIG_FILE" light add $CHAIN_1_RPC_ADDR -c "$CHAIN_1_ID" -f -p -s "$GAIA_DATA/$CHAIN_1_ID/data" -y &>/dev/null || printf "\tError adding primary peer to %s\n" "$CHAIN_1_RPC_ADDR" # set the secondary peers for clients on each chain echo "Adding secondary peers to light client configuration..." diff --git a/scripts/one-chain b/scripts/one-chain index 6d59e857e6..e7e0fed600 100755 --- a/scripts/one-chain +++ b/scripts/one-chain @@ -105,6 +105,7 @@ fi # Set proper defaults and change ports (use a different sed for Mac or Linux) echo "Change settings in config.toml file..." if [ $platform = 'linux' ]; then + sed -i 's#"172800s"#"200s"#g' $CHAIN_DIR/$CHAIN_ID/config/genesis.json sed -i 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPC_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml sed -i 's#"tcp://0.0.0.0:26656"#"tcp://0.0.0.0:'"$P2P_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml sed -i 's#"localhost:6060"#"localhost:'"$PROF_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml @@ -113,6 +114,7 @@ if [ $platform = 'linux' ]; then sed -i 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAIN_ID/config/config.toml # sed -i '' 's#index-events = \[\]#index-events = \["message.action","send_packet.packet_src_channel","send_packet.packet_sequence"\]#g' $CHAIN_DIR/$CHAIN_ID/config/app.toml else + sed -i '' 's#"172800s"#"200s"#g' $CHAIN_DIR/$CHAIN_ID/config/genesis.json sed -i '' 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPC_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml sed -i '' 's#"tcp://0.0.0.0:26656"#"tcp://0.0.0.0:'"$P2P_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml sed -i '' 's#"localhost:6060"#"localhost:'"$PROF_PORT"'"#g' $CHAIN_DIR/$CHAIN_ID/config/config.toml @@ -131,5 +133,7 @@ $BINARY --home $CHAIN_DIR/$CHAIN_ID start --pruning=nothing --grpc.address="0.0. # Show validator's and user's balance sleep 3 RPC_ADDR="tcp://localhost:$RPC_PORT" +echo "Balances for validator '$VALIDATOR' @ '$RPC_ADDR'" $BINARY --node "$RPC_ADDR" query bank balances $VALIDATOR --log_level error +echo "Balances for user '$USER' @ '$RPC_ADDR'" $BINARY --node "$RPC_ADDR" query bank balances $USER --log_level error diff --git a/scripts/setup-chains b/scripts/setup-chains index b440582f35..f6f6a14a17 100755 --- a/scripts/setup-chains +++ b/scripts/setup-chains @@ -63,9 +63,7 @@ mkdir -p "$GAIA_DATA" && cd "$GAIA_DATA" && cd ../ ONE_CHAIN="$(dirname "$0")/one-chain" CHAIN_0_RPC_PORT=26657 -CHAIN_0_RPC_ADDR="localhost:$CHAIN_0_RPC_PORT" CHAIN_1_RPC_PORT=26557 -CHAIN_1_RPC_ADDR="localhost:$CHAIN_1_RPC_PORT" CHAIN_0_SAMOLEANS=100000000000 CHAIN_1_SAMOLEANS=100000000000