diff --git a/Cargo.toml b/Cargo.toml index 5d7413065..b60ac903f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ resolver = "2" members = [ "crates/ibc", + "crates/ibc-components", + "crates/ibc-cosmos-components", "crates/ibc-derive", "crates/ibc-query", ] @@ -12,3 +14,11 @@ exclude = [ "ci/cw-check", "ci/no-std-check", ] + +[patch.crates-io] +cgp-core = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } +cgp-component = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } +cgp-component-macro = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } +cgp-error = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } +cgp-async = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } +cgp-run = { git = "https://github.com/informalsystems/cgp.git", branch = "main" } \ No newline at end of file diff --git a/crates/ibc-components/Cargo.toml b/crates/ibc-components/Cargo.toml new file mode 100644 index 000000000..5f8d40aee --- /dev/null +++ b/crates/ibc-components/Cargo.toml @@ -0,0 +1,23 @@ + +[package] +name = "ibc-components" +version = "0.47.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/cosmos/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.60" +description = """ + Implementation of the Inter-Blockchain Communication Protocol (IBC). + This crate comprises the main data structures and on-chain logic. +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +cgp-core = { version = "0.1.0" } diff --git a/crates/ibc-components/src/lib.rs b/crates/ibc-components/src/lib.rs new file mode 100644 index 000000000..0c9ac1ac8 --- /dev/null +++ b/crates/ibc-components/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/crates/ibc-cosmos-components/Cargo.toml b/crates/ibc-cosmos-components/Cargo.toml new file mode 100644 index 000000000..3c3219796 --- /dev/null +++ b/crates/ibc-cosmos-components/Cargo.toml @@ -0,0 +1,29 @@ + +[package] +name = "ibc-cosmos-components" +version = "0.47.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/cosmos/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.60" +description = """ + Implementation of the Inter-Blockchain Communication Protocol (IBC). + This crate comprises the main data structures and on-chain logic. +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +cgp-core = { version = "0.1.0" } +ibc = { path = "../ibc" } + +[dependencies.tendermint-light-client-verifier] +version = "0.34" +default-features = false +features = ["rust-crypto"] diff --git a/crates/ibc-cosmos-components/src/client/mod.rs b/crates/ibc-cosmos-components/src/client/mod.rs new file mode 100644 index 000000000..a8118c56a --- /dev/null +++ b/crates/ibc-cosmos-components/src/client/mod.rs @@ -0,0 +1 @@ +pub mod verify; diff --git a/crates/ibc-cosmos-components/src/client/verify.rs b/crates/ibc-cosmos-components/src/client/verify.rs new file mode 100644 index 000000000..916c35c97 --- /dev/null +++ b/crates/ibc-cosmos-components/src/client/verify.rs @@ -0,0 +1,179 @@ +use alloc::format; +use core::fmt::Display; + +use cgp_core::prelude::*; +use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use ibc::clients::ics07_tendermint::error::{Error, IntoResult}; +use ibc::clients::ics07_tendermint::header::Header as TmHeader; +use ibc::core::ics02_client::error::ClientError; +use ibc::core::ics24_host::identifier::{ChainId, ClientId}; +use ibc::core::ics24_host::path::ClientConsensusStatePath; +use ibc::core::timestamp::Timestamp; +use ibc::core::ContextError; +use ibc::prelude::ToString; +use tendermint_light_client_verifier::options::Options; +use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState}; +use tendermint_light_client_verifier::{ProdVerifier, Verifier}; + +pub trait HasChainId { + fn chain_id(&self) -> &ChainId; +} + +pub trait HasLightClientOptions { + fn light_client_options(&self) -> Options; +} + +pub trait HasVerifier { + fn verifier(&self) -> &ProdVerifier; +} + +pub trait HasHostTimestamp { + fn host_timestamp(&self) -> Result; +} + +pub trait HasConsensusState: HasAnyConsensusStateType { + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result; +} + +pub trait HasAnyConsensusStateType { + type AnyConsensusState; +} + +pub trait HasClientStateType { + type ClientState; +} + +pub trait HasModule { + type ModuleId; + + type Module; +} + +pub trait CanGetRoute: HasModule { + fn get_route(&self, module_id: &Self::ModuleId) -> &Self::Module; +} + +pub struct TendermintClientType; + +#[derive_component(HeaderVerifierComponent, HeaderVerifier)] +pub trait CanVerifyHeader: HasClientStateType + HasErrorType { + fn verify_header( + &self, + client_state: &Self::ClientState, + client_id: &ClientId, + header: TmHeader, + ) -> Result<(), Self::Error>; +} + +pub struct VerifyTendermintHeader; + +impl HeaderVerifier for VerifyTendermintHeader +where + Context: HasConsensusState + + HasHostTimestamp + + HasClientStateType + + HasErrorType, + Context::AnyConsensusState: TryInto, + >::Error: Display, + Context::ClientState: HasChainId + HasLightClientOptions + HasVerifier, +{ + fn verify_header( + ctx: &Context, + client_state: &Context::ClientState, + client_id: &ClientId, + header: TmHeader, + ) -> Result<(), ClientError> + where + Context: HasConsensusState + HasHostTimestamp + HasClientStateType, + Context::AnyConsensusState: TryInto, + >::Error: Display, + Context::ClientState: HasChainId + HasLightClientOptions + HasVerifier, + { + // Checks that the header fields are valid. + header.validate_basic()?; + + // The tendermint-light-client crate though works on heights that are assumed + // to have the same revision number. We ensure this here. + header.verify_chain_id_version_matches_height(client_state.chain_id())?; + + // Delegate to tendermint-light-client, which contains the required checks + // of the new header against the trusted consensus state. + { + let trusted_state = + { + let trusted_client_cons_state_path = + ClientConsensusStatePath::new(client_id, &header.trusted_height); + let trusted_consensus_state: TmConsensusState = ctx + .consensus_state(&trusted_client_cons_state_path)? + .try_into() + .map_err(|err| ClientError::Other { + description: err.to_string(), + })?; + + check_header_trusted_next_validator_set(&header, &trusted_consensus_state)?; + + TrustedBlockState { + chain_id: &client_state + .chain_id() + .to_string() + .try_into() + .map_err(|e| ClientError::Other { + description: format!("failed to parse chain id: {}", e), + })?, + header_time: trusted_consensus_state.timestamp, + height: header.trusted_height.revision_height().try_into().map_err( + |_| ClientError::ClientSpecific { + description: Error::InvalidHeaderHeight { + height: header.trusted_height.revision_height(), + } + .to_string(), + }, + )?, + next_validators: &header.trusted_next_validator_set, + next_validators_hash: trusted_consensus_state.next_validators_hash, + } + }; + + let untrusted_state = UntrustedBlockState { + signed_header: &header.signed_header, + validators: &header.validator_set, + // NB: This will skip the + // VerificationPredicates::next_validators_match check for the + // untrusted state. + next_validators: None, + }; + + let options = client_state.light_client_options(); + let now = ctx.host_timestamp()?.into_tm_time().ok_or_else(|| { + ClientError::ClientSpecific { + description: "host timestamp is not a valid TM timestamp".to_string(), + } + })?; + + // main header verification, delegated to the tendermint-light-client crate. + client_state + .verifier() + .verify_update_header(untrusted_state, trusted_state, &options, now) + .into_result()?; + } + + Ok(()) + } +} + +fn check_header_trusted_next_validator_set( + header: &TmHeader, + trusted_consensus_state: &TmConsensusState, +) -> Result<(), ClientError> { + if header.trusted_next_validator_set.hash() == trusted_consensus_state.next_validators_hash { + Ok(()) + } else { + Err(ClientError::HeaderVerificationFailure { + reason: "header trusted next validator set hash does not match hash stored on chain" + .to_string(), + }) + } +} diff --git a/crates/ibc-cosmos-components/src/lib.rs b/crates/ibc-cosmos-components/src/lib.rs new file mode 100644 index 000000000..2e3846a0b --- /dev/null +++ b/crates/ibc-cosmos-components/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] + +extern crate alloc; + +pub mod client; diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index ae4d4d56b..ee4f0e703 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -66,6 +66,7 @@ num-traits = { version = "0.2.15", default-features = false } derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display", "try_into"] } uint = { version = "0.9", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["serde_no_std"] } +cgp-core = { version = "0.1.0" } ## for codec encode or decode parity-scale-codec = { version = "3.0.0", default-features = false, features = ["full"], optional = true } diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs b/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs index a2fabec1a..c3f8e62e2 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs @@ -23,6 +23,8 @@ impl ClientState { where ClientValidationContext: TmValidationContext, { + // VerifyTendermintHeader::verify_header(ctx, self, client_id, header) + // Checks that the header fields are valid. header.validate_basic()?; diff --git a/crates/ibc/src/clients/ics07_tendermint/error.rs b/crates/ibc/src/clients/ics07_tendermint/error.rs index 135cb1948..53e925995 100644 --- a/crates/ibc/src/clients/ics07_tendermint/error.rs +++ b/crates/ibc/src/clients/ics07_tendermint/error.rs @@ -124,7 +124,7 @@ impl From for Error { } } -pub(crate) trait IntoResult { +pub trait IntoResult { fn into_result(self) -> Result; }