From e6fa94b679b09482fc2fdb7921808e48d4a4bb52 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 5 Sep 2024 18:48:48 +0200 Subject: [PATCH] Submit misbehaviour messages using the CCV consumer id (Permissionless ICS) --- Cargo.lock | 5 +- Cargo.toml | 1 + crates/relayer-cli/src/commands/evidence.rs | 28 ++++++-- .../ics28_ccv/msgs/ccv_double_voting.rs | 4 ++ .../ics28_ccv/msgs/ccv_misbehaviour.rs | 4 ++ .../src/applications/ics28_ccv/msgs/mod.rs | 16 +++++ crates/relayer/src/chain/cosmos.rs | 60 +++++++++++++++- crates/relayer/src/foreign_client.rs | 68 +++++++++++++++---- flake.lock | 25 ++++++- flake.nix | 7 +- 10 files changed, 186 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e4f2e7523..136764926d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,9 +1549,8 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1678333cf68c9094ca66aaf9a271269f1f6bf5c26881161def8bd88cee831a23" +version = "0.47.1" +source = "git+https://github.com/cosmos/ibc-proto-rs?branch=romac/permissionless-ics#901f8cad529913555bccd9b2283553a2c66bb343" dependencies = [ "base64 0.22.1", "bytes", diff --git a/Cargo.toml b/Cargo.toml index ef8483f32a..b3e130b61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ uuid = "1.10.0" overflow-checks = true [patch.crates-io] +ibc-proto = { git = "https://github.com/cosmos/ibc-proto-rs", branch = "romac/permissionless-ics" } # tendermint = { git = "https://github.com/informalsystems/tendermint-rs.git", branch = "main" } # tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs.git", branch = "main" } # tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs.git", branch = "main" } diff --git a/crates/relayer-cli/src/commands/evidence.rs b/crates/relayer-cli/src/commands/evidence.rs index fa54be707b..f7adc601b8 100644 --- a/crates/relayer-cli/src/commands/evidence.rs +++ b/crates/relayer-cli/src/commands/evidence.rs @@ -18,7 +18,7 @@ use ibc_relayer::chain::endpoint::ChainEndpoint; use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle}; use ibc_relayer::chain::requests::{IncludeProof, PageRequest, QueryHeight}; use ibc_relayer::chain::tracking::TrackedMsgs; -use ibc_relayer::foreign_client::ForeignClient; +use ibc_relayer::foreign_client::{fetch_ccv_consumer_id, ForeignClient}; use ibc_relayer::spawn::spawn_chain_runtime_with_modified_config; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_double_voting::MsgSubmitIcsConsumerDoubleVoting; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_misbehaviour::MsgSubmitIcsConsumerMisbehaviour; @@ -323,6 +323,8 @@ fn submit_duplicate_vote_evidence( return Ok(ControlFlow::Continue(())); } + let consumer_id = fetch_ccv_consumer_id(counterparty_chain_handle, counterparty_client_id)?; + let infraction_height = evidence.vote_a.height; // Get the trusted height in the same way we do for client updates, @@ -359,6 +361,7 @@ fn submit_duplicate_vote_evidence( submitter: signer.clone(), duplicate_vote_evidence: evidence.clone(), infraction_block_header, + consumer_id, } .to_any(); @@ -578,13 +581,24 @@ fn submit_light_client_attack_evidence( counterparty.id(), ); - let msg = MsgSubmitIcsConsumerMisbehaviour { - submitter: signer.clone(), - misbehaviour: misbehaviour.clone(), + match fetch_ccv_consumer_id(counterparty, &counterparty_client_id) { + Ok(consumer_id) => { + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: misbehaviour.clone(), + consumer_id, + } + .to_any(), + ); + } + Err(e) => { + error!( + "cannot submit CCV light client attack evidence to client `{}` on provider chain `{}`: {e}", + counterparty_client_id, counterparty.id() + ); + } } - .to_any(); - - msgs.push(msg); }; // We do not need to submit the misbehaviour if the client is already frozen. diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs index a1ce4291d9..9f07f31539 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs @@ -9,6 +9,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_DOUBLE_VOTING_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVoting"; @@ -18,6 +19,7 @@ pub struct MsgSubmitIcsConsumerDoubleVoting { pub submitter: Signer, pub duplicate_vote_evidence: DuplicateVoteEvidence, pub infraction_block_header: Header, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerDoubleVoting { @@ -57,6 +59,7 @@ impl TryFrom for MsgSubmitIcsConsumerDoubleVoting { .map_err(|e| { Error::invalid_raw_double_voting(format!("cannot convert header: {e}")) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -67,6 +70,7 @@ impl From for RawIcsDoubleVoting { submitter: value.submitter.to_string(), duplicate_vote_evidence: Some(value.duplicate_vote_evidence.into()), infraction_block_header: Some(value.infraction_block_header.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs index 8b5c6c2750..67a7d20102 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs @@ -10,6 +10,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_MISBEHAVIOR_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour"; @@ -18,6 +19,7 @@ pub const ICS_MISBEHAVIOR_TYPE_URL: &str = pub struct MsgSubmitIcsConsumerMisbehaviour { pub submitter: Signer, pub misbehaviour: Misbehaviour, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerMisbehaviour { @@ -48,6 +50,7 @@ impl TryFrom for MsgSubmitIcsConsumerMisbehaviour { .map_err(|_e| { Error::invalid_raw_misbehaviour("cannot convert misbehaviour".into()) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -57,6 +60,7 @@ impl From for RawIcsMisbehaviour { RawIcsMisbehaviour { submitter: value.submitter.to_string(), misbehaviour: Some(value.misbehaviour.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs index df28e4a73a..61710c577f 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs @@ -1,3 +1,19 @@ pub mod ccv_double_voting; pub mod ccv_misbehaviour; pub mod error; + +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, Serialize, Deserialize)] +pub struct ConsumerId(String); + +impl ConsumerId { + pub const fn new(id: String) -> Self { + Self(id) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index 338a7f9af1..0416ac6efb 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -1,8 +1,10 @@ use alloc::sync::Arc; use bytes::Buf; use bytes::Bytes; +use config::CosmosSdkConfig; use core::{future::Future, str::FromStr, time::Duration}; use futures::future::join_all; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use num_bigint::BigInt; use prost::Message; use std::cmp::Ordering; @@ -366,6 +368,7 @@ impl CosmosSdkChain { } /// Performs a gRPC query to fetch CCV Consumer chain staking parameters. + /// Assumes we are the consumer chain. pub fn query_ccv_consumer_chain_params(&self) -> Result { crate::time!( "query_ccv_consumer_chain_params", @@ -399,6 +402,14 @@ impl CosmosSdkChain { Ok(params) } + /// Performs a gRPC query to fetch the CCV ConsumerID corresponding + /// to the given ClientID. + /// + /// Assumes we are the provider chain. + pub fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.block_on(query_ccv_consumer_id(&self.config, client_id)) + } + /// Performs a gRPC query for Cosmos chain staking parameters. pub fn query_staking_params(&self) -> Result { crate::time!( @@ -2533,6 +2544,9 @@ impl ChainEndpoint for CosmosSdkChain { } fn query_consumer_chains(&self) -> Result, Error> { + use ibc_proto::interchain_security::ccv::provider::v1::ConsumerPhase; + use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest; + crate::time!( "query_consumer_chains", { @@ -2546,9 +2560,10 @@ impl ChainEndpoint for CosmosSdkChain { ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient::new, ))?; - let request = tonic::Request::new( - ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest {}, - ); + let request = tonic::Request::new(QueryConsumerChainsRequest { + phase: ConsumerPhase::Launched as i32, + limit: 100, + }); let response = self .block_on(client.query_consumer_chains(request)) @@ -2774,6 +2789,45 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { Ok(()) } +/// Performs a gRPC query to fetch the CCV ConsumerID corresponding +/// to the given ClientID. +/// +/// Assumes we are the provider chain. +pub async fn query_ccv_consumer_id( + config: &CosmosSdkConfig, + client_id: &ClientId, +) -> Result { + use ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient; + use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerIdFromClientIdRequest; + + crate::telemetry!(query, &config.id, "query_ccv_consumer_id"); + crate::time!( + "query_ccv_consumer_id", + { + "src_chain": &config.id, + } + ); + + let grpc_addr = Uri::from_str(&config.grpc_addr.to_string()) + .map_err(|e| Error::invalid_uri(config.grpc_addr.to_string(), e))?; + + let mut client = create_grpc_client(grpc_addr, QueryClient::new) + .await? + .max_decoding_message_size(config.max_grpc_decoding_size.get_bytes() as usize); + + let request = tonic::Request::new(QueryConsumerIdFromClientIdRequest { + client_id: client_id.to_string(), + }); + + let response = client + .query_consumer_id_from_client_id(request) + .await + .map_err(|e| Error::grpc_status(e, "query_ccv_consumer_id".to_owned()))?; + + let consumer_id = response.into_inner().consumer_id; + Ok(ConsumerId::new(consumer_id)) +} + #[cfg(test)] mod tests { use super::calculate_fee; diff --git a/crates/relayer/src/foreign_client.rs b/crates/relayer/src/foreign_client.rs index c05a5739f4..744ddb7e04 100644 --- a/crates/relayer/src/foreign_client.rs +++ b/crates/relayer/src/foreign_client.rs @@ -9,6 +9,7 @@ use std::thread; use std::time::Instant; use ibc_proto::google::protobuf::Any; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use itertools::Itertools; use tracing::{debug, error, info, instrument, trace, warn}; @@ -31,6 +32,7 @@ use ibc_relayer_types::tx_msg::Msg; use ibc_relayer_types::Height; use crate::chain::client::ClientSettings; +use crate::chain::cosmos::query_ccv_consumer_id; use crate::chain::handle::ChainHandle; use crate::chain::requests::*; use crate::chain::tracking::TrackedMsgs; @@ -41,6 +43,7 @@ use crate::error::Error as RelayerError; use crate::event::IbcEventWithHeight; use crate::misbehaviour::{AnyMisbehaviour, MisbehaviourEvidence}; use crate::telemetry; +use crate::util::block_on; use crate::util::collate::CollatedIterExt; use crate::util::pretty::{PrettyDuration, PrettySlice}; @@ -365,7 +368,7 @@ pub enum ConsensusStateTrusted { NotTrusted { elapsed: Duration, network_timestamp: Timestamp, - consensus_state_timestmap: Timestamp, + consensus_state_timestamp: Timestamp, }, Trusted { elapsed: Duration, @@ -767,12 +770,12 @@ impl ForeignClient { error!( latest_height = %client_state.latest_height(), - network_timestmap = %network_timestamp, - consensus_state_timestamp = %consensus_state_timestmap, + network_timestamp = %network_timestamp, + consensus_state_timestamp = %consensus_state_timestamp, elapsed = ?elapsed, "client state is not valid: latest height is outside of trusting period!", ); @@ -829,7 +832,7 @@ impl ForeignClient ForeignClient ForeignClient { + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: tm_misbehaviour, + consumer_id, + } + .to_any(), + ); } - .to_any(), - ); + Err(e) => { + error!( + "cannot build CCV misbehaviour evidence: failed to fetch CCV consumer id for client {}: {}", + self.id, e + ); + } + } } msgs.push( @@ -1931,3 +1945,31 @@ pub fn extract_client_id(event: &IbcEvent) -> Result<&ClientId, ForeignClientErr )), } } + +pub fn fetch_ccv_consumer_id( + provider: &impl ChainHandle, + client_id: &ClientId, +) -> Result { + let provider_config = provider.config().map_err(|e| { + ForeignClientError::misbehaviour( + format!("failed to get config of provider chain {}", provider.id(),), + e, + ) + })?; + + let ChainConfig::CosmosSdk(provider_config) = provider_config; + + let consumer_id = + block_on(query_ccv_consumer_id(&provider_config, client_id)).map_err(|e| { + ForeignClientError::misbehaviour( + format!( + "failed to query CCV consumer id corresponding to client {} from provider {}", + client_id, + provider.id() + ), + e, + ) + })?; + + Ok(consumer_id) +} diff --git a/flake.lock b/flake.lock index ae1418f0a4..a661e135be 100644 --- a/flake.lock +++ b/flake.lock @@ -158,6 +158,7 @@ "gaia17-src": "gaia17-src", "gaia18-src": "gaia18-src", "gaia19-src": "gaia19-src", + "gaia20-src": "gaia20-src", "gaia5-src": "gaia5-src", "gaia6-ordered-src": "gaia6-ordered-src", "gaia6-src": "gaia6-src", @@ -225,15 +226,16 @@ "wasmvm_2_1_2-src": "wasmvm_2_1_2-src" }, "locked": { - "lastModified": 1725007328, - "narHash": "sha256-DMRDFFpXIJyKRNhiVhXaIoOJkmVyKbt6DO6M5F8CCec=", + "lastModified": 1725305683, + "narHash": "sha256-3yOKmHTsSXU5X2PiqCZ3WcmVSCNjQpJ4xQft7cOcgDo=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "d87f011075da3ee1afbbe3443ca25461af7d49fd", + "rev": "483a52c178745ff207ba319c02e04a859d9b80a1", "type": "github" }, "original": { "owner": "informalsystems", + "ref": "luca_joss/add-gaia-v20-alpha", "repo": "cosmos.nix", "type": "github" } @@ -673,6 +675,23 @@ "type": "github" } }, + "gaia20-src": { + "flake": false, + "locked": { + "lastModified": 1725271426, + "narHash": "sha256-ApRAGsbv2+lYKLyeIVHMrx5Gg2VetLXCs8oml0QYhCg=", + "owner": "cosmos", + "repo": "gaia", + "rev": "b5b22dc6e8eef40a6de67d62409601c6e5198fed", + "type": "github" + }, + "original": { + "owner": "cosmos", + "repo": "gaia", + "rev": "b5b22dc6e8eef40a6de67d62409601c6e5198fed", + "type": "github" + } + }, "gaia5-src": { "flake": false, "locked": { diff --git a/flake.nix b/flake.nix index 1a9ae4229a..891a19b4d8 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,9 @@ description = "Nix development dependencies for ibc-rs"; inputs = { - nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; - flake-utils.url = github:numtide/flake-utils; - cosmos-nix.url = github:informalsystems/cosmos.nix; + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + cosmos-nix.url = "github:informalsystems/cosmos.nix/luca_joss/add-gaia-v20-alpha"; }; outputs = inputs: let @@ -33,6 +33,7 @@ evmos gaia6-ordered gaia18 + gaia20 ibc-go-v2-simapp ibc-go-v3-simapp ibc-go-v4-simapp