From 65d84464842b3620f0bd66a07af30705bfa37761 Mon Sep 17 00:00:00 2001 From: Emmanuel Ekpenyong <39572181+gr4yha7@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:56:08 +0100 Subject: [PATCH 1/6] fix(ibc-clients/tendermint): disallow empty proof-specs and empty depth range in proof-specs (#1104) * fix: disallow empty proof-specs and empty depth range in proof-specs * chore: add changelog entry * nits * test: add unit test * chore: use default option (none) * avoid new var * nit in changelog * rm redundant clone * propagate errors * proof depth is unsigned * use impl method --------- Co-authored-by: Ranadeep Biswas Co-authored-by: Rano | Ranadeep --- ...-fix-tendermint-empty-proof-specs-check.md | 2 ++ .../types/src/client_state.rs | 24 +++++++++++++------ .../ics07-tendermint/types/src/error.rs | 9 +++++++ ibc-core/ics23-commitment/types/src/error.rs | 4 ++++ ibc-core/ics23-commitment/types/src/specs.rs | 20 ++++++++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 .changelog/unreleased/bug-fixes/1100-fix-tendermint-empty-proof-specs-check.md diff --git a/.changelog/unreleased/bug-fixes/1100-fix-tendermint-empty-proof-specs-check.md b/.changelog/unreleased/bug-fixes/1100-fix-tendermint-empty-proof-specs-check.md new file mode 100644 index 0000000000..b4a3aa5421 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1100-fix-tendermint-empty-proof-specs-check.md @@ -0,0 +1,2 @@ +- [ibc-client-tendermint-types] Check ics23 proof specs for empty depth range. + ([\#1100](https://github.com/cosmos/ibc-rs/issues/1100)) diff --git a/ibc-clients/ics07-tendermint/types/src/client_state.rs b/ibc-clients/ics07-tendermint/types/src/client_state.rs index 5fbb531894..98ed611322 100644 --- a/ibc-clients/ics07-tendermint/types/src/client_state.rs +++ b/ibc-clients/ics07-tendermint/types/src/client_state.rs @@ -176,12 +176,8 @@ impl ClientState { }); } - // Disallow empty proof-specs - if self.proof_specs.is_empty() { - return Err(Error::Validation { - reason: "ClientState proof-specs cannot be empty".to_string(), - }); - } + // Sanity checks on client proof specs + self.proof_specs.validate()?; // `upgrade_path` itself may be empty, but if not then each key must be non-empty for (idx, key) in self.upgrade_path.iter().enumerate() { @@ -565,7 +561,21 @@ mod tests { Test { name: "Invalid (empty) proof specs".to_string(), params: ClientStateParams { - proof_specs: ProofSpecs::from(Vec::::new()), + proof_specs: Vec::::new().into(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Invalid (empty) proof specs depth range".to_string(), + params: ClientStateParams { + proof_specs: vec![Ics23ProofSpec { + leaf_spec: None, + inner_spec: None, + min_depth: 2, + max_depth: 1, + prehash_key_before_comparison: false, + }].into(), ..default_params }, want_pass: false, diff --git a/ibc-clients/ics07-tendermint/types/src/error.rs b/ibc-clients/ics07-tendermint/types/src/error.rs index 556d5e6fe5..3e2a511126 100644 --- a/ibc-clients/ics07-tendermint/types/src/error.rs +++ b/ibc-clients/ics07-tendermint/types/src/error.rs @@ -5,6 +5,7 @@ use core::time::Duration; use displaydoc::Display; use ibc_core_client_types::error::ClientError; use ibc_core_client_types::Height; +use ibc_core_commitment_types::error::CommitmentError; use ibc_core_host_types::error::IdentifierError; use ibc_core_host_types::identifiers::ClientId; use ibc_primitives::prelude::*; @@ -35,6 +36,8 @@ pub enum Error { MissingSignedHeader, /// invalid header, failed basic validation: `{reason}` Validation { reason: String }, + /// invalid client proof specs: `{0}` + InvalidProofSpec(CommitmentError), /// invalid raw client state: `{reason}` InvalidRawClientState { reason: String }, /// missing validator set @@ -119,6 +122,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: CommitmentError) -> Self { + Self::InvalidProofSpec(e) + } +} + pub trait IntoResult { fn into_result(self) -> Result; } diff --git a/ibc-core/ics23-commitment/types/src/error.rs b/ibc-core/ics23-commitment/types/src/error.rs index 88625226ff..0d9cf21b1a 100644 --- a/ibc-core/ics23-commitment/types/src/error.rs +++ b/ibc-core/ics23-commitment/types/src/error.rs @@ -13,6 +13,10 @@ pub enum CommitmentError { EmptyMerkleRoot, /// empty verified value EmptyVerifiedValue, + /// empty proof specs + EmptyProofSpecs, + /// invalid depth range: [{0}, {1}] + InvalidDepthRange(i32, i32), /// mismatch between the number of proofs with that of specs NumberOfSpecsMismatch, /// mismatch between the number of proofs with that of keys diff --git a/ibc-core/ics23-commitment/types/src/specs.rs b/ibc-core/ics23-commitment/types/src/specs.rs index 3c2b1df75e..2e18094a08 100644 --- a/ibc-core/ics23-commitment/types/src/specs.rs +++ b/ibc-core/ics23-commitment/types/src/specs.rs @@ -2,6 +2,8 @@ use ibc_primitives::prelude::*; use ibc_proto::ics23::{InnerSpec as RawInnerSpec, LeafOp as RawLeafOp, ProofSpec as RawProofSpec}; + +use crate::error::CommitmentError; /// An array of proof specifications. /// /// This type encapsulates different types of proof specifications, mostly predefined, e.g., for @@ -23,6 +25,24 @@ impl ProofSpecs { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + pub fn validate(&self) -> Result<(), CommitmentError> { + if self.is_empty() { + return Err(CommitmentError::EmptyProofSpecs); + } + for proof_spec in &self.0 { + if proof_spec.0.max_depth < proof_spec.0.min_depth + || proof_spec.0.min_depth < 0 + || proof_spec.0.max_depth < 0 + { + return Err(CommitmentError::InvalidDepthRange( + proof_spec.0.min_depth, + proof_spec.0.max_depth, + )); + } + } + Ok(()) + } } impl Default for ProofSpecs { From 4f0ab6aaee3b4a1014fffbbcc58e87a75ea24c56 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Thu, 7 Mar 2024 20:29:49 -0800 Subject: [PATCH 2/6] imp(api)!: refactor client relevant APIs to allow independent ICS-02 integration (#1115) * imp: refactor client-related APIs to allow independent ICS-02 integration * chore: add unclog * imp: keep self methods in ICS24 context + better signature for validate_self_client * nit: revert naming to host_consensus_state * imp: use client prelude for context relevant imports * nit: rename to HostClient/ConsensusState * imp: use trait bounds instead of ClientStateMut * imp: move away from AnyConsensusState assoc type at ICS07 * docs: add a docstring for client_update_meta * chore: further clean-ups * fix: revert to the initial design approach using ClientStateMut --- .../1114-refactor-client-relevant-apis.md | 3 + ci/no-std-check/Cargo.lock | 1 + .../src/client_state/execution.rs | 70 +++++---- .../src/client_state/misbehaviour.rs | 19 +-- .../src/client_state/update_client.rs | 37 ++--- .../src/client_state/validation.rs | 34 ++--- ibc-clients/ics07-tendermint/src/context.rs | 57 +++++--- .../ics02-client/context/src/client_state.rs | 12 +- ibc-core/ics02-client/context/src/context.rs | 47 ++++-- ibc-core/ics02-client/context/src/lib.rs | 7 + .../ics02-client/src/handler/create_client.rs | 26 ++-- .../ics02-client/src/handler/update_client.rs | 41 ++---- .../src/handler/upgrade_client.rs | 19 +-- ibc-core/ics03-connection/src/delay.rs | 2 +- .../src/handler/conn_open_ack.rs | 17 ++- .../src/handler/conn_open_confirm.rs | 12 +- .../src/handler/conn_open_init.rs | 8 +- .../src/handler/conn_open_try.rs | 18 ++- ibc-core/ics04-channel/src/context.rs | 36 +---- .../src/handler/acknowledgement.rs | 11 +- .../src/handler/chan_close_confirm.rs | 11 +- .../src/handler/chan_close_init.rs | 7 +- .../src/handler/chan_open_ack.rs | 9 +- .../src/handler/chan_open_confirm.rs | 9 +- .../src/handler/chan_open_init.rs | 5 +- .../src/handler/chan_open_try.rs | 9 +- .../ics04-channel/src/handler/recv_packet.rs | 9 +- .../ics04-channel/src/handler/send_packet.rs | 10 +- ibc-core/ics04-channel/src/handler/timeout.rs | 9 +- .../src/handler/timeout_on_close.rs | 9 +- .../cosmos/src/upgrade_proposal/context.rs | 21 +-- .../cosmos/src/upgrade_proposal/handler.rs | 3 +- .../cosmos/src/upgrade_proposal/mod.rs | 2 +- .../cosmos/src/validate_self_client.rs | 55 ++++--- ibc-core/ics24-host/src/context.rs | 63 ++++---- ibc-core/ics25-handler/src/entrypoint.rs | 9 +- ibc-query/src/core/channel/query.rs | 13 +- ibc-query/src/core/channel/service.rs | 11 +- ibc-query/src/core/client/query.rs | 30 ++-- ibc-query/src/core/client/service.rs | 24 ++- ibc-query/src/core/connection/query.rs | 15 +- ibc-query/src/core/connection/service.rs | 11 +- ibc-query/src/core/context.rs | 8 +- ibc-testkit/src/relayer/context.rs | 1 + .../testapp/ibc/clients/mock/client_state.rs | 59 ++++---- ibc-testkit/src/testapp/ibc/clients/mod.rs | 31 +++- .../src/testapp/ibc/core/client_ctx.rs | 91 +++++++----- ibc-testkit/src/testapp/ibc/core/core_ctx.rs | 137 ++++++------------ .../tests/core/ics02_client/create_client.rs | 7 +- .../tests/core/ics02_client/update_client.rs | 6 +- .../tests/core/ics02_client/upgrade_client.rs | 2 +- 51 files changed, 582 insertions(+), 581 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/1114-refactor-client-relevant-apis.md diff --git a/.changelog/unreleased/breaking-changes/1114-refactor-client-relevant-apis.md b/.changelog/unreleased/breaking-changes/1114-refactor-client-relevant-apis.md new file mode 100644 index 0000000000..42bf3fa7ed --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1114-refactor-client-relevant-apis.md @@ -0,0 +1,3 @@ +- [ibc] Refactor client relevant APIs for improved modularity and allow + standalone ICS-02 integration + ([\#1114](https://github.com/cosmos/ibc-rs/issues/1114)) diff --git a/ci/no-std-check/Cargo.lock b/ci/no-std-check/Cargo.lock index 038afd5a6b..fb12ae90cf 100644 --- a/ci/no-std-check/Cargo.lock +++ b/ci/no-std-check/Cargo.lock @@ -1306,6 +1306,7 @@ dependencies = [ name = "ibc-client-tendermint" version = "0.50.0" dependencies = [ + "derive_more", "ibc-client-tendermint-types", "ibc-core-client", "ibc-core-commitment-types", diff --git a/ibc-clients/ics07-tendermint/src/client_state/execution.rs b/ibc-clients/ics07-tendermint/src/client_state/execution.rs index 0cc6c436dc..c42950ca0f 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/execution.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/execution.rs @@ -1,25 +1,25 @@ use ibc_client_tendermint_types::{ ClientState as ClientStateType, ConsensusState as ConsensusStateType, Header as TmHeader, }; -use ibc_core_client::context::client_state::ClientStateExecution; -use ibc_core_client::context::ClientExecutionContext; +use ibc_core_client::context::prelude::*; use ibc_core_client::types::error::ClientError; use ibc_core_client::types::Height; use ibc_core_host::types::identifiers::ClientId; use ibc_core_host::types::path::{ClientConsensusStatePath, ClientStatePath}; -use ibc_core_host::ExecutionContext; use ibc_primitives::prelude::*; use ibc_primitives::proto::Any; use super::ClientState; -use crate::consensus_state::ConsensusState as TmConsensusState; -use crate::context::{CommonContext, ExecutionContext as TmExecutionContext}; +use crate::context::{ + ConsensusStateConverter, ExecutionContext as TmExecutionContext, + ValidationContext as TmValidationContext, +}; impl ClientStateExecution for ClientState where - E: TmExecutionContext + ExecutionContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E: TmExecutionContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { fn initialise( &self, @@ -77,12 +77,12 @@ pub fn initialise( consensus_state: Any, ) -> Result<(), ClientError> where - E: TmExecutionContext + ExecutionContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E: TmExecutionContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { - let host_timestamp = CommonContext::host_timestamp(ctx)?; - let host_height = CommonContext::host_height(ctx)?; + let host_timestamp = TmValidationContext::host_timestamp(ctx)?; + let host_height = TmValidationContext::host_height(ctx)?; let tm_consensus_state = ConsensusStateType::try_from(consensus_state)?; @@ -122,9 +122,9 @@ pub fn update_state( header: Any, ) -> Result, ClientError> where - E: TmExecutionContext + ExecutionContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E: TmExecutionContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { let header = TmHeader::try_from(header)?; let header_height = header.height(); @@ -138,7 +138,7 @@ where header_height.revision_height(), ); - CommonContext::consensus_state(ctx, &path_at_header_height).ok() + ctx.consensus_state(&path_at_header_height).ok() }; if maybe_existing_consensus_state.is_some() { @@ -147,8 +147,8 @@ where // // Do nothing. } else { - let host_timestamp = CommonContext::host_timestamp(ctx)?; - let host_height = CommonContext::host_height(ctx)?; + let host_timestamp = TmValidationContext::host_timestamp(ctx)?; + let host_height = TmValidationContext::host_height(ctx)?; let new_consensus_state = ConsensusStateType::from(header.clone()); let new_client_state = client_state.clone().with_header(header)?; @@ -189,9 +189,9 @@ pub fn update_on_misbehaviour( _client_message: Any, ) -> Result<(), ClientError> where - E: TmExecutionContext + ExecutionContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E: TmExecutionContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { // NOTE: frozen height is set to `Height {revision_height: 0, // revision_number: 1}` and it is the same for all misbehaviour. This @@ -221,12 +221,12 @@ pub fn update_on_upgrade( upgraded_consensus_state: Any, ) -> Result where - E: TmExecutionContext + ExecutionContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E: TmExecutionContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { let mut upgraded_tm_client_state = ClientState::try_from(upgraded_client_state)?; - let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?; + let upgraded_tm_cons_state = ConsensusStateType::try_from(upgraded_consensus_state)?; upgraded_tm_client_state.0.zero_custom_fields(); @@ -263,12 +263,12 @@ where let new_consensus_state = ConsensusStateType::new( sentinel_root.into(), upgraded_tm_cons_state.timestamp(), - upgraded_tm_cons_state.next_validators_hash(), + upgraded_tm_cons_state.next_validators_hash, ); let latest_height = new_client_state.latest_height; - let host_timestamp = CommonContext::host_timestamp(ctx)?; - let host_height = CommonContext::host_height(ctx)?; + let host_timestamp = TmValidationContext::host_timestamp(ctx)?; + let host_height = TmValidationContext::host_height(ctx)?; ctx.store_client_state( ClientStatePath::new(client_id.clone()), @@ -301,7 +301,9 @@ pub fn prune_oldest_consensus_state( client_id: &ClientId, ) -> Result<(), ClientError> where - E: ClientExecutionContext + CommonContext, + E: ClientExecutionContext + TmValidationContext, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { let mut heights = ctx.consensus_state_heights(client_id)?; @@ -313,12 +315,8 @@ where height.revision_number(), height.revision_height(), ); - let consensus_state = CommonContext::consensus_state(ctx, &client_consensus_state_path)?; - let tm_consensus_state = consensus_state - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })?; + let consensus_state = ctx.consensus_state(&client_consensus_state_path)?; + let tm_consensus_state = consensus_state.try_into()?; let host_timestamp = ctx.host_timestamp()? diff --git a/ibc-clients/ics07-tendermint/src/client_state/misbehaviour.rs b/ibc-clients/ics07-tendermint/src/client_state/misbehaviour.rs index 777bca1da5..63d4c47b0d 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/misbehaviour.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/misbehaviour.rs @@ -11,7 +11,7 @@ use ibc_primitives::Timestamp; use tendermint_light_client_verifier::Verifier; use super::TmValidationContext; -use crate::context::TmVerifier; +use crate::context::{ConsensusStateConverter, TmVerifier}; /// Determines whether or not two conflicting headers at the same height would /// have convinced the light client. @@ -24,6 +24,7 @@ pub fn verify_misbehaviour( ) -> Result<(), ClientError> where V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { misbehaviour.validate_basic()?; @@ -36,11 +37,7 @@ where ); let consensus_state = ctx.consensus_state(&consensus_state_path)?; - consensus_state - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })? + consensus_state.try_into()? }; let header_2 = misbehaviour.header2(); @@ -52,11 +49,7 @@ where ); let consensus_state = ctx.consensus_state(&consensus_state_path)?; - consensus_state - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })? + consensus_state.try_into()? }; let current_timestamp = ctx.host_timestamp()?; @@ -64,14 +57,14 @@ where verify_misbehaviour_header( client_state, header_1, - trusted_consensus_state_1.inner(), + &trusted_consensus_state_1, current_timestamp, verifier, )?; verify_misbehaviour_header( client_state, header_2, - trusted_consensus_state_2.inner(), + &trusted_consensus_state_2, current_timestamp, verifier, ) diff --git a/ibc-clients/ics07-tendermint/src/client_state/update_client.rs b/ibc-clients/ics07-tendermint/src/client_state/update_client.rs index b5be61f497..d6dbaa2c86 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/update_client.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/update_client.rs @@ -9,8 +9,9 @@ use ibc_primitives::prelude::*; use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState}; use tendermint_light_client_verifier::Verifier; -use crate::consensus_state::ConsensusState as TmConsensusState; -use crate::context::{TmVerifier, ValidationContext as TmValidationContext}; +use crate::context::{ + ConsensusStateConverter, TmVerifier, ValidationContext as TmValidationContext, +}; pub fn verify_header( client_state: &ClientStateType, @@ -21,6 +22,7 @@ pub fn verify_header( ) -> Result<(), ClientError> where V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { // Checks that the header fields are valid. header.validate_basic()?; @@ -38,14 +40,11 @@ where header.trusted_height.revision_number(), header.trusted_height.revision_height(), ); - let trusted_consensus_state: TmConsensusState = ctx + let trusted_consensus_state = ctx .consensus_state(&trusted_client_cons_state_path)? - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })?; + .try_into()?; - header.check_trusted_next_validator_set(trusted_consensus_state.inner())?; + header.check_trusted_next_validator_set(&trusted_consensus_state)?; TrustedBlockState { chain_id: &client_state.chain_id.to_string().try_into().map_err(|e| { @@ -65,7 +64,7 @@ where .to_string(), })?, next_validators: &header.trusted_next_validator_set, - next_validators_hash: trusted_consensus_state.next_validators_hash(), + next_validators_hash: trusted_consensus_state.next_validators_hash, } }; @@ -106,6 +105,7 @@ pub fn check_for_misbehaviour_update_client( ) -> Result where V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { let maybe_existing_consensus_state = { let path_at_header_height = ClientConsensusStatePath::new( @@ -119,15 +119,9 @@ where match maybe_existing_consensus_state { Some(existing_consensus_state) => { - let existing_consensus_state = - existing_consensus_state - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })?; + let existing_consensus_state = existing_consensus_state.try_into()?; - let header_consensus_state = - TmConsensusState::from(ConsensusStateType::from(header.clone())); + let header_consensus_state = ConsensusStateType::from(header.clone()); // There is evidence of misbehaviour if the stored consensus state // is different from the new one we received. @@ -144,10 +138,7 @@ where if let Some(prev_cs) = maybe_prev_cs { // New header timestamp cannot occur *before* the // previous consensus state's height - let prev_cs: TmConsensusState = - prev_cs.try_into().map_err(|err| ClientError::Other { - description: err.to_string(), - })?; + let prev_cs = prev_cs.try_into()?; if header.signed_header.header().time <= prev_cs.timestamp() { return Ok(true); @@ -163,9 +154,7 @@ where if let Some(next_cs) = maybe_next_cs { // New (untrusted) header timestamp cannot occur *after* next // consensus state's height - let next_cs = next_cs.try_into().map_err(|err| ClientError::Other { - description: err.to_string(), - })?; + let next_cs = next_cs.try_into()?; if header.signed_header.header().time >= next_cs.timestamp() { return Ok(true); diff --git a/ibc-clients/ics07-tendermint/src/client_state/validation.rs b/ibc-clients/ics07-tendermint/src/client_state/validation.rs index 0b79e9d459..876c43a16a 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/validation.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/validation.rs @@ -3,7 +3,6 @@ use ibc_client_tendermint_types::{ TENDERMINT_HEADER_TYPE_URL, TENDERMINT_MISBEHAVIOUR_TYPE_URL, }; use ibc_core_client::context::client_state::ClientStateValidation; -use ibc_core_client::context::ClientValidationContext; use ibc_core_client::types::error::ClientError; use ibc_core_client::types::Status; use ibc_core_host::types::identifiers::ClientId; @@ -15,14 +14,14 @@ use super::{ check_for_misbehaviour_misbehavior, check_for_misbehaviour_update_client, ClientState, }; use crate::client_state::{verify_header, verify_misbehaviour}; -use crate::consensus_state::ConsensusState as TmConsensusState; -use crate::context::{DefaultVerifier, TmVerifier, ValidationContext as TmValidationContext}; +use crate::context::{ + ConsensusStateConverter, DefaultVerifier, TmVerifier, ValidationContext as TmValidationContext, +}; impl ClientStateValidation for ClientState where - V: ClientValidationContext + TmValidationContext, - V::AnyConsensusState: TryInto, - ClientError: From<>::Error>, + V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { /// The default verification logic exposed by ibc-rs simply delegates to a /// standalone `verify_client_message` function. This is to make it as simple @@ -74,7 +73,8 @@ pub fn verify_client_message( verifier: &impl TmVerifier, ) -> Result<(), ClientError> where - V: ClientValidationContext + TmValidationContext, + V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { match client_message.type_url.as_str() { TENDERMINT_HEADER_TYPE_URL => { @@ -128,7 +128,8 @@ pub fn check_for_misbehaviour( client_message: Any, ) -> Result where - V: ClientValidationContext + TmValidationContext, + V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { match client_message.type_url.as_str() { TENDERMINT_HEADER_TYPE_URL => { @@ -154,29 +155,24 @@ pub fn status( client_id: &ClientId, ) -> Result where - V: ClientValidationContext + TmValidationContext, + V: TmValidationContext, + V::ConsensusStateRef: ConsensusStateConverter, { if client_state.is_frozen() { return Ok(Status::Frozen); } - let latest_consensus_state: TmConsensusState = { - let any_latest_consensus_state = match ctx.consensus_state(&ClientConsensusStatePath::new( + let latest_consensus_state = { + match ctx.consensus_state(&ClientConsensusStatePath::new( client_id.clone(), client_state.latest_height.revision_number(), client_state.latest_height.revision_height(), )) { - Ok(cs) => cs, + Ok(cs) => cs.try_into()?, // if the client state does not have an associated consensus state for its latest height // then it must be expired Err(_) => return Ok(Status::Expired), - }; - - any_latest_consensus_state - .try_into() - .map_err(|err| ClientError::Other { - description: err.to_string(), - })? + } }; // Note: if the `duration_since()` is `None`, indicating that the latest diff --git a/ibc-clients/ics07-tendermint/src/context.rs b/ibc-clients/ics07-tendermint/src/context.rs index cd998f6df1..9b61df1d7f 100644 --- a/ibc-clients/ics07-tendermint/src/context.rs +++ b/ibc-clients/ics07-tendermint/src/context.rs @@ -1,62 +1,71 @@ -use ibc_core_client::context::ClientExecutionContext; +use ibc_client_tendermint_types::ConsensusState as ConsensusStateType; +use ibc_core_client::context::prelude::*; +use ibc_core_client::types::error::ClientError; use ibc_core_client::types::Height; use ibc_core_handler_types::error::ContextError; use ibc_core_host::types::identifiers::ClientId; -use ibc_core_host::types::path::ClientConsensusStatePath; use ibc_primitives::prelude::*; use ibc_primitives::Timestamp; use tendermint_light_client_verifier::ProdVerifier; -use crate::consensus_state::ConsensusState as TmConsensusState; +/// Enables conversion (`TryInto` and `From`) between the consensus state type +/// used by the host and the one specific to the Tendermint light client, which +/// is `ConsensusStateType`. +pub trait ConsensusStateConverter: + TryInto + From +{ +} -/// Client's context required during both validation and execution -pub trait CommonContext { - type ConversionError: ToString; - type AnyConsensusState: TryInto; +impl ConsensusStateConverter for C where + C: TryInto + From +{ +} +/// Client's context required during validation +pub trait ValidationContext: ClientValidationContext +where + Self::ConsensusStateRef: ConsensusStateConverter, +{ /// Returns the current timestamp of the local chain. fn host_timestamp(&self) -> Result; /// Returns the current height of the local chain. fn host_height(&self) -> Result; - /// Retrieve the consensus state for the given client ID at the specified - /// height. - /// - /// Returns an error if no such state exists. - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result; - /// Returns all the heights at which a consensus state is stored fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError>; -} -/// Client's context required during validation -pub trait ValidationContext: CommonContext { /// Search for the lowest consensus state higher than `height`. fn next_consensus_state( &self, client_id: &ClientId, height: &Height, - ) -> Result, ContextError>; + ) -> Result, ContextError>; /// Search for the highest consensus state lower than `height`. fn prev_consensus_state( &self, client_id: &ClientId, height: &Height, - ) -> Result, ContextError>; + ) -> Result, ContextError>; } /// Client's context required during execution. /// /// This trait is automatically implemented for all types that implement -/// [`CommonContext`] and [`ClientExecutionContext`] -pub trait ExecutionContext: CommonContext + ClientExecutionContext {} +/// [`ValidationContext`] and [`ClientExecutionContext`] +pub trait ExecutionContext: ValidationContext + ClientExecutionContext +where + Self::ConsensusStateRef: ConsensusStateConverter, +{ +} -impl ExecutionContext for T where T: CommonContext + ClientExecutionContext {} +impl ExecutionContext for T +where + T: ValidationContext + ClientExecutionContext, + T::ConsensusStateRef: ConsensusStateConverter, +{ +} /// Specifies the Verifier interface that hosts must adhere to when customizing /// Tendermint client verification behaviour. diff --git a/ibc-core/ics02-client/context/src/client_state.rs b/ibc-core/ics02-client/context/src/client_state.rs index 0616d0bd41..0d1c57a378 100644 --- a/ibc-core/ics02-client/context/src/client_state.rs +++ b/ibc-core/ics02-client/context/src/client_state.rs @@ -10,11 +10,17 @@ use ibc_core_host_types::path::Path; use ibc_primitives::prelude::*; use ibc_primitives::proto::Any; +/// Convenient trait to decode a client state from an `Any` type and obtain a +/// handle to the local instance of `ClientState`. +pub trait ClientStateDecoder: Into + TryFrom {} + +impl ClientStateDecoder for T where T: Into + TryFrom {} + /// `ClientState` methods needed in both validation and execution. /// /// They do not require access to a client `ValidationContext` nor /// `ExecutionContext`. -pub trait ClientStateCommon { +pub trait ClientStateCommon: ClientStateDecoder { /// Performs basic validation on the `consensus_state`. /// /// Notably, an implementation should verify that it can properly @@ -90,7 +96,7 @@ pub trait ClientStateCommon { /// // My Context methods /// } /// ``` -pub trait ClientStateValidation +pub trait ClientStateValidation: ClientStateCommon where V: ClientValidationContext, { @@ -126,7 +132,7 @@ where /// The generic type `E` enables light client developers to expand the set of /// methods available under the [`ClientExecutionContext`] trait and use them in /// their implementation for executing a client state transition. -pub trait ClientStateExecution +pub trait ClientStateExecution: ClientStateValidation where E: ClientExecutionContext, { diff --git a/ibc-core/ics02-client/context/src/context.rs b/ibc-core/ics02-client/context/src/context.rs index a09673bb76..1b4e98e525 100644 --- a/ibc-core/ics02-client/context/src/context.rs +++ b/ibc-core/ics02-client/context/src/context.rs @@ -4,17 +4,36 @@ use ibc_core_host_types::identifiers::ClientId; use ibc_core_host_types::path::{ClientConsensusStatePath, ClientStatePath}; use ibc_primitives::Timestamp; -use super::client_state::ClientState; -use super::consensus_state::ConsensusState; +use crate::client_state::{ClientStateExecution, ClientStateValidation}; +use crate::consensus_state::ConsensusState; /// Defines the methods available to clients for validating client state /// transitions. The generic `V` parameter in /// [crate::client_state::ClientStateValidation] must /// inherit from this trait. -pub trait ClientValidationContext { - /// Returns the time and height when the client state for the given - /// [`ClientId`] was updated with a header for the given [`Height`] - fn update_meta( +pub trait ClientValidationContext: Sized { + type ClientStateRef: ClientStateValidation; + type ConsensusStateRef: ConsensusState; + + /// Returns the ClientState for the given identifier `client_id`. + /// + /// Note: Clients have the responsibility to store client states on client creation and update. + fn client_state(&self, client_id: &ClientId) -> Result; + + /// Retrieve the consensus state for the given client ID at the specified + /// height. + /// + /// Returns an error if no such state exists. + /// + /// Note: Clients have the responsibility to store consensus states on client creation and update. + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result; + + /// Returns the timestamp and height of the host when it processed a client + /// update request at the specified height. + fn client_update_meta( &self, client_id: &ClientId, height: &Height, @@ -29,23 +48,27 @@ pub trait ClientValidationContext { /// Specifically, clients have the responsibility to store their client state /// and consensus states. This trait defines a uniform interface to do that for /// all clients. -pub trait ClientExecutionContext: Sized { - type V: ClientValidationContext; - type AnyClientState: ClientState; - type AnyConsensusState: ConsensusState; +pub trait ClientExecutionContext: + ClientValidationContext +{ + type ClientStateMut: ClientStateExecution; + + fn client_state_mut(&self, client_id: &ClientId) -> Result { + self.client_state(client_id) + } /// Called upon successful client creation and update fn store_client_state( &mut self, client_state_path: ClientStatePath, - client_state: Self::AnyClientState, + client_state: Self::ClientStateRef, ) -> Result<(), ContextError>; /// Called upon successful client creation and update fn store_consensus_state( &mut self, consensus_state_path: ClientConsensusStatePath, - consensus_state: Self::AnyConsensusState, + consensus_state: Self::ConsensusStateRef, ) -> Result<(), ContextError>; /// Delete the consensus state from the store located at the given `ClientConsensusStatePath` diff --git a/ibc-core/ics02-client/context/src/lib.rs b/ibc-core/ics02-client/context/src/lib.rs index 65d41640d2..7b873f38f8 100644 --- a/ibc-core/ics02-client/context/src/lib.rs +++ b/ibc-core/ics02-client/context/src/lib.rs @@ -24,6 +24,13 @@ pub mod consensus_state; mod context; pub use context::*; +/// Trait preludes for the ICS-02 client implementation. +pub mod prelude { + pub use crate::client_state::*; + pub use crate::consensus_state::*; + pub use crate::context::*; +} + pub mod types { #[doc(inline)] pub use ibc_core_client_types::*; diff --git a/ibc-core/ics02-client/src/handler/create_client.rs b/ibc-core/ics02-client/src/handler/create_client.rs index e0a47e05b1..4a4148251d 100644 --- a/ibc-core/ics02-client/src/handler/create_client.rs +++ b/ibc-core/ics02-client/src/handler/create_client.rs @@ -1,14 +1,12 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgCreateClient`. -use ibc_core_client_context::client_state::{ - ClientStateCommon, ClientStateExecution, ClientStateValidation, -}; +use ibc_core_client_context::prelude::*; use ibc_core_client_types::error::ClientError; use ibc_core_client_types::events::CreateClient; use ibc_core_client_types::msgs::MsgCreateClient; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; -use ibc_core_host::{ExecutionContext, ValidationContext}; +use ibc_core_host::{ClientStateMut, ClientStateRef, ExecutionContext, ValidationContext}; use ibc_primitives::prelude::*; pub fn validate(ctx: &Ctx, msg: MsgCreateClient) -> Result<(), ContextError> @@ -26,11 +24,13 @@ where // Construct this client's identifier let id_counter = ctx.client_counter()?; - let client_state = ctx.decode_client_state(client_state)?; + let client_val_ctx = ctx.get_client_validation_context(); + + let client_state = ClientStateRef::::try_from(client_state)?; let client_id = client_state.client_type().build_client_id(id_counter); - let status = client_state.status(ctx.get_client_validation_context(), &client_id)?; + let status = client_state.status(client_val_ctx, &client_id)?; if status.is_frozen() { return Err(ClientError::ClientFrozen { @@ -41,7 +41,7 @@ where client_state.verify_consensus_state(consensus_state)?; - if ctx.client_state(&client_id).is_ok() { + if client_val_ctx.client_state(&client_id).is_ok() { return Err(ClientError::ClientStateAlreadyExists { client_id }.into()); }; @@ -60,15 +60,15 @@ where // Construct this client's identifier let id_counter = ctx.client_counter()?; - let client_state = ctx.decode_client_state(client_state)?; + + let client_exec_ctx = ctx.get_client_execution_context(); + + let client_state = ClientStateMut::::try_from(client_state)?; + let client_type = client_state.client_type(); let client_id = client_type.build_client_id(id_counter); - client_state.initialise( - ctx.get_client_execution_context(), - &client_id, - consensus_state, - )?; + client_state.initialise(client_exec_ctx, &client_id, consensus_state)?; ctx.increase_client_counter()?; diff --git a/ibc-core/ics02-client/src/handler/update_client.rs b/ibc-core/ics02-client/src/handler/update_client.rs index bb00372c6b..81e140b8a9 100644 --- a/ibc-core/ics02-client/src/handler/update_client.rs +++ b/ibc-core/ics02-client/src/handler/update_client.rs @@ -1,8 +1,6 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgUpdateAnyClient`. -use ibc_core_client_context::client_state::{ - ClientStateCommon, ClientStateExecution, ClientStateValidation, -}; +use ibc_core_client_context::prelude::*; use ibc_core_client_types::error::ClientError; use ibc_core_client_types::events::{ClientMisbehaviour, UpdateClient}; use ibc_core_client_types::msgs::MsgUpdateOrMisbehaviour; @@ -21,20 +19,18 @@ where let client_id = msg.client_id().clone(); + let client_val_ctx = ctx.get_client_validation_context(); + // Read client state from the host chain store. The client should already exist. - let client_state = ctx.client_state(&client_id)?; + let client_state = client_val_ctx.client_state(&client_id)?; client_state - .status(ctx.get_client_validation_context(), &client_id)? + .status(client_val_ctx, &client_id)? .verify_is_active()?; let client_message = msg.client_message(); - client_state.verify_client_message( - ctx.get_client_validation_context(), - &client_id, - client_message, - )?; + client_state.verify_client_message(client_val_ctx, &client_id, client_message)?; Ok(()) } @@ -49,20 +45,16 @@ where MsgUpdateOrMisbehaviour::Misbehaviour(_) => UpdateKind::SubmitMisbehaviour, }; let client_message = msg.client_message(); - let client_state = ctx.client_state(&client_id)?; - let found_misbehaviour = client_state.check_for_misbehaviour( - ctx.get_client_validation_context(), - &client_id, - client_message.clone(), - )?; + let client_exec_ctx = ctx.get_client_execution_context(); + + let client_state = client_exec_ctx.client_state(&client_id)?; + + let found_misbehaviour = + client_state.check_for_misbehaviour(client_exec_ctx, &client_id, client_message.clone())?; if found_misbehaviour { - client_state.update_state_on_misbehaviour( - ctx.get_client_execution_context(), - &client_id, - client_message, - )?; + client_state.update_state_on_misbehaviour(client_exec_ctx, &client_id, client_message)?; let event = IbcEvent::ClientMisbehaviour(ClientMisbehaviour::new( client_id, @@ -80,11 +72,8 @@ where let header = client_message; - let consensus_heights = client_state.update_state( - ctx.get_client_execution_context(), - &client_id, - header.clone(), - )?; + let consensus_heights = + client_state.update_state(client_exec_ctx, &client_id, header.clone())?; { let event = { diff --git a/ibc-core/ics02-client/src/handler/upgrade_client.rs b/ibc-core/ics02-client/src/handler/upgrade_client.rs index 5bf1386e42..a072988a23 100644 --- a/ibc-core/ics02-client/src/handler/upgrade_client.rs +++ b/ibc-core/ics02-client/src/handler/upgrade_client.rs @@ -1,9 +1,6 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgUpgradeAnyClient`. //! -use ibc_core_client_context::client_state::{ - ClientStateCommon, ClientStateExecution, ClientStateValidation, -}; -use ibc_core_client_context::consensus_state::ConsensusState; +use ibc_core_client_context::prelude::*; use ibc_core_client_types::error::ClientError; use ibc_core_client_types::events::UpgradeClient; use ibc_core_client_types::msgs::MsgUpgradeClient; @@ -23,12 +20,14 @@ where ctx.validate_message_signer(&signer)?; + let client_val_ctx = ctx.get_client_validation_context(); + // Read the current latest client state from the host chain store. - let old_client_state = ctx.client_state(&client_id)?; + let old_client_state = client_val_ctx.client_state(&client_id)?; // Check if the client is active. old_client_state - .status(ctx.get_client_validation_context(), &client_id)? + .status(client_val_ctx, &client_id)? .verify_is_active()?; // Read the latest consensus state from the host chain store. @@ -37,7 +36,7 @@ where old_client_state.latest_height().revision_number(), old_client_state.latest_height().revision_height(), ); - let old_consensus_state = ctx + let old_consensus_state = client_val_ctx .consensus_state(&old_client_cons_state_path) .map_err(|_| ClientError::ConsensusStateNotFound { client_id, @@ -62,10 +61,12 @@ where { let MsgUpgradeClient { client_id, .. } = msg; - let old_client_state = ctx.client_state(&client_id)?; + let client_exec_ctx = ctx.get_client_execution_context(); + + let old_client_state = client_exec_ctx.client_state(&client_id)?; let latest_height = old_client_state.update_state_on_upgrade( - ctx.get_client_execution_context(), + client_exec_ctx, &client_id, msg.upgraded_client_state.clone(), msg.upgraded_consensus_state, diff --git a/ibc-core/ics03-connection/src/delay.rs b/ibc-core/ics03-connection/src/delay.rs index feecf23fba..06fb030989 100644 --- a/ibc-core/ics03-connection/src/delay.rs +++ b/ibc-core/ics03-connection/src/delay.rs @@ -21,7 +21,7 @@ where let client_id = connection_end.client_id(); let last_client_update = ctx .get_client_validation_context() - .update_meta(client_id, &packet_proof_height)?; + .client_update_meta(client_id, &packet_proof_height)?; // Fetch the connection delay time and height periods. let conn_delay_time_period = connection_end.delay_period(); diff --git a/ibc-core/ics03-connection/src/handler/conn_open_ack.rs b/ibc-core/ics03-connection/src/handler/conn_open_ack.rs index 82b447201b..aca9bad684 100644 --- a/ibc-core/ics03-connection/src/handler/conn_open_ack.rs +++ b/ibc-core/ics03-connection/src/handler/conn_open_ack.rs @@ -1,7 +1,6 @@ //! Protocol logic specific to processing ICS3 messages of type `MsgConnectionOpenAck`. -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection_types::error::ConnectionError; use ibc_core_connection_types::events::OpenAck; use ibc_core_connection_types::msgs::MsgConnectionOpenAck; @@ -44,7 +43,12 @@ where .into()); } - ctx_a.validate_self_client(msg.client_state_of_a_on_b.clone())?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + + let client_state_of_a_on_b = + Ctx::HostClientState::try_from(msg.client_state_of_a_on_b.clone())?; + + ctx_a.validate_self_client(client_state_of_a_on_b)?; msg.version .verify_is_supported(vars.conn_end_on_a.versions())?; @@ -53,10 +57,10 @@ where // Proof verification. { - let client_state_of_b_on_a = ctx_a.client_state(vars.client_id_on_a())?; + let client_state_of_b_on_a = client_val_ctx_a.client_state(vars.client_id_on_a())?; client_state_of_b_on_a - .status(ctx_a.get_client_validation_context(), vars.client_id_on_a())? + .status(client_val_ctx_a, vars.client_id_on_a())? .verify_is_active()?; client_state_of_b_on_a.validate_proof_height(msg.proofs_height_on_b)?; @@ -66,7 +70,8 @@ where msg.proofs_height_on_b.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let prefix_on_a = ctx_a.commitment_prefix(); let prefix_on_b = vars.conn_end_on_a.counterparty().prefix(); diff --git a/ibc-core/ics03-connection/src/handler/conn_open_confirm.rs b/ibc-core/ics03-connection/src/handler/conn_open_confirm.rs index f265c60293..f9ef4eb61b 100644 --- a/ibc-core/ics03-connection/src/handler/conn_open_confirm.rs +++ b/ibc-core/ics03-connection/src/handler/conn_open_confirm.rs @@ -1,7 +1,6 @@ //! Protocol logic specific to processing ICS3 messages of type `MsgConnectionOpenConfirm`. -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection_types::error::ConnectionError; use ibc_core_connection_types::events::OpenConfirm; use ibc_core_connection_types::msgs::MsgConnectionOpenConfirm; @@ -42,10 +41,12 @@ where // Verify proofs { - let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?; + let client_val_ctx_b = ctx_b.get_client_validation_context(); + + let client_state_of_a_on_b = client_val_ctx_b.client_state(client_id_on_b)?; client_state_of_a_on_b - .status(ctx_b.get_client_validation_context(), client_id_on_b)? + .status(client_val_ctx_b, client_id_on_b)? .verify_is_active()?; client_state_of_a_on_b.validate_proof_height(msg.proof_height_on_a)?; @@ -54,7 +55,8 @@ where msg.proof_height_on_a.revision_number(), msg.proof_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let prefix_on_a = conn_end_on_b.counterparty().prefix(); let prefix_on_b = ctx_b.commitment_prefix(); diff --git a/ibc-core/ics03-connection/src/handler/conn_open_init.rs b/ibc-core/ics03-connection/src/handler/conn_open_init.rs index 090d1b8b30..8d42866169 100644 --- a/ibc-core/ics03-connection/src/handler/conn_open_init.rs +++ b/ibc-core/ics03-connection/src/handler/conn_open_init.rs @@ -1,5 +1,5 @@ //! Protocol logic specific to ICS3 messages of type `MsgConnectionOpenInit`. -use ibc_core_client::context::client_state::ClientStateValidation; +use ibc_core_client::context::prelude::*; use ibc_core_connection_types::events::OpenInit; use ibc_core_connection_types::msgs::MsgConnectionOpenInit; use ibc_core_connection_types::{ConnectionEnd, Counterparty, State}; @@ -16,11 +16,13 @@ where { ctx_a.validate_message_signer(&msg.signer)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + // An IBC client running on the local (host) chain should exist. - let client_state_of_b_on_a = ctx_a.client_state(&msg.client_id_on_a)?; + let client_state_of_b_on_a = client_val_ctx_a.client_state(&msg.client_id_on_a)?; client_state_of_b_on_a - .status(ctx_a.get_client_validation_context(), &msg.client_id_on_a)? + .status(client_val_ctx_a, &msg.client_id_on_a)? .verify_is_active()?; if let Some(version) = msg.version { diff --git a/ibc-core/ics03-connection/src/handler/conn_open_try.rs b/ibc-core/ics03-connection/src/handler/conn_open_try.rs index f192998182..99e15f2097 100644 --- a/ibc-core/ics03-connection/src/handler/conn_open_try.rs +++ b/ibc-core/ics03-connection/src/handler/conn_open_try.rs @@ -1,6 +1,5 @@ //! Protocol logic specific to processing ICS3 messages of type `MsgConnectionOpenTry`.; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection_types::error::ConnectionError; use ibc_core_connection_types::events::OpenTry; use ibc_core_connection_types::msgs::MsgConnectionOpenTry; @@ -34,7 +33,12 @@ where { ctx_b.validate_message_signer(&msg.signer)?; - ctx_b.validate_self_client(msg.client_state_of_b_on_a.clone())?; + let client_val_ctx_b = ctx_b.get_client_validation_context(); + + let client_state_of_b_on_a = + Ctx::HostClientState::try_from(msg.client_state_of_b_on_a.clone())?; + + ctx_b.validate_self_client(client_state_of_b_on_a)?; let host_height = ctx_b.host_height().map_err(|_| ConnectionError::Other { description: "failed to get host height".to_string(), @@ -52,10 +56,11 @@ where // Verify proofs { - let client_state_of_a_on_b = ctx_b.client_state(vars.conn_end_on_b.client_id())?; + let client_state_of_a_on_b = + client_val_ctx_b.client_state(vars.conn_end_on_b.client_id())?; client_state_of_a_on_b - .status(ctx_b.get_client_validation_context(), &msg.client_id_on_b)? + .status(client_val_ctx_b, &msg.client_id_on_b)? .verify_is_active()?; client_state_of_a_on_b.validate_proof_height(msg.proofs_height_on_a)?; @@ -65,7 +70,8 @@ where msg.proofs_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let prefix_on_a = vars.conn_end_on_b.counterparty().prefix(); let prefix_on_b = ctx_b.commitment_prefix(); diff --git a/ibc-core/ics04-channel/src/context.rs b/ibc-core/ics04-channel/src/context.rs index 8dcd05fac6..1fba1de8fd 100644 --- a/ibc-core/ics04-channel/src/context.rs +++ b/ibc-core/ics04-channel/src/context.rs @@ -2,25 +2,18 @@ use ibc_core_channel_types::channel::ChannelEnd; use ibc_core_channel_types::commitment::PacketCommitment; -use ibc_core_client::context::client_state::ClientState; -use ibc_core_client::context::consensus_state::ConsensusState; -use ibc_core_client::context::{ClientExecutionContext, ClientValidationContext}; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::ConnectionEnd; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::IbcEvent; -use ibc_core_host::types::identifiers::{ClientId, ConnectionId, Sequence}; -use ibc_core_host::types::path::{ - ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath, -}; +use ibc_core_host::types::identifiers::{ConnectionId, Sequence}; +use ibc_core_host::types::path::{ChannelEndPath, CommitmentPath, SeqSendPath}; use ibc_core_host::{ExecutionContext, ValidationContext}; use ibc_primitives::prelude::*; /// Methods required in send packet validation, to be implemented by the host pub trait SendPacketValidationContext { type V: ClientValidationContext; - type E: ClientExecutionContext; - type AnyConsensusState: ConsensusState; - type AnyClientState: ClientState; /// Retrieve the context that implements all clients' `ValidationContext`. fn get_client_validation_context(&self) -> &Self::V; @@ -31,15 +24,6 @@ pub trait SendPacketValidationContext { /// Returns the ConnectionState for the given identifier `connection_id`. fn connection_end(&self, connection_id: &ConnectionId) -> Result; - /// Returns the ClientState for the given identifier `client_id`. Necessary dependency towards - /// proof verification. - fn client_state(&self, client_id: &ClientId) -> Result; - - fn client_consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result; - fn get_next_sequence_send(&self, seq_send_path: &SeqSendPath) -> Result; } @@ -49,9 +33,6 @@ where T: ValidationContext, { type V = T::V; - type E = T::E; - type AnyConsensusState = T::AnyConsensusState; - type AnyClientState = T::AnyClientState; fn get_client_validation_context(&self) -> &Self::V { self.get_client_validation_context() @@ -65,17 +46,6 @@ where self.connection_end(connection_id) } - fn client_state(&self, client_id: &ClientId) -> Result { - self.client_state(client_id) - } - - fn client_consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - self.consensus_state(client_cons_state_path) - } - fn get_next_sequence_send( &self, seq_send_path: &SeqSendPath, diff --git a/ibc-core/ics04-channel/src/handler/acknowledgement.rs b/ibc-core/ics04-channel/src/handler/acknowledgement.rs index 516d581e6d..ec6aa41bcb 100644 --- a/ibc-core/ics04-channel/src/handler/acknowledgement.rs +++ b/ibc-core/ics04-channel/src/handler/acknowledgement.rs @@ -3,8 +3,7 @@ use ibc_core_channel_types::commitment::{compute_ack_commitment, compute_packet_ use ibc_core_channel_types::error::{ChannelError, PacketError}; use ibc_core_channel_types::events::AcknowledgePacket; use ibc_core_channel_types::msgs::MsgAcknowledgement; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::delay::verify_conn_delay_passed; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; @@ -175,7 +174,10 @@ where // Verify proofs { let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + + let client_val_ctx_a = ctx_a.get_client_validation_context(); + + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? @@ -187,7 +189,8 @@ where msg.proof_height_on_b.revision_number(), msg.proof_height_on_b.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let ack_commitment = compute_ack_commitment(&msg.acknowledgement); let ack_path_on_b = AckPath::new(&packet.port_id_on_b, &packet.chan_id_on_b, packet.seq_on_a); diff --git a/ibc-core/ics04-channel/src/handler/chan_close_confirm.rs b/ibc-core/ics04-channel/src/handler/chan_close_confirm.rs index dd3b51b06f..5385d18c3d 100644 --- a/ibc-core/ics04-channel/src/handler/chan_close_confirm.rs +++ b/ibc-core/ics04-channel/src/handler/chan_close_confirm.rs @@ -4,8 +4,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, State, State as use ibc_core_channel_types::error::ChannelError; use ibc_core_channel_types::events::CloseConfirm; use ibc_core_channel_types::msgs::MsgChannelCloseConfirm; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -112,7 +111,10 @@ where // Verify proofs { let client_id_on_b = conn_end_on_b.client_id(); - let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?; + + let client_val_ctx_b = ctx_b.get_client_validation_context(); + + let client_state_of_a_on_b = client_val_ctx_b.client_state(client_id_on_b)?; client_state_of_a_on_b .status(ctx_b.get_client_validation_context(), client_id_on_b)? @@ -124,7 +126,8 @@ where msg.proof_height_on_a.revision_number(), msg.proof_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let prefix_on_a = conn_end_on_b.counterparty().prefix(); let port_id_on_a = &chan_end_on_b.counterparty().port_id; let chan_id_on_a = chan_end_on_b diff --git a/ibc-core/ics04-channel/src/handler/chan_close_init.rs b/ibc-core/ics04-channel/src/handler/chan_close_init.rs index fe7588361b..15c38c9f5f 100644 --- a/ibc-core/ics04-channel/src/handler/chan_close_init.rs +++ b/ibc-core/ics04-channel/src/handler/chan_close_init.rs @@ -3,7 +3,7 @@ use ibc_core_channel_types::channel::State; use ibc_core_channel_types::error::ChannelError; use ibc_core_channel_types::events::CloseInit; use ibc_core_channel_types::msgs::MsgChannelCloseInit; -use ibc_core_client::context::client_state::ClientStateValidation; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -110,7 +110,10 @@ where conn_end_on_a.verify_state_matches(&ConnectionState::Open)?; let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + + let client_val_ctx_a = ctx_a.get_client_validation_context(); + + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? .verify_is_active()?; diff --git a/ibc-core/ics04-channel/src/handler/chan_open_ack.rs b/ibc-core/ics04-channel/src/handler/chan_open_ack.rs index 5bf0e3211e..7e5b6b2405 100644 --- a/ibc-core/ics04-channel/src/handler/chan_open_ack.rs +++ b/ibc-core/ics04-channel/src/handler/chan_open_ack.rs @@ -3,8 +3,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, State, State as use ibc_core_channel_types::error::ChannelError; use ibc_core_channel_types::events::OpenAck; use ibc_core_channel_types::msgs::MsgChannelOpenAck; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -109,7 +108,8 @@ where // Verify proofs { let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? @@ -121,7 +121,8 @@ where msg.proof_height_on_b.revision_number(), msg.proof_height_on_b.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let prefix_on_b = conn_end_on_a.counterparty().prefix(); let port_id_on_b = &chan_end_on_a.counterparty().port_id; let conn_id_on_b = conn_end_on_a.counterparty().connection_id().ok_or( diff --git a/ibc-core/ics04-channel/src/handler/chan_open_confirm.rs b/ibc-core/ics04-channel/src/handler/chan_open_confirm.rs index bb9920d3b9..dfbc777c1e 100644 --- a/ibc-core/ics04-channel/src/handler/chan_open_confirm.rs +++ b/ibc-core/ics04-channel/src/handler/chan_open_confirm.rs @@ -4,8 +4,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, State, State as use ibc_core_channel_types::error::ChannelError; use ibc_core_channel_types::events::OpenConfirm; use ibc_core_channel_types::msgs::MsgChannelOpenConfirm; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -114,7 +113,8 @@ where // Verify proofs { let client_id_on_b = conn_end_on_b.client_id(); - let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?; + let client_val_ctx_b = ctx_b.get_client_validation_context(); + let client_state_of_a_on_b = client_val_ctx_b.client_state(client_id_on_b)?; client_state_of_a_on_b .status(ctx_b.get_client_validation_context(), client_id_on_b)? @@ -126,7 +126,8 @@ where msg.proof_height_on_a.revision_number(), msg.proof_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let prefix_on_a = conn_end_on_b.counterparty().prefix(); let port_id_on_a = &chan_end_on_b.counterparty().port_id; let chan_id_on_a = chan_end_on_b diff --git a/ibc-core/ics04-channel/src/handler/chan_open_init.rs b/ibc-core/ics04-channel/src/handler/chan_open_init.rs index 81f6452041..58a161d0bd 100644 --- a/ibc-core/ics04-channel/src/handler/chan_open_init.rs +++ b/ibc-core/ics04-channel/src/handler/chan_open_init.rs @@ -3,7 +3,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, State}; use ibc_core_channel_types::events::OpenInit; use ibc_core_channel_types::msgs::MsgChannelOpenInit; -use ibc_core_client::context::client_state::ClientStateValidation; +use ibc_core_client::context::prelude::*; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; use ibc_core_host::types::identifiers::ChannelId; @@ -120,7 +120,8 @@ where // Note: Not needed check if the connection end is OPEN. Optimistic channel handshake is allowed. let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? diff --git a/ibc-core/ics04-channel/src/handler/chan_open_try.rs b/ibc-core/ics04-channel/src/handler/chan_open_try.rs index 80888b0b16..08c82deac7 100644 --- a/ibc-core/ics04-channel/src/handler/chan_open_try.rs +++ b/ibc-core/ics04-channel/src/handler/chan_open_try.rs @@ -4,8 +4,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, State as Channel use ibc_core_channel_types::error::ChannelError; use ibc_core_channel_types::events::OpenTry; use ibc_core_channel_types::msgs::MsgChannelOpenTry; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -135,7 +134,8 @@ where // Verify proofs { let client_id_on_b = conn_end_on_b.client_id(); - let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?; + let client_val_ctx_b = ctx_b.get_client_validation_context(); + let client_state_of_a_on_b = client_val_ctx_b.client_state(client_id_on_b)?; client_state_of_a_on_b .status(ctx_b.get_client_validation_context(), client_id_on_b)? @@ -148,7 +148,8 @@ where msg.proof_height_on_a.revision_number(), msg.proof_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let prefix_on_a = conn_end_on_b.counterparty().prefix(); let port_id_on_a = msg.port_id_on_a.clone(); let chan_id_on_a = msg.chan_id_on_a.clone(); diff --git a/ibc-core/ics04-channel/src/handler/recv_packet.rs b/ibc-core/ics04-channel/src/handler/recv_packet.rs index 9e42e269d4..8c0cd25a16 100644 --- a/ibc-core/ics04-channel/src/handler/recv_packet.rs +++ b/ibc-core/ics04-channel/src/handler/recv_packet.rs @@ -4,8 +4,7 @@ use ibc_core_channel_types::error::{ChannelError, PacketError}; use ibc_core_channel_types::events::{ReceivePacket, WriteAcknowledgement}; use ibc_core_channel_types::msgs::MsgRecvPacket; use ibc_core_channel_types::packet::Receipt; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::delay::verify_conn_delay_passed; use ibc_core_connection::types::State as ConnectionState; use ibc_core_handler_types::error::ContextError; @@ -179,7 +178,8 @@ where // Verify proofs { let client_id_on_b = conn_end_on_b.client_id(); - let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?; + let client_val_ctx_b = ctx_b.get_client_validation_context(); + let client_state_of_a_on_b = client_val_ctx_b.client_state(client_id_on_b)?; client_state_of_a_on_b .status(ctx_b.get_client_validation_context(), client_id_on_b)? @@ -193,7 +193,8 @@ where msg.proof_height_on_a.revision_height(), ); - let consensus_state_of_a_on_b = ctx_b.consensus_state(&client_cons_state_path_on_b)?; + let consensus_state_of_a_on_b = + client_val_ctx_b.consensus_state(&client_cons_state_path_on_b)?; let expected_commitment_on_a = compute_packet_commitment( &msg.packet.data, diff --git a/ibc-core/ics04-channel/src/handler/send_packet.rs b/ibc-core/ics04-channel/src/handler/send_packet.rs index a0f3405c05..f1c6c29b5c 100644 --- a/ibc-core/ics04-channel/src/handler/send_packet.rs +++ b/ibc-core/ics04-channel/src/handler/send_packet.rs @@ -3,8 +3,7 @@ use ibc_core_channel_types::commitment::compute_packet_commitment; use ibc_core_channel_types::error::PacketError; use ibc_core_channel_types::events::SendPacket; use ibc_core_channel_types::packet::Packet; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; use ibc_core_host::types::path::{ @@ -51,7 +50,9 @@ pub fn send_packet_validate( let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? @@ -72,7 +73,8 @@ pub fn send_packet_validate( latest_height_on_a.revision_number(), latest_height_on_a.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.client_consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let latest_timestamp = consensus_state_of_b_on_a.timestamp(); let packet_timestamp = packet.timeout_timestamp_on_b; if let Expiry::Expired = latest_timestamp.check_expiry(&packet_timestamp) { diff --git a/ibc-core/ics04-channel/src/handler/timeout.rs b/ibc-core/ics04-channel/src/handler/timeout.rs index bff8be649a..aca19b9f8b 100644 --- a/ibc-core/ics04-channel/src/handler/timeout.rs +++ b/ibc-core/ics04-channel/src/handler/timeout.rs @@ -3,8 +3,7 @@ use ibc_core_channel_types::commitment::compute_packet_commitment; use ibc_core_channel_types::error::{ChannelError, PacketError}; use ibc_core_channel_types::events::{ChannelClosed, TimeoutPacket}; use ibc_core_channel_types::msgs::{MsgTimeout, MsgTimeoutOnClose}; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::delay::verify_conn_delay_passed; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::{IbcEvent, MessageEvent}; @@ -186,7 +185,8 @@ where // Verify proofs { let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? @@ -200,7 +200,8 @@ where msg.proof_height_on_b.revision_number(), msg.proof_height_on_b.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let timestamp_of_b = consensus_state_of_b_on_a.timestamp(); if !msg.packet.timed_out(×tamp_of_b, msg.proof_height_on_b) { diff --git a/ibc-core/ics04-channel/src/handler/timeout_on_close.rs b/ibc-core/ics04-channel/src/handler/timeout_on_close.rs index b448bba448..49dcc9a05d 100644 --- a/ibc-core/ics04-channel/src/handler/timeout_on_close.rs +++ b/ibc-core/ics04-channel/src/handler/timeout_on_close.rs @@ -2,8 +2,7 @@ use ibc_core_channel_types::channel::{ChannelEnd, Counterparty, Order, State}; use ibc_core_channel_types::commitment::compute_packet_commitment; use ibc_core_channel_types::error::{ChannelError, PacketError}; use ibc_core_channel_types::msgs::MsgTimeoutOnClose; -use ibc_core_client::context::client_state::{ClientStateCommon, ClientStateValidation}; -use ibc_core_client::context::consensus_state::ConsensusState; +use ibc_core_client::context::prelude::*; use ibc_core_connection::delay::verify_conn_delay_passed; use ibc_core_handler_types::error::ContextError; use ibc_core_host::types::path::{ @@ -65,7 +64,8 @@ where // Verify proofs { let client_id_on_a = conn_end_on_a.client_id(); - let client_state_of_b_on_a = ctx_a.client_state(client_id_on_a)?; + let client_val_ctx_a = ctx_a.get_client_validation_context(); + let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?; client_state_of_b_on_a .status(ctx_a.get_client_validation_context(), client_id_on_a)? @@ -78,7 +78,8 @@ where msg.proof_height_on_b.revision_number(), msg.proof_height_on_b.revision_height(), ); - let consensus_state_of_b_on_a = ctx_a.consensus_state(&client_cons_state_path_on_a)?; + let consensus_state_of_b_on_a = + client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?; let prefix_on_b = conn_end_on_a.counterparty().prefix(); let port_id_on_b = chan_end_on_a.counterparty().port_id.clone(); let chan_id_on_b = chan_end_on_a diff --git a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/context.rs b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/context.rs index eaa9313a8d..925ab547f4 100644 --- a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/context.rs +++ b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/context.rs @@ -5,21 +5,22 @@ //! [Basecoin-rs](https://github.com/informalsystems/basecoin-rs) repository. //! If it proves to be generic enough, we may move it to the ICS02 section. -use ibc_core_client_context::client_state::ClientState; -use ibc_core_client_context::consensus_state::ConsensusState; -use ibc_core_client_context::{ClientExecutionContext, ClientValidationContext}; +use ibc_core_client_context::ClientValidationContext; use ibc_core_client_types::error::UpgradeClientError; use ibc_core_host_types::path::UpgradeClientPath; use super::Plan; +pub type UpgradedClientStateRef = + <::V as ClientValidationContext>::ClientStateRef; + +pub type UpgradedConsensusStateRef = + <::V as ClientValidationContext>::ConsensusStateRef; + /// Helper context to validate client upgrades, providing methods to retrieve /// an upgrade plan and related upgraded client and consensus states. pub trait UpgradeValidationContext { type V: ClientValidationContext; - type E: ClientExecutionContext; - type AnyConsensusState: ConsensusState; - type AnyClientState: ClientState; /// Returns the upgrade plan that is scheduled and not have been executed yet. fn upgrade_plan(&self) -> Result; @@ -28,13 +29,13 @@ pub trait UpgradeValidationContext { fn upgraded_client_state( &self, upgrade_path: &UpgradeClientPath, - ) -> Result; + ) -> Result, UpgradeClientError>; /// Returns the upgraded consensus state at the specified upgrade path. fn upgraded_consensus_state( &self, upgrade_path: &UpgradeClientPath, - ) -> Result; + ) -> Result, UpgradeClientError>; } /// Helper context to execute client upgrades, providing methods to schedule @@ -50,13 +51,13 @@ pub trait UpgradeExecutionContext: UpgradeValidationContext { fn store_upgraded_client_state( &mut self, upgrade_path: UpgradeClientPath, - client_state: Self::AnyClientState, + client_state: UpgradedClientStateRef, ) -> Result<(), UpgradeClientError>; /// Stores the upgraded consensus state at the specified upgrade path. fn store_upgraded_consensus_state( &mut self, upgrade_path: UpgradeClientPath, - consensus_state: Self::AnyConsensusState, + consensus_state: UpgradedConsensusStateRef, ) -> Result<(), UpgradeClientError>; } diff --git a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/handler.rs b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/handler.rs index 31f1871750..99e04f8332 100644 --- a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/handler.rs +++ b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/handler.rs @@ -4,6 +4,7 @@ use ibc_core_host_types::path::UpgradeClientPath; use ibc_primitives::prelude::*; use tendermint::abci::Event as TmEvent; +use super::UpgradedClientStateRef; use crate::upgrade_proposal::{UpgradeClientProposal, UpgradeExecutionContext, UpgradeProposal}; /// Handles an upgrade client proposal @@ -17,7 +18,7 @@ pub fn upgrade_client_proposal_handler( ) -> Result where Ctx: UpgradeExecutionContext, - Ctx::AnyClientState: From, + UpgradedClientStateRef: From, { let plan = proposal.plan; diff --git a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/mod.rs b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/mod.rs index cd1500402a..db4919d924 100644 --- a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/mod.rs +++ b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/mod.rs @@ -6,7 +6,7 @@ mod handler; mod plan; mod proposal; -pub use context::{UpgradeExecutionContext, UpgradeValidationContext}; +pub use context::*; pub use events::{UpgradeChain, UpgradeClientProposal}; pub use handler::upgrade_client_proposal_handler; pub use plan::Plan; diff --git a/ibc-core/ics24-host/cosmos/src/validate_self_client.rs b/ibc-core/ics24-host/cosmos/src/validate_self_client.rs index 9f32348ebd..72974ab75f 100644 --- a/ibc-core/ics24-host/cosmos/src/validate_self_client.rs +++ b/ibc-core/ics24-host/cosmos/src/validate_self_client.rs @@ -1,7 +1,6 @@ use core::time::Duration; -use ibc_client_tendermint::client_state::ClientState; -use ibc_core_client_context::client_state::ClientStateCommon; +use ibc_client_tendermint::types::ClientState as TmClientState; use ibc_core_client_types::error::ClientError; use ibc_core_client_types::Height; use ibc_core_commitment_types::specs::ProofSpecs; @@ -9,29 +8,23 @@ use ibc_core_connection_types::error::ConnectionError; use ibc_core_handler_types::error::ContextError; use ibc_core_host_types::identifiers::ChainId; use ibc_primitives::prelude::*; -use ibc_proto::google::protobuf::Any; use tendermint::trust_threshold::TrustThresholdFraction as TendermintTrustThresholdFraction; -/// Provides an implementation of `ValidationContext::validate_self_client` for -/// Tendermint-based hosts. +/// Provides a default implementation intended for implementing the +/// `ValidationContext::validate_self_client` API. +/// +/// This validation logic tailored for Tendermint client states of a host chain +/// operating across various counterparty chains. pub trait ValidateSelfClientContext { fn validate_self_tendermint_client( &self, - client_state_of_host_on_counterparty: Any, + client_state_of_host_on_counterparty: TmClientState, ) -> Result<(), ContextError> { - let tm_client_state = ClientState::try_from(client_state_of_host_on_counterparty) - .map_err(|_| ConnectionError::InvalidClientState { - reason: "client must be a tendermint client".to_string(), - }) - .map_err(ContextError::ConnectionError)?; - - let tm_client_state_inner = tm_client_state.inner(); - - tm_client_state_inner + client_state_of_host_on_counterparty .validate() .map_err(ClientError::from)?; - if tm_client_state_inner.is_frozen() { + if client_state_of_host_on_counterparty.is_frozen() { return Err(ClientError::ClientFrozen { description: String::new(), } @@ -39,18 +32,18 @@ pub trait ValidateSelfClientContext { } let self_chain_id = self.chain_id(); - if self_chain_id != &tm_client_state_inner.chain_id { + if self_chain_id != &client_state_of_host_on_counterparty.chain_id { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "invalid chain-id. expected: {}, got: {}", - self_chain_id, tm_client_state_inner.chain_id + self_chain_id, client_state_of_host_on_counterparty.chain_id ), }, )); } - let latest_height = tm_client_state.latest_height(); + let latest_height = client_state_of_host_on_counterparty.latest_height; let self_revision_number = self_chain_id.revision_number(); if self_revision_number != latest_height.revision_number() { return Err(ContextError::ConnectionError( @@ -76,20 +69,20 @@ pub trait ValidateSelfClientContext { )); } - if self.proof_specs() != &tm_client_state_inner.proof_specs { + if self.proof_specs() != &client_state_of_host_on_counterparty.proof_specs { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "client has invalid proof specs. expected: {:?}, got: {:?}", self.proof_specs(), - tm_client_state_inner.proof_specs + client_state_of_host_on_counterparty.proof_specs ), }, )); } let _ = { - let trust_level = tm_client_state_inner.trust_level; + let trust_level = client_state_of_host_on_counterparty.trust_level; TendermintTrustThresholdFraction::new( trust_level.numerator(), @@ -100,35 +93,37 @@ pub trait ValidateSelfClientContext { })? }; - if self.unbonding_period() != tm_client_state_inner.unbonding_period { + if self.unbonding_period() != client_state_of_host_on_counterparty.unbonding_period { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "invalid unbonding period. expected: {:?}, got: {:?}", self.unbonding_period(), - tm_client_state_inner.unbonding_period, + client_state_of_host_on_counterparty.unbonding_period, ), }, )); } - if tm_client_state_inner.unbonding_period < tm_client_state_inner.trusting_period { + if client_state_of_host_on_counterparty.unbonding_period + < client_state_of_host_on_counterparty.trusting_period + { return Err(ContextError::ConnectionError(ConnectionError::InvalidClientState{ reason: format!( "unbonding period must be greater than trusting period. unbonding period ({:?}) < trusting period ({:?})", - tm_client_state_inner.unbonding_period, - tm_client_state_inner.trusting_period + client_state_of_host_on_counterparty.unbonding_period, + client_state_of_host_on_counterparty.trusting_period )})); } - if !tm_client_state_inner.upgrade_path.is_empty() - && self.upgrade_path() != tm_client_state_inner.upgrade_path + if !client_state_of_host_on_counterparty.upgrade_path.is_empty() + && self.upgrade_path() != client_state_of_host_on_counterparty.upgrade_path { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "invalid upgrade path. expected: {:?}, got: {:?}", self.upgrade_path(), - tm_client_state_inner.upgrade_path + client_state_of_host_on_counterparty.upgrade_path ), }, )); diff --git a/ibc-core/ics24-host/src/context.rs b/ibc-core/ics24-host/src/context.rs index b2525b3cb9..9335fce603 100644 --- a/ibc-core/ics24-host/src/context.rs +++ b/ibc-core/ics24-host/src/context.rs @@ -3,9 +3,7 @@ use core::time::Duration; use ibc_core_channel_types::channel::ChannelEnd; use ibc_core_channel_types::commitment::{AcknowledgementCommitment, PacketCommitment}; use ibc_core_channel_types::packet::Receipt; -use ibc_core_client_context::client_state::ClientState; -use ibc_core_client_context::consensus_state::ConsensusState; -use ibc_core_client_context::{ClientExecutionContext, ClientValidationContext}; +use ibc_core_client_context::prelude::*; use ibc_core_client_types::Height; use ibc_core_commitment_types::commitment::CommitmentPrefix; use ibc_core_connection_types::version::{ @@ -14,13 +12,12 @@ use ibc_core_connection_types::version::{ use ibc_core_connection_types::ConnectionEnd; use ibc_core_handler_types::error::ContextError; use ibc_core_handler_types::events::IbcEvent; -use ibc_core_host_types::identifiers::{ClientId, ConnectionId, Sequence}; +use ibc_core_host_types::identifiers::{ConnectionId, Sequence}; use ibc_core_host_types::path::{ - AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, CommitmentPath, - ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, CommitmentPath, ConnectionPath, ReceiptPath, + SeqAckPath, SeqRecvPath, SeqSendPath, }; use ibc_primitives::prelude::*; -use ibc_primitives::proto::Any; use ibc_primitives::{Signer, Timestamp}; use crate::utils::calculate_block_delay; @@ -30,32 +27,14 @@ use crate::utils::calculate_block_delay; /// Trait used for the top-level `validate` entrypoint in the `ibc-core` crate. pub trait ValidationContext { type V: ClientValidationContext; - type E: ClientExecutionContext; - type AnyConsensusState: ConsensusState; - type AnyClientState: ClientState; + /// The client state type for the host chain. + type HostClientState: ClientStateValidation; + /// The consensus state type for the host chain. + type HostConsensusState: ConsensusState; /// Retrieve the context that implements all clients' `ValidationContext`. fn get_client_validation_context(&self) -> &Self::V; - /// Returns the ClientState for the given identifier `client_id`. - /// - /// Note: Clients have the responsibility to store client states on client creation and update. - fn client_state(&self, client_id: &ClientId) -> Result; - - /// Tries to decode the given `client_state` into a concrete light client state. - fn decode_client_state(&self, client_state: Any) -> Result; - - /// Retrieve the consensus state for the given client ID at the specified - /// height. - /// - /// Returns an error if no such state exists. - /// - /// Note: Clients have the responsibility to store consensus states on client creation and update. - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result; - /// Returns the current height of the local chain. fn host_height(&self) -> Result; @@ -66,7 +45,7 @@ pub trait ValidationContext { fn host_consensus_state( &self, height: &Height, - ) -> Result; + ) -> Result; /// Returns a natural number, counting how many clients have been created /// thus far. The value of this counter should increase only via method @@ -76,17 +55,18 @@ pub trait ValidationContext { /// Returns the ConnectionEnd for the given identifier `conn_id`. fn connection_end(&self, conn_id: &ConnectionId) -> Result; - /// Validates the `ClientState` of the client (a client referring to host) stored on the counterparty chain against the host's internal state. + /// Validates the `ClientState` of the host chain stored on the counterparty + /// chain against the host's internal state. /// /// For more information on the specific requirements for validating the /// client state of a host chain, please refer to the [ICS24 host /// requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements#client-state-validation) /// /// Additionally, implementations specific to individual chains can be found - /// in the `ibc-core-hostkit` crate. + /// in the `ibc-core/ics24-host` module. fn validate_self_client( &self, - client_state_of_host_on_counterparty: Any, + client_state_of_host_on_counterparty: Self::HostClientState, ) -> Result<(), ContextError>; /// Returns the prefix that the local chain uses in the KV store. @@ -166,6 +146,8 @@ pub trait ValidationContext { /// /// Trait used for the top-level `execute` and `dispatch` entrypoints in the `ibc-core` crate. pub trait ExecutionContext: ValidationContext { + type E: ClientExecutionContext; + /// Retrieve the context that implements all clients' `ExecutionContext`. fn get_client_execution_context(&mut self) -> &mut Self::E; @@ -262,3 +244,18 @@ pub trait ExecutionContext: ValidationContext { /// Log the given message. fn log_message(&mut self, message: String) -> Result<(), ContextError>; } + +/// Convenient type alias for `ClientStateRef`, providing access to client +/// validation methods within the context. +pub type ClientStateRef = + <::V as ClientValidationContext>::ClientStateRef; + +/// Convenient type alias for `ClientStateMut`, providing access to client +/// execution methods within the context. +pub type ClientStateMut = + <::E as ClientExecutionContext>::ClientStateMut; + +/// Convenient type alias for `ConsensusStateRef`, providing access to client +/// validation methods within the context. +pub type ConsensusStateRef = + <::V as ClientValidationContext>::ConsensusStateRef; diff --git a/ibc-core/ics25-handler/src/entrypoint.rs b/ibc-core/ics25-handler/src/entrypoint.rs index 957e147c0d..f41ad4a532 100644 --- a/ibc-core/ics25-handler/src/entrypoint.rs +++ b/ibc-core/ics25-handler/src/entrypoint.rs @@ -22,11 +22,14 @@ use ibc_core_router::router::Router; use ibc_core_router::types::error::RouterError; /// Entrypoint which performs both validation and message execution -pub fn dispatch( - ctx: &mut impl ExecutionContext, +pub fn dispatch( + ctx: &mut Ctx, router: &mut impl Router, msg: MsgEnvelope, -) -> Result<(), ContextError> { +) -> Result<(), ContextError> +where + Ctx: ExecutionContext, +{ validate(ctx, router, msg.clone())?; execute(ctx, router, msg) } diff --git a/ibc-query/src/core/channel/query.rs b/ibc-query/src/core/channel/query.rs index 56d9bb5bea..f0e64e80b1 100644 --- a/ibc-query/src/core/channel/query.rs +++ b/ibc-query/src/core/channel/query.rs @@ -3,13 +3,14 @@ use alloc::format; use core::str::FromStr; +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::Height; use ibc::core::host::types::identifiers::{ChannelId, ConnectionId, PortId, Sequence}; use ibc::core::host::types::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, Path, ReceiptPath, SeqRecvPath, SeqSendPath, }; -use ibc::core::host::ValidationContext; +use ibc::core::host::{ConsensusStateRef, ValidationContext}; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::channel::v1::{ QueryChannelClientStateRequest, QueryChannelClientStateResponse, @@ -121,7 +122,6 @@ pub fn query_channel_client_state( ) -> Result where I: QueryContext, - ::AnyClientState: Into, { let channel_id = ChannelId::from_str(request.channel_id.as_str())?; @@ -139,7 +139,9 @@ where description: format!("Channel {} does not have a connection", channel_id), })??; - let client_state = ibc_ctx.client_state(connection_end.client_id())?; + let client_val_ctx = ibc_ctx.get_client_validation_context(); + + let client_state = client_val_ctx.client_state(connection_end.client_id())?; let current_height = ibc_ctx.host_height()?; @@ -173,7 +175,7 @@ pub fn query_channel_consensus_state( ) -> Result where I: QueryContext, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { let channel_id = ChannelId::from_str(request.channel_id.as_str())?; @@ -198,8 +200,9 @@ where height.revision_number(), height.revision_height(), ); + let client_val_ctx = ibc_ctx.get_client_validation_context(); - let consensus_state = ibc_ctx.consensus_state(&consensus_path)?; + let consensus_state = client_val_ctx.consensus_state(&consensus_path)?; let current_height = ibc_ctx.host_height()?; diff --git a/ibc-query/src/core/channel/service.rs b/ibc-query/src/core/channel/service.rs index 105f97988a..7367caa485 100644 --- a/ibc-query/src/core/channel/service.rs +++ b/ibc-query/src/core/channel/service.rs @@ -2,7 +2,7 @@ //! `I` must be a type where writes from one thread are readable from another. //! This means using `Arc>` or `Arc>` in most cases. -use ibc::core::host::ValidationContext; +use ibc::core::host::ConsensusStateRef; use ibc::core::primitives::prelude::*; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::channel::v1::query_server::Query as ChannelQuery; @@ -37,8 +37,7 @@ use crate::core::context::QueryContext; pub struct ChannelQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { ibc_context: I, } @@ -46,8 +45,7 @@ where impl ChannelQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { /// The parameter `ibc_context` must be a type where writes from one thread are readable from another. /// This means using `Arc>` or `Arc>` in most cases. @@ -60,8 +58,7 @@ where impl ChannelQuery for ChannelQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { async fn channel( &self, diff --git a/ibc-query/src/core/client/query.rs b/ibc-query/src/core/client/query.rs index 5162c5de35..2b1341d431 100644 --- a/ibc-query/src/core/client/query.rs +++ b/ibc-query/src/core/client/query.rs @@ -4,14 +4,15 @@ use alloc::format; use core::str::FromStr; use ibc::core::client::context::client_state::ClientStateValidation; +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::error::ClientError; use ibc::core::client::types::Height; use ibc::core::host::types::identifiers::ClientId; use ibc::core::host::types::path::{ ClientConsensusStatePath, ClientStatePath, Path, UpgradeClientPath, }; -use ibc::core::host::ValidationContext; -use ibc::cosmos_host::upgrade_proposal::UpgradeValidationContext; +use ibc::core::host::{ConsensusStateRef, ValidationContext}; +use ibc::cosmos_host::upgrade_proposal::{UpgradeValidationContext, UpgradedConsensusStateRef}; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::client::v1::{ ConsensusStateWithHeight, IdentifiedClientState, QueryClientStateRequest, @@ -23,7 +24,7 @@ use ibc_proto::ibc::core::client::v1::{ QueryUpgradedConsensusStateResponse, }; -use crate::core::context::{ProvableContext, QueryContext}; +use crate::core::context::QueryContext; use crate::error::QueryError; /// Queries for the client state of a given client id. @@ -32,12 +33,13 @@ pub fn query_client_state( request: &QueryClientStateRequest, ) -> Result where - I: ValidationContext + ProvableContext, - ::AnyClientState: Into, + I: QueryContext, { let client_id = ClientId::from_str(request.client_id.as_str())?; - let client_state = ibc_ctx.client_state(&client_id)?; + let client_val_ctx = ibc_ctx.get_client_validation_context(); + + let client_state = client_val_ctx.client_state(&client_id)?; let current_height = ibc_ctx.host_height()?; @@ -64,7 +66,6 @@ pub fn query_client_states( ) -> Result where I: QueryContext, - ::AnyClientState: Into, { let client_states = ibc_ctx.client_states()?; @@ -88,7 +89,7 @@ pub fn query_consensus_state( ) -> Result where I: QueryContext, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { let client_id = ClientId::from_str(request.client_id.as_str())?; @@ -103,7 +104,9 @@ where } else { let height = Height::new(request.revision_number, request.revision_height)?; - let consensus_state = ibc_ctx.consensus_state(&ClientConsensusStatePath::new( + let client_val_ctx = ibc_ctx.get_client_validation_context(); + + let consensus_state = client_val_ctx.consensus_state(&ClientConsensusStatePath::new( client_id.clone(), height.revision_number(), height.revision_height(), @@ -141,7 +144,7 @@ pub fn query_consensus_states( ) -> Result where I: QueryContext, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { let client_id = ClientId::from_str(request.client_id.as_str())?; @@ -191,8 +194,8 @@ where I: ValidationContext, { let client_id = ClientId::from_str(request.client_id.as_str())?; - - let client_state = ibc_ctx.client_state(&client_id)?; + let client_val_ctx = ibc_ctx.get_client_validation_context(); + let client_state = client_val_ctx.client_state(&client_id)?; let client_validation_ctx = ibc_ctx.get_client_validation_context(); let client_status = client_state.status(client_validation_ctx, &client_id)?; @@ -208,7 +211,6 @@ pub fn query_upgraded_client_state( ) -> Result where U: UpgradeValidationContext, - ::AnyClientState: Into, { let plan = upgrade_ctx.upgrade_plan().map_err(ClientError::from)?; @@ -230,7 +232,7 @@ pub fn query_upgraded_consensus_state( ) -> Result where U: UpgradeValidationContext, - ::AnyConsensusState: Into, + UpgradedConsensusStateRef: Into, { let plan = upgrade_ctx.upgrade_plan().map_err(ClientError::from)?; diff --git a/ibc-query/src/core/client/service.rs b/ibc-query/src/core/client/service.rs index 6b1c06afd0..a1a7d7d320 100644 --- a/ibc-query/src/core/client/service.rs +++ b/ibc-query/src/core/client/service.rs @@ -2,9 +2,11 @@ //! `I` must be a type where writes from one thread are readable from another. //! This means using `Arc>` or `Arc>` in most cases. -use ibc::core::host::ValidationContext; +use ibc::core::host::ConsensusStateRef; use ibc::core::primitives::prelude::*; -use ibc::cosmos_host::upgrade_proposal::UpgradeValidationContext; +use ibc::cosmos_host::upgrade_proposal::{ + UpgradeValidationContext, UpgradedClientStateRef, UpgradedConsensusStateRef, +}; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::client::v1::query_server::Query as ClientQuery; use ibc_proto::ibc::core::client::v1::{ @@ -33,10 +35,8 @@ pub struct ClientQueryService where I: QueryContext + Send + Sync + 'static, U: UpgradeValidationContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + UpgradedClientStateRef: Into, + UpgradedConsensusStateRef: Into, { ibc_context: I, upgrade_context: U, @@ -46,10 +46,8 @@ impl ClientQueryService where I: QueryContext + Send + Sync + 'static, U: UpgradeValidationContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + UpgradedClientStateRef: Into, + UpgradedConsensusStateRef: Into, { /// Parameters `ibc_context` and `upgrade_context` must be a type where writes from one thread are readable from another. /// This means using `Arc>` or `Arc>` in most cases. @@ -66,10 +64,8 @@ impl ClientQuery for ClientQueryService where I: QueryContext + Send + Sync + 'static, U: UpgradeValidationContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, + UpgradedConsensusStateRef: Into, { async fn client_state( &self, diff --git a/ibc-query/src/core/connection/query.rs b/ibc-query/src/core/connection/query.rs index c75bd36b6a..09d3048067 100644 --- a/ibc-query/src/core/connection/query.rs +++ b/ibc-query/src/core/connection/query.rs @@ -4,12 +4,13 @@ use alloc::format; use alloc::vec::Vec; use core::str::FromStr; +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::Height; use ibc::core::host::types::identifiers::{ClientId, ConnectionId}; use ibc::core::host::types::path::{ ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, ConnectionPath, Path, }; -use ibc::core::host::ValidationContext; +use ibc::core::host::{ConsensusStateRef, ValidationContext}; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::client::v1::IdentifiedClientState; use ibc_proto::ibc::core::connection::v1::{ @@ -78,7 +79,6 @@ pub fn query_client_connections( ) -> Result where I: QueryContext, - ::AnyClientState: Into, { let client_id = ClientId::from_str(request.client_id.as_str())?; @@ -109,13 +109,14 @@ pub fn query_connection_client_state( ) -> Result where I: QueryContext, - ::AnyClientState: Into, { let connection_id = ConnectionId::from_str(request.connection_id.as_str())?; let connection_end = ibc_ctx.connection_end(&connection_id)?; - let client_state = ibc_ctx.client_state(connection_end.client_id())?; + let client_val_ctx = ibc_ctx.get_client_validation_context(); + + let client_state = client_val_ctx.client_state(connection_end.client_id())?; let current_height = ibc_ctx.host_height()?; @@ -148,7 +149,7 @@ pub fn query_connection_consensus_state( ) -> Result where I: ValidationContext + ProvableContext, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { let connection_id = ConnectionId::from_str(request.connection_id.as_str())?; @@ -162,7 +163,9 @@ where height.revision_height(), ); - let consensus_state = ibc_ctx.consensus_state(&consensus_path)?; + let client_val_ctx = ibc_ctx.get_client_validation_context(); + + let consensus_state = client_val_ctx.consensus_state(&consensus_path)?; let current_height = ibc_ctx.host_height()?; diff --git a/ibc-query/src/core/connection/service.rs b/ibc-query/src/core/connection/service.rs index a01a3b004e..75c20b1853 100644 --- a/ibc-query/src/core/connection/service.rs +++ b/ibc-query/src/core/connection/service.rs @@ -2,7 +2,7 @@ //! `I` must be a type where writes from one thread are readable from another. //! This means using `Arc>` or `Arc>` in most cases. -use ibc::core::host::ValidationContext; +use ibc::core::host::ConsensusStateRef; use ibc::core::primitives::prelude::*; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::core::connection::v1::query_server::Query as ConnectionQuery; @@ -28,8 +28,7 @@ use crate::core::context::QueryContext; pub struct ConnectionQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { ibc_context: I, } @@ -37,8 +36,7 @@ where impl ConnectionQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { /// The parameter `ibc_context` must be a type where writes from one thread are readable from another. /// This means using `Arc>` or `Arc>` in most cases. @@ -51,8 +49,7 @@ where impl ConnectionQuery for ConnectionQueryService where I: QueryContext + Send + Sync + 'static, - ::AnyClientState: Into, - ::AnyConsensusState: Into, + ConsensusStateRef: Into, { async fn connection( &self, diff --git a/ibc-query/src/core/context.rs b/ibc-query/src/core/context.rs index 2f4eef7a14..693dafec9b 100644 --- a/ibc-query/src/core/context.rs +++ b/ibc-query/src/core/context.rs @@ -7,7 +7,7 @@ use ibc::core::connection::types::IdentifiedConnectionEnd; use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::{ClientId, ConnectionId, Sequence}; use ibc::core::host::types::path::{ChannelEndPath, Path}; -use ibc::core::host::ValidationContext; +use ibc::core::host::{ClientStateRef, ConsensusStateRef, ValidationContext}; use ibc::core::primitives::prelude::*; /// Context to be implemented by the host to provide proofs in query responses @@ -22,15 +22,13 @@ pub trait QueryContext: ProvableContext + ValidationContext { // Client queries /// Returns the list of all clients. - fn client_states( - &self, - ) -> Result::AnyClientState)>, ContextError>; + fn client_states(&self) -> Result)>, ContextError>; /// Returns the list of all consensus states for the given client. fn consensus_states( &self, client_id: &ClientId, - ) -> Result::AnyConsensusState)>, ContextError>; + ) -> Result)>, ContextError>; /// Returns the list of all heights at which consensus states for the given client are. fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError>; diff --git a/ibc-testkit/src/relayer/context.rs b/ibc-testkit/src/relayer/context.rs index d413313457..852f91bd3d 100644 --- a/ibc-testkit/src/relayer/context.rs +++ b/ibc-testkit/src/relayer/context.rs @@ -1,3 +1,4 @@ +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::Height; use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::ClientId; diff --git a/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs b/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs index ab1cf2d737..39cd35cfee 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs @@ -1,10 +1,7 @@ use core::str::FromStr; use core::time::Duration; -use ibc::core::client::context::client_state::{ - ClientStateCommon, ClientStateExecution, ClientStateValidation, -}; -use ibc::core::client::context::{ClientExecutionContext, ClientValidationContext}; +use ibc::core::client::context::prelude::*; use ibc::core::client::types::error::{ClientError, UpgradeClientError}; use ibc::core::client::types::{Height, Status}; use ibc::core::commitment_types::commitment::{ @@ -126,24 +123,22 @@ impl From for Any { } } -pub trait MockClientContext { - type ConversionError: ToString; - type AnyConsensusState: TryInto; +pub trait ConsensusStateConverter: + TryInto + From +{ +} + +impl ConsensusStateConverter for C where + C: TryInto + From +{ +} +pub trait MockClientContext { /// Returns the current timestamp of the local chain. fn host_timestamp(&self) -> Result; /// Returns the current height of the local chain. fn host_height(&self) -> Result; - - /// Retrieve the consensus state for the given client ID at the specified - /// height. - /// - /// Returns an error if no such state exists. - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result; } impl ClientStateCommon for MockClientState { @@ -215,8 +210,7 @@ impl ClientStateCommon for MockClientState { impl ClientStateValidation for MockClientState where V: ClientValidationContext + MockClientContext, - V::AnyConsensusState: TryInto, - ClientError: From<>::Error>, + V::ConsensusStateRef: ConsensusStateConverter, { fn verify_client_message( &self, @@ -264,20 +258,17 @@ where return Ok(Status::Frozen); } - let latest_consensus_state: MockConsensusState = { - let any_latest_consensus_state = - match ctx.consensus_state(&ClientConsensusStatePath::new( - client_id.clone(), - self.latest_height().revision_number(), - self.latest_height().revision_height(), - )) { - Ok(cs) => cs, - // if the client state does not have an associated consensus state for its latest height - // then it must be expired - Err(_) => return Ok(Status::Expired), - }; - - any_latest_consensus_state.try_into()? + let latest_consensus_state = { + match ctx.consensus_state(&ClientConsensusStatePath::new( + client_id.clone(), + self.latest_height().revision_number(), + self.latest_height().revision_height(), + )) { + Ok(cs) => cs.try_into()?, + // if the client state does not have an associated consensus state for its latest height + // then it must be expired + Err(_) => return Ok(Status::Expired), + } }; let now = ctx.host_timestamp()?; @@ -298,8 +289,8 @@ where impl ClientStateExecution for MockClientState where E: ClientExecutionContext + MockClientContext, - ::AnyClientState: From, - ::AnyConsensusState: From, + E::ClientStateRef: From, + E::ConsensusStateRef: ConsensusStateConverter, { fn initialise( &self, diff --git a/ibc-testkit/src/testapp/ibc/clients/mod.rs b/ibc-testkit/src/testapp/ibc/clients/mod.rs index 27ce9e34c9..618466dc50 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mod.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mod.rs @@ -1,6 +1,6 @@ pub mod mock; -use derive_more::{From, TryInto}; +use derive_more::From; use ibc::clients::tendermint::client_state::ClientState as TmClientState; use ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; use ibc::clients::tendermint::types::{ @@ -67,7 +67,7 @@ impl From for AnyConsensusState { } } -#[derive(Debug, Clone, From, TryInto, PartialEq, ConsensusState)] +#[derive(Debug, Clone, From, PartialEq, ConsensusState)] pub enum AnyConsensusState { Tendermint(TmConsensusState), Mock(MockConsensusState), @@ -99,3 +99,30 @@ impl From for Any { } } } + +impl TryFrom for ConsensusStateType { + type Error = ClientError; + + fn try_from(value: AnyConsensusState) -> Result { + match value { + AnyConsensusState::Tendermint(cs) => Ok(cs.inner().clone()), + _ => Err(ClientError::Other { + description: "failed to convert AnyConsensusState to TmConsensusState".to_string(), + }), + } + } +} + +impl TryFrom for MockConsensusState { + type Error = ClientError; + + fn try_from(value: AnyConsensusState) -> Result { + match value { + AnyConsensusState::Mock(cs) => Ok(cs), + _ => Err(ClientError::Other { + description: "failed to convert AnyConsensusState to MockConsensusState" + .to_string(), + }), + } + } +} diff --git a/ibc-testkit/src/testapp/ibc/core/client_ctx.rs b/ibc-testkit/src/testapp/ibc/core/client_ctx.rs index 227c647afc..940e6fa43d 100644 --- a/ibc-testkit/src/testapp/ibc/core/client_ctx.rs +++ b/ibc-testkit/src/testapp/ibc/core/client_ctx.rs @@ -1,9 +1,4 @@ -use alloc::collections::BTreeMap; -use alloc::vec::Vec; - -use ibc::clients::tendermint::context::{ - CommonContext as TmCommonContext, ValidationContext as TmValidationContext, -}; +use ibc::clients::tendermint::context::ValidationContext as TmValidationContext; use ibc::core::client::context::{ClientExecutionContext, ClientValidationContext}; use ibc::core::client::types::error::ClientError; use ibc::core::client::types::Height; @@ -12,6 +7,7 @@ use ibc::core::host::types::identifiers::{ChannelId, ClientId, PortId}; use ibc::core::host::types::path::{ClientConsensusStatePath, ClientStatePath}; use ibc::core::host::ValidationContext; use ibc::core::primitives::Timestamp; +use ibc::primitives::prelude::*; use crate::testapp::ibc::clients::mock::client_state::MockClientContext; use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState}; @@ -31,9 +27,6 @@ pub struct MockClientRecord { } impl MockClientContext for MockContext { - type ConversionError = &'static str; - type AnyConsensusState = AnyConsensusState; - fn host_timestamp(&self) -> Result { ValidationContext::host_timestamp(self) } @@ -41,19 +34,9 @@ impl MockClientContext for MockContext { fn host_height(&self) -> Result { ValidationContext::host_height(self) } - - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - ValidationContext::consensus_state(self, client_cons_state_path) - } } -impl TmCommonContext for MockContext { - type ConversionError = &'static str; - type AnyConsensusState = AnyConsensusState; - +impl TmValidationContext for MockContext { fn host_timestamp(&self) -> Result { ValidationContext::host_timestamp(self) } @@ -62,13 +45,6 @@ impl TmCommonContext for MockContext { ValidationContext::host_height(self) } - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - ValidationContext::consensus_state(self, client_cons_state_path) - } - fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError> { let ibc_store = self.ibc_store.lock(); let client_record = @@ -83,14 +59,12 @@ impl TmCommonContext for MockContext { Ok(heights) } -} -impl TmValidationContext for MockContext { fn next_consensus_state( &self, client_id: &ClientId, height: &Height, - ) -> Result, ContextError> { + ) -> Result, ContextError> { let ibc_store = self.ibc_store.lock(); let client_record = ibc_store @@ -124,7 +98,7 @@ impl TmValidationContext for MockContext { &self, client_id: &ClientId, height: &Height, - ) -> Result, ContextError> { + ) -> Result, ContextError> { let ibc_store = self.ibc_store.lock(); let client_record = ibc_store @@ -156,7 +130,52 @@ impl TmValidationContext for MockContext { } impl ClientValidationContext for MockContext { - fn update_meta( + type ClientStateRef = AnyClientState; + type ConsensusStateRef = AnyConsensusState; + + fn client_state(&self, client_id: &ClientId) -> Result { + match self.ibc_store.lock().clients.get(client_id) { + Some(client_record) => { + client_record + .client_state + .clone() + .ok_or_else(|| ClientError::ClientStateNotFound { + client_id: client_id.clone(), + }) + } + None => Err(ClientError::ClientStateNotFound { + client_id: client_id.clone(), + }), + } + .map_err(ContextError::ClientError) + } + + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result { + let client_id = &client_cons_state_path.client_id; + let height = Height::new( + client_cons_state_path.revision_number, + client_cons_state_path.revision_height, + )?; + match self.ibc_store.lock().clients.get(client_id) { + Some(client_record) => match client_record.consensus_states.get(&height) { + Some(consensus_state) => Ok(consensus_state.clone()), + None => Err(ClientError::ConsensusStateNotFound { + client_id: client_id.clone(), + height, + }), + }, + None => Err(ClientError::ConsensusStateNotFound { + client_id: client_id.clone(), + height, + }), + } + .map_err(ContextError::ClientError) + } + + fn client_update_meta( &self, client_id: &ClientId, height: &Height, @@ -177,14 +196,12 @@ impl ClientValidationContext for MockContext { } impl ClientExecutionContext for MockContext { - type V = Self; - type AnyClientState = AnyClientState; - type AnyConsensusState = AnyConsensusState; + type ClientStateMut = AnyClientState; fn store_client_state( &mut self, client_state_path: ClientStatePath, - client_state: Self::AnyClientState, + client_state: Self::ClientStateRef, ) -> Result<(), ContextError> { let mut ibc_store = self.ibc_store.lock(); @@ -205,7 +222,7 @@ impl ClientExecutionContext for MockContext { fn store_consensus_state( &mut self, consensus_state_path: ClientConsensusStatePath, - consensus_state: Self::AnyConsensusState, + consensus_state: Self::ConsensusStateRef, ) -> Result<(), ContextError> { let mut ibc_store = self.ibc_store.lock(); diff --git a/ibc-testkit/src/testapp/ibc/core/core_ctx.rs b/ibc-testkit/src/testapp/ibc/core/core_ctx.rs index 5c561d14eb..4901b2d2ca 100644 --- a/ibc-testkit/src/testapp/ibc/core/core_ctx.rs +++ b/ibc-testkit/src/testapp/ibc/core/core_ctx.rs @@ -3,7 +3,6 @@ use core::ops::Add; use core::time::Duration; -use ibc::clients::tendermint::client_state::ClientState; use ibc::core::channel::types::channel::ChannelEnd; use ibc::core::channel::types::commitment::{AcknowledgementCommitment, PacketCommitment}; use ibc::core::channel::types::error::{ChannelError, PacketError}; @@ -15,81 +14,24 @@ use ibc::core::connection::types::error::ConnectionError; use ibc::core::connection::types::ConnectionEnd; use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::events::IbcEvent; -use ibc::core::host::types::identifiers::{ClientId, ConnectionId, Sequence}; +use ibc::core::host::types::identifiers::{ConnectionId, Sequence}; use ibc::core::host::types::path::{ - AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, CommitmentPath, - ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, CommitmentPath, ConnectionPath, ReceiptPath, + SeqAckPath, SeqRecvPath, SeqSendPath, }; use ibc::core::host::{ExecutionContext, ValidationContext}; use ibc::core::primitives::prelude::*; use ibc::core::primitives::{Signer, Timestamp}; -use ibc::primitives::proto::Any; use super::types::MockContext; use crate::testapp::ibc::clients::mock::client_state::MockClientState; -use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState}; +use crate::testapp::ibc::clients::mock::consensus_state::MockConsensusState; +use crate::testapp::ibc::clients::AnyConsensusState; impl ValidationContext for MockContext { type V = Self; - type E = Self; - type AnyConsensusState = AnyConsensusState; - type AnyClientState = AnyClientState; - - fn client_state(&self, client_id: &ClientId) -> Result { - match self.ibc_store.lock().clients.get(client_id) { - Some(client_record) => { - client_record - .client_state - .clone() - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - }) - } - None => Err(ClientError::ClientStateNotFound { - client_id: client_id.clone(), - }), - } - .map_err(ContextError::ClientError) - } - - fn decode_client_state(&self, client_state: Any) -> Result { - if let Ok(client_state) = ClientState::try_from(client_state.clone()) { - client_state.inner().validate().map_err(ClientError::from)?; - Ok(client_state.into()) - } else if let Ok(client_state) = MockClientState::try_from(client_state.clone()) { - Ok(client_state.into()) - } else { - Err(ClientError::UnknownClientStateType { - client_state_type: client_state.type_url, - }) - } - .map_err(ContextError::ClientError) - } - - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - let client_id = &client_cons_state_path.client_id; - let height = Height::new( - client_cons_state_path.revision_number, - client_cons_state_path.revision_height, - )?; - match self.ibc_store.lock().clients.get(client_id) { - Some(client_record) => match client_record.consensus_states.get(&height) { - Some(consensus_state) => Ok(consensus_state.clone()), - None => Err(ClientError::ConsensusStateNotFound { - client_id: client_id.clone(), - height, - }), - }, - None => Err(ClientError::ConsensusStateNotFound { - client_id: client_id.clone(), - height, - }), - } - .map_err(ContextError::ClientError) - } + type HostClientState = MockClientState; + type HostConsensusState = MockConsensusState; fn host_height(&self) -> Result { Ok(self.latest_height()) @@ -105,40 +47,31 @@ impl ValidationContext for MockContext { .expect("Never fails")) } - fn host_consensus_state(&self, height: &Height) -> Result { - match self.host_block(height) { - Some(block_ref) => Ok(block_ref.clone().into()), - None => Err(ClientError::MissingLocalConsensusState { height: *height }), - } - .map_err(ConnectionError::Client) - .map_err(ContextError::ConnectionError) - } - fn client_counter(&self) -> Result { Ok(self.ibc_store.lock().client_ids_counter) } - fn connection_end(&self, cid: &ConnectionId) -> Result { - match self.ibc_store.lock().connections.get(cid) { - Some(connection_end) => Ok(connection_end.clone()), - None => Err(ConnectionError::ConnectionNotFound { - connection_id: cid.clone(), - }), + fn host_consensus_state(&self, height: &Height) -> Result { + let cs: AnyConsensusState = match self.host_block(height) { + Some(block_ref) => Ok(block_ref.clone().into()), + None => Err(ClientError::MissingLocalConsensusState { height: *height }), + } + .map_err(ContextError::ClientError)?; + + match cs { + AnyConsensusState::Mock(cs) => Ok(cs), + _ => Err(ClientError::Other { + description: "unexpected consensus state type".to_string(), + } + .into()), } - .map_err(ContextError::ConnectionError) } fn validate_self_client( &self, - client_state_of_host_on_counterparty: Any, + client_state_of_host_on_counterparty: Self::HostClientState, ) -> Result<(), ContextError> { - let mock_client_state = MockClientState::try_from(client_state_of_host_on_counterparty) - .map_err(|_| ConnectionError::InvalidClientState { - reason: "client must be a mock client".to_string(), - }) - .map_err(ContextError::ConnectionError)?; - - if mock_client_state.is_frozen() { + if client_state_of_host_on_counterparty.is_frozen() { return Err(ClientError::ClientFrozen { description: String::new(), } @@ -147,25 +80,31 @@ impl ValidationContext for MockContext { let self_chain_id = &self.host_chain_id; let self_revision_number = self_chain_id.revision_number(); - if self_revision_number != mock_client_state.latest_height().revision_number() { + if self_revision_number + != client_state_of_host_on_counterparty + .latest_height() + .revision_number() + { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "client is not in the same revision as the chain. expected: {}, got: {}", self_revision_number, - mock_client_state.latest_height().revision_number() + client_state_of_host_on_counterparty + .latest_height() + .revision_number() ), }, )); } let host_current_height = self.latest_height().increment(); - if mock_client_state.latest_height() >= host_current_height { + if client_state_of_host_on_counterparty.latest_height() >= host_current_height { return Err(ContextError::ConnectionError( ConnectionError::InvalidClientState { reason: format!( "client has latest height {} greater than or equal to chain height {}", - mock_client_state.latest_height(), + client_state_of_host_on_counterparty.latest_height(), host_current_height ), }, @@ -175,6 +114,16 @@ impl ValidationContext for MockContext { Ok(()) } + fn connection_end(&self, cid: &ConnectionId) -> Result { + match self.ibc_store.lock().connections.get(cid) { + Some(connection_end) => Ok(connection_end.clone()), + None => Err(ConnectionError::ConnectionNotFound { + connection_id: cid.clone(), + }), + } + .map_err(ContextError::ConnectionError) + } + fn commitment_prefix(&self) -> CommitmentPrefix { CommitmentPrefix::try_from(b"mock".to_vec()).expect("Never fails") } @@ -350,6 +299,8 @@ impl ValidationContext for MockContext { } impl ExecutionContext for MockContext { + type E = Self; + fn get_client_execution_context(&mut self) -> &mut Self::E { self } diff --git a/ibc-testkit/tests/core/ics02_client/create_client.rs b/ibc-testkit/tests/core/ics02_client/create_client.rs index 3540aefb62..447af33fda 100644 --- a/ibc-testkit/tests/core/ics02_client/create_client.rs +++ b/ibc-testkit/tests/core/ics02_client/create_client.rs @@ -2,13 +2,14 @@ use ibc::clients::tendermint::types::{ client_type as tm_client_type, ConsensusState as TmConsensusState, }; use ibc::core::client::context::client_state::ClientStateCommon; +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::error::ClientError; use ibc::core::client::types::msgs::{ClientMsg, MsgCreateClient}; use ibc::core::client::types::Height; use ibc::core::entrypoint::{execute, validate}; use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::msgs::MsgEnvelope; -use ibc::core::host::ValidationContext; +use ibc::core::host::{ClientStateRef, ValidationContext}; use ibc_testkit::fixtures::clients::tendermint::{ dummy_tendermint_header, dummy_tm_client_state_from_header, }; @@ -48,7 +49,7 @@ fn test_create_client_ok() { assert!(res.is_ok(), "execution happy path"); - let expected_client_state = ctx.decode_client_state(msg.client_state).unwrap(); + let expected_client_state = ClientStateRef::::try_from(msg.client_state).unwrap(); assert_eq!(expected_client_state.client_type(), client_type); assert_eq!(ctx.client_state(&client_id).unwrap(), expected_client_state); } @@ -84,7 +85,7 @@ fn test_tm_create_client_ok() { assert!(res.is_ok(), "tendermint client execution happy path"); - let expected_client_state = ctx.decode_client_state(msg.client_state).unwrap(); + let expected_client_state = ClientStateRef::::try_from(msg.client_state).unwrap(); assert_eq!(expected_client_state.client_type(), client_type); assert_eq!(ctx.client_state(&client_id).unwrap(), expected_client_state); } diff --git a/ibc-testkit/tests/core/ics02_client/update_client.rs b/ibc-testkit/tests/core/ics02_client/update_client.rs index bc7bf02ad6..a3fd7bec7f 100644 --- a/ibc-testkit/tests/core/ics02_client/update_client.rs +++ b/ibc-testkit/tests/core/ics02_client/update_client.rs @@ -256,7 +256,7 @@ fn test_consensus_state_pruning() { expired_height.revision_number(), expired_height.revision_height(), ); - assert!(ctx.update_meta(&client_id, &expired_height).is_err()); + assert!(ctx.client_update_meta(&client_id, &expired_height).is_err()); assert!(ctx.consensus_state(&client_cons_state_path).is_err()); // Check that latest valid consensus state exists. @@ -267,7 +267,9 @@ fn test_consensus_state_pruning() { earliest_valid_height.revision_height(), ); - assert!(ctx.update_meta(&client_id, &earliest_valid_height).is_ok()); + assert!(ctx + .client_update_meta(&client_id, &earliest_valid_height) + .is_ok()); assert!(ctx.consensus_state(&client_cons_state_path).is_ok()); let end_host_timestamp = ctx.host_timestamp().unwrap(); diff --git a/ibc-testkit/tests/core/ics02_client/upgrade_client.rs b/ibc-testkit/tests/core/ics02_client/upgrade_client.rs index 84d5b1fbdb..d9b15c173a 100644 --- a/ibc-testkit/tests/core/ics02_client/upgrade_client.rs +++ b/ibc-testkit/tests/core/ics02_client/upgrade_client.rs @@ -1,4 +1,5 @@ use ibc::clients::tendermint::types::client_type; +use ibc::core::client::context::ClientValidationContext; use ibc::core::client::types::error::{ClientError, UpgradeClientError}; use ibc::core::client::types::msgs::{ClientMsg, MsgUpgradeClient}; use ibc::core::client::types::Height; @@ -7,7 +8,6 @@ use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::events::{IbcEvent, MessageEvent}; use ibc::core::handler::types::msgs::MsgEnvelope; use ibc::core::host::types::path::ClientConsensusStatePath; -use ibc::core::host::ValidationContext; use ibc::core::primitives::downcast; use ibc_testkit::fixtures::clients::tendermint::{ dummy_tendermint_header, dummy_tm_client_state_from_header, From e1f19af161878f9daf779aed0f19229f97d54814 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Fri, 8 Mar 2024 11:12:25 -0800 Subject: [PATCH 3/6] docs: ADR-010 enabling standalone ICS-02 integration (#1116) * docs: ADR-010 enabling standalone ICS-02 integration * imp: explaining why decode_client_state will be removed * imp: use diff-formatted code snippets * chore: put a comment for decode_client_state * chore: apply suggestions from code review Co-authored-by: Sean Chen Signed-off-by: Farhad Shabani * chore: apply suggestions from code review Co-authored-by: Sean Chen Signed-off-by: Farhad Shabani * imp: update with details for host_consensus_state & validate_self_client * nit: lint markdown * nit: rename to HostClient/ConsensusState * imp: update ADR with changes in implementation PR * chore: back to the original implementation idea * fix: adjust ClientStateDecoder * nit * nit: add comment for minimizing associated types --------- Signed-off-by: Farhad Shabani Co-authored-by: Sean Chen --- ...010-enable-standalone-ics02-integration.md | 380 ++++++++++++++++++ docs/architecture/assets/adr010.png | Bin 0 -> 172082 bytes 2 files changed, 380 insertions(+) create mode 100644 docs/architecture/adr-010-enable-standalone-ics02-integration.md create mode 100644 docs/architecture/assets/adr010.png diff --git a/docs/architecture/adr-010-enable-standalone-ics02-integration.md b/docs/architecture/adr-010-enable-standalone-ics02-integration.md new file mode 100644 index 0000000000..117bea1086 --- /dev/null +++ b/docs/architecture/adr-010-enable-standalone-ics02-integration.md @@ -0,0 +1,380 @@ +# ADR 010: ENABLE STANDALONE ICS-02 INTEGRATION + +## Changelog + +- 2024-03-05: Draft Proposed + +## Status + +Proposed + +## Context + +In ADR-07, we previously granted light client developers the flexibility to +introduce custom APIs to the client contexts. This decision was made to +accommodate potential scenarios where not all methods required by every future +light client might be present in the extensive +[`ValidationContext`](https://github.com/cosmos/ibc-rs/blob/4421b0dcd6eab6c914b8c5a7d45817b1edb6b2ce/ibc-core/ics24-host/src/context.rs#L31) +or +[`ExecutionContext`](https://github.com/cosmos/ibc-rs/blob/4421b0dcd6eab6c914b8c5a7d45817b1edb6b2ce/ibc-core/ics24-host/src/context.rs#L168) +traits. + +In this ADR, while retaining that capability, we propose a reorganization of +client-related APIs by consolidating them under the ICS-02 client validation and +execution contexts. While the main top-level context traits in ICS-24 +(`ibc-core-host` crate) will still have access to these APIs through the +existing accessor +[`get_client_validation_context()`](https://github.com/cosmos/ibc-rs/blob/4421b0dcd6eab6c914b8c5a7d45817b1edb6b2ce/ibc-core/ics24-host/src/context.rs#L38) +and +[`get_client_execution_context()`](https://github.com/cosmos/ibc-rs/blob/4421b0dcd6eab6c914b8c5a7d45817b1edb6b2ce/ibc-core/ics24-host/src/context.rs#L170) +methods. + +As a result of this ADR, client relevant methods, namely **`client_state()`** +and **`consensus_state()`**, will be extracted from the large ICS-24 validation +and execution context traits. Instead, they will find their new home under the +ICS-02 +[`ClientValidationContext`](https://github.com/cosmos/ibc-rs/blob/caee889ad308a4aac3f9905de9cdda76c5533cfb/ibc-core/ics02-client/context/src/context.rs#L14) +and +[`ClientExecutionContext`](https://github.com/cosmos/ibc-rs/blob/caee889ad308a4aac3f9905de9cdda76c5533cfb/ibc-core/ics02-client/context/src/context.rs#L32) +traits. + +## Objectives + +The primary objective is to enhance the delineation of the APIs that connect +ibc-rs to the host’s storage, offering a more modular approach that allows +developers who wish to integrate only the implemented client states under the +ibc-rs to their IBC infra. In other words, this enhancement does not mandate +hosts to implement all the IBC layers, such as connection, channel, or any of +the IBC applications, when only light client support is required. + +As a result, this ADR sets the stage for several potential use cases, including: + +1. **Streamlines CosmWasm light client implementation** + - Since the refined structure allows for the independent integration of + ICS-02 client traits into the storage, it becomes straightforward to + implement light clients living in ibc-rs as CosmWasm contracts, which + empowers simpler Wasm client integration with ibc-go driven chains. + +2. **Enables client integration with various IBC implementations** + - Any IBC implementation, whether it is based on ibc-rs or a fork of ibc-rs, + will have this capability to incorporate the latest light client + implementations by ibc-rs. So that even if an IBC implementation diverges + from the ibc-rs framework, hosts can benefit from the latest version of + light client implementation. This is important from the security + standpoint, broadens the applicability of ibc-rs, and facilitates + collaboration with other IBC-adjacent solutions. + +As part of this ADR and to minimize breaking changes later, we aim to address +the following client-relevant API deficiencies as well: + +1. **Refining access to validation vs execution methods** + - So far, unauthorized access to client execution methods [has been + possible](https://github.com/cosmos/ibc-rs/blob/4421b0dcd6eab6c914b8c5a7d45817b1edb6b2ce/ibc-core/ics24-host/src/context.rs#L33) + under the `ValidationContext`. This will be rectified to ensure that only + the `ClientState` validation method is callable within the validation + contexts. This enhances the precision of access control, aligning it more + closely with the intended functionality. + +2. **Bringing more clarity to how context APIs are related** + - The ambiguous relationship between the main context APIs at ICS-24, ICS-02 + client APIs, and client-specific APIs can get cleared. This ADR aims for a + more transparent relationship between these contexts, making it easier to + understand their interactions and roles. For instance some client relevant + methods are located under the client contexts, while others are under the + main (ICS-24) contexts. + +3. **Minimizing associated types’ declarations** + - Our current APIs pose a challenge by requiring users to introduce multiple + yet the same associated types like `AnyClientState`, `AnyConsensusState`, + etc across different layers. A more efficient solution involves + introducing such types in fewer places, providing better access to the + traits across different layers. However, it's worth acknowledging that an + ideal solution might entail utilizing features from forthcoming stable + versions of Rust. This would enable us to leverage the advantages provided + by either + [associated_type_bounds](https://rust-lang.github.io/rfcs/2289-associated-type-bounds.html), + [associated_type_defaults](https://rust-lang.github.io/rfcs/2532-associated-type-defaults.html), + or + [implied_bounds](https://rust-lang.github.io/rfcs/2089-implied-bounds.html) + capabilities. + +## Decision + +Here is a high-level diagram that illustrates the optimal boundaries for each +context trait, specifying the scope of access by each of these contexts. In +addition, the classification of APIs should be such that they allow the +independent integration of light client implementations. + +
+ Main components +
+ +The primary `ValidationContext` and `ExecutionContext` traits at the ICS-24 host +level will be restructured as follows, only having sufficient access to +respective ICS-02 client contexts, without caring about the types or methods +associated with client or consensus states of counterparty chains. It is +noteworthy that, to better illustrate the desired outcome from the current +state, the code snippets below are in the `diff` format. + +```diff +pub trait ValidationContext { + type V: ClientValidationContext; ++ type HostClientState: ClientStateValidation; ++ type HostConsensusState: ConsensusState; +- type E: ClientExecutionContext; +- type AnyConsensusState: ConsensusState; +- type AnyClientState: ClientState; + + /// Retrieve the context that implements all clients' `ValidationContext`. + fn get_client_validation_context(&self) -> &Self::V; + + // This method will be removed and replaced by a `ClientStateDecoder` trait that will encapsulate the ability to decode a client state from an `Any` +- fn decode_client_state(&self, client_state: Any) -> Result; + +- fn client_state(&self, client_id: &ClientId) -> Result; + +- fn consensus_state( +- &self, +- client_cons_state_path: &ClientConsensusStatePath, +- ) -> Result; + + fn host_consensus_state( + &self, + height: &Height, +- ) -> Result; ++ ) -> Result; + + fn validate_self_client( + &self, +- client_state_of_host_on_counterparty: Any, ++ client_state_of_host_on_counterparty: Self::HostClientState, + ) -> Result<(), ContextError>; + + ... // other methods +} + +pub trait ExecutionContext: ValidationContext { ++ type E: ClientExecutionContext; + + /// Retrieve the context that implements all clients' `ExecutionContext`. + fn get_client_execution_context(&mut self) -> &mut Self::E; + + ... // other methods + +/// Convenient type aliases ++ pub type ClientStateRef = ++ <::V as ClientValidationContext>::ClientStateRef; + ++ pub type ClientStateMut = ++ <::E as ClientExecutionContext>::ClientStateMut; + ++ pub type ConsensusStateRef = ++ <::V as ClientValidationContext>::ConsensusStateRef; +} +``` + +We should also highlight that ICS-02 houses various types, APIs and +implementations essential for enabling **light clients of counterparty chains +operating on the host chain**. Therefore, the `host_consensus_state()` and +`validate_self_client()` methods, though initially appearing to be ICS-02 +specific, play a crucial role in the connection handshake validating receiving +datagrams against the client and consensus states of the host. + +In this ADR, these methods will continue to be housed under the main context +traits. However, we will explicitly define the accepted types for these methods +as `HostClientState` and `HostConsensusState`. This refinement aims for more +clarity and will optimize the decoding process, removing an unnecessary layer of +decoding during information retrieval. Previously, the decoding process for +these methods involved obtaining `AnyClientState` and `AnyConsensusState` types +and then converting them into the concrete `HostClientState` and +`HostConsensusState` types. + +Following the aforementioned points, in the ICS-02 level, the +`ClientValidationContext` and `ClientExecutionContext` traits will be +restructured as follows, containing all the client relevant methods and types: + +```diff +pub trait ClientValidationContext: Sized { + // Given that we will be dropping `decode_client_state()` method, + // the client state type introduced here should have implemented `TryFrom` and `Into` traits. ++ type ClientStateRef: ClientStateValidation; ++ type ConsensusStateRef: ConsensusState; + ++ fn client_state(&self, client_id: &ClientId) -> Result; + ++ fn consensus_state( ++ &self, ++ client_cons_state_path: &ClientConsensusStatePath, ++ ) -> Result; + + fn client_update_meta( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result<(Timestamp, Height), ContextError>; +} + +pub trait ClientExecutionContext: ++ ClientValidationContext +{ +- type V: ClientValidationContext; +- type AnyClientState: ClientState; +- type AnyConsensusState: ConsensusState; ++ type ClientStateMut: ClientStateExecution; + ++ fn client_state_mut(&self, client_id: &ClientId) -> Result { ++ self.client_state(client_id) ++ } + + fn store_client_state( + &mut self, + client_state_path: ClientStatePath, + client_state: Self::ClientStateMut, + ) -> Result<(), ContextError>; + + fn store_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + consensus_state: Self::ConsensusStateRef, + ) -> Result<(), ContextError>; + + fn delete_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + ) -> Result<(), ContextError>; + + fn store_update_meta( + &mut self, + client_id: ClientId, + height: Height, + host_timestamp: Timestamp, + host_height: Height, + ) -> Result<(), ContextError>; + + fn delete_update_meta( + &mut self, + client_id: ClientId, + height: Height, + ) -> Result<(), ContextError>; +} +``` + +The introduction of the `ClientStateMut` associated type in addition to the +`ClientStateRef` became necessary to tackle the limitation that the +`ClientState` retrieved from the regular `client_state()` method provides access +only to validation methods. However, in `execute` handlers, there are scenarios +(like +[here](https://github.com/cosmos/ibc-rs/blob/f272f30e0f773d85a99fc553b75d41f9f768d5c5/ibc-core/ics02-client/src/handler/update_client.rs#L54)) +where access to the execution methods of the client state is required. We aim to +simplify the user experience by providing a default implementation, relieving +users from the need to implement the `client_state_mut` method. + +Also, the introduction of `` is prompted +by the need to address the characteristics of concrete `ClientState` +definitions. For instance, in the case of ICS-07, such as `TmClientState`, the +struct definition can't be split into two fragments, one for validation and the +other for execution. Therefore, contexts implementing `ClientExecutionContext` +must introduce a `ClientStateMut` type the same as `ClientStateRef`. + +With the mentioned classification, we can now streamline ICS-07 specific APIs, +eliminating the requirement for implementing a redundant `consensus_state()` +method. For the sake of simplification, we can remove the `CommonContext` trait +and consolidate everything under the `TmValidationContext` as follows: + +```diff ++ /// Enables conversion (`TryInto` and `From`) between the consensus state type ++ /// used by the host and the one specific to the Tendermint light client, which ++ /// is `ConsensusStateType`. ++ pub trait ConsensusStateConverter: ++ TryInto + From ++ { ++ } + ++ impl ConsensusStateConverter for C where ++ C: TryInto + From ++ { ++ } + +- pub trait CommonContext { +- // methods will be moved to the below `ValidationContext` +- } + +// Client's context required during validation +pub trait ValidationContext: ++ ClientValidationContext +{ ++ type ConversionError: ToString; ++ type AnyConsensusState: TryInto; + ++ fn host_timestamp(&self) -> Result; + ++ fn host_height(&self) -> Result; + +- fn consensus_state( +- &self, +- client_cons_state_path: &ClientConsensusStatePath, +- ) -> Result; + ++ fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError>; + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError>; + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError>; +} + +-impl ExecutionContext for T where T: CommonContext + ClientExecutionContext {} ++impl ExecutionContext for T where T: ValidationContext + ClientExecutionContext {} + +``` + +### Remarks + +- We move away from the `decode_client_state()` method. Since per our design, + users must utilize a `ClientState` type that implements both `TryFrom` + and `Into`, therefore, we can offer a trait called `ClientStateDecoder` + as follows, making it readily available for users seeking to decode/encode a + client state from/into the `Any` type: + + ```rust + pub trait ClientStateDecoder: TryFrom + Into {} + + impl ClientStateDecoder for T where T: TryFrom {} + + ``` + +- We will maintain the `client_counter()` and `increase_client_counter()` + methods within the main context traits. This stems from the fact that light + clients do not rely on the relative positions in their processes. + Additionally, these counters are globally tracked, and only IBC handlers + invoke these methods for setting or retrieving client identifiers. + +## Consequences + +### Positive + +- Enables easy light client integration without the need for integrating the + entire IBC stack +- Establishes a clearer relationship between APIs (ICS-02 <> ICS-24), promoting + better development practices +- Eliminates redundant methods/types, enhancing integration efficiency +- Methods under the client contexts will align more with ibc-go client keepers, + improving interoperability. + +### Negative + +- Some challenges may arise in identifying trait bounds during a light client + implementation. + - Introducing user-importable type aliases helps mitigate this concern. +- Additionally, this ADR employs significant breaking changes to the hosts. + +## References + +- [Issue link](https://github.com/cosmos/ibc-rs/issues/1114) diff --git a/docs/architecture/assets/adr010.png b/docs/architecture/assets/adr010.png new file mode 100644 index 0000000000000000000000000000000000000000..c64e4c40f7c95ef29f42e65e40f7db22de3da113 GIT binary patch literal 172082 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

0Po=a^dT9>d;1V@NH7e6~ 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%0DI~ZPk z|K4@9a)^=3eo@_7t4a?Q@O&WLvz)Bxc6q zL)*ZyR`xERj0$hh19!^sT(!}(&sLjE2QcA-a(`lE>p0}+`8=is47TyDH7)CzbpA?w ziI->Rr=90zoGE>Xh(u0>X2yHn5)_0GjdzhD!UsIcGlUD~+miQkiPrDF@tRXPQ%lg`UHJ;1!z!04@o@P!mXPpK;Nk*tsYI4u@T`t||} zDI2>yYbQ=DTX_9hp&Lu_Tj}ACYpLRBs@QLQNN)gl{Xzi)t=8?~RYN=H-f>y))?EAx_ z_>scWkN2V6X1FCQ`Q?-ABf(lJks5r0*2k`g=C=~L6}F*B^bnXSueX!#T=M5#p-MdAY&E5TgRJ>jUhYE)Kzz_HfVL5yBws(zA5)M z;zPo{URc1bGw&e0(>~B0i?F5o#_ucEBxU*`=n$RnXvvVd~YgdT1flgG$qgcG| zXMyb+P__elKLIbvH}L~6FRuyOX~zVgknpm?{QX*`T~hkXKC5>|&{uG?(=#QfI0*E6 zBEE}6d~(0XN^i2vq0I0E;;H;JnBkaFaDf8@p01yxdsME>V$+I-jmI=zdJP_izfsT; zau9Od=CWgI`|hLQLtzGnNNVPx$7IiQJnIYQQg^cZ9A_1|$*Ru-w!}pgh;>*?s=kj6 zDAag7_$;Ce%`+Gs069^};E%tPgK*`aGsw^c`Y>IVsAa)+*)_jUjJb1gGRcIrX~Qhv zc9x2BLV zqLV$?5{$(b;#c)~eI&4jKXa7cNw-MSDwV>>OOmj(U!_Pc=N4f`$Q@Zf!E${@_xL^b zqkNiPgzNHcQR~r9^Ph=^Edg4HM#QetVmnyV?KzK_W+L`PErU2g%sPfeq9G zk$pJ3D3D|`v|Uiu&M16i4nlg`gmbW`RYyb$Z}Ll~3I5_7YR$->pkaG_wZ-?%f212W zL`r;R*cNsI@xJ6izs>H4-}LhT$HWdlyK{3jR z?nU@X>uor3TPI1qE9c;HcP-7%9=5y(F`$5?xnKOm+=VBUTx+v9?6Jswt6SF6+TVnU z_gH_5H?cOMtF40`Pl(S&?uqs^fZp>dJql{Dduu(EW8fsvo72IRG5=E0-{U5ByI)g2 zZ00Ik(%o&&_pvEWrg$+2Tkwk3b1BW8fPLtDILykaXTBcBPFJFq3j!BTmiIBZ-}*%S za5frpHpxdZ7)Oo(@tpht`iFh(!Bi)Lyf%3Yf+`56;y=SF+wT6@KjI)-vjU}Cb^LCA zlC-$y!iV)^O@s1+_0jDHKN%boM;`NQIemAg{m!PRmuBv%h*2FYcepxTkI(7$RCJ~^ zDLA>FjYpZ!Gn9BSney${hdwTw{;4oB6@wfRxLQ4IYfKpD{T(IuRt7P{?e-0&tdGq0 zc>RTB_1sY9gCB&KeXrFmnIPI_@4s{C?R8deM10I~vY10jP&d%~fPh}kcoX{TN5lj( z8huvs_i#WATag^kqfb$AKY628NCUOcCTa`EBMJh;BtNPKZR^HP*NfWG6z+LdS!oiu zWUiPUoO559jK0G}DxT~7Car|bkCn=dOF!j~Ygy*sZK%*Ld39}LXleaF)8jZYbd z`u^Uc+p*UtIh?ut3SF%UknSr_egFk($n_eKjO4Zfqo}`>lFtNgam&QO=N0-2X{m^p zo$EQbhq&mb$0G;WdC;_^;{qYdJR!+`@<*g0U)+>jbL{P?Q@%czis34vANl+SRY@y4 zNcdw-X?42=F@6SPXO6u|{CmU=L`4I?7KC%sWAmDD^do1sN&$a{I>d~ToQ=@~GSgW#`(@g9G z-iZkxh6cf)sDw z(CY}?@)|1V-6!*#W=IbvP>}_mfsYv)R0TZQ4$9eaTSwDhvlhu^uW7?zc6Fd}>;h>C zvi%|nk|6HgPxxI3>EnZkasS@!f)LcFm1!oO;XHF2L% zLBDqm9EKC7Wv{2)Qw5F5W_3~Nti=?!ornVKWUj|yc0OMqA#&Pb3M0WDnjmY#rT$$; zTTPB4`6L7zGqu_Tj=OjSaxY(eB}_%?o>74VpLHk~8Y8HmE%tj(+R?Z?+JDFD8Ct$y zC3Rf3yVipM*p{7~IxTqd>zEjQ4i}jz%_dN z=?B=SUl)uF@S*+oBXVU`iYgW6%INo*UCbgan-Q~G(sASC4dh8$k$_rW@2PLR!WWUR zEl$M)&&CI;H&=VJ{VRSis4Eppfo}U;d=3^NzUDJJ=$AB!&jdF0Wrma#-rglRzb^=L zZ7ZhZqV~CO#l`mou2Fc8RsLONE!+*bf+=O+r9go!DYA{o+vocC{}cq;K16ZwP37rR z!o`d#^NDaii>*Vxbm8M!-W^jmd?ck--$Aj^hSlA}e`24wKm!)fK~){J8}NCHq?)@D zxBmuXuxWJVo($qa3{nKMM~k0CqRDHE*)Q!4QdJM6`MF9mbki)+0apX;_ZuQiVdOUY zB%{qPnbC0*ZQbY_@WZ(&4QlCak*!^O=YkZF)x6;Iumr8#m(t(gLDYrtHZ4-SyyR5d z+lwA1sxiY8wVY=1FLOUVy;n4rw1UYBZ=NPT_|@RJ4KyM~-ij?h${ZiqZKA~c$XB@y zF3#eBul{#@;%5v~bYZXdv{c0d(EoVTKlh0c&4c^{N6xPtKAbsDd)o4-&{G448MP{yZ{Z&hly4a+bX)tGro(qL*Sv zZIs_D?IP6!;Oz|?H60|oHs2@yweMj*_xkX(VJt3|szhjg0}NCg;3hj9;ChfvkrImE znJKl$rpP!S;w@&@wU@uZsJ|$nH zV`3)d>2Q(!17Z%_y`T*e?Y*OrJ2=_*3Aq;LW!!&!WF#~wwP{-c9ni0#Zb^O(h(kn| zzRgMUrazw4y`>Fi7vg)IjPhd8pugenZHd9h8+&q7M4m)9^;d@q@L)98QH)$&2SZ8p z(V0A2MMWdja+RZVUsCQSFb2agbNd+Ulw0O?+={;>#bq;IE-*jXdYA>85lHX=T3M|z zxzjh`03TlEYijgQ3cHWTjhx|5_ok&7DY%n z+R5c9p(F8|T40UHjMU&D(RpymrmBr^v-dtJ%8&|BD# z3YS^j^7phA3FxV^?BnXWBe7bM?E~H{KDx#VUlc$=z;XHpPD9whz*-+H+?P=K%6xnY zX2vD^Zz>6~X!7!L5Mrj^;_7P$n)(xupA$Ct$BTg*w5ufdCE^pfT*gqvUeQFlk~nOW zxA1#B7Ch?x7y+yfXRL4)ItuppNS=FRNU7V?6-88W{I;T_lv8HyJ|JVVCIz-zdx|`y z^f%B@=VA z7S#oO&)&sl^Chd!53i}FAttuHqN}IUxc&$Qz@xuEGhxT<>RK*n`Ixp z`$z~xGA3qsGLK(pqXG-2iQ}a*P=EpkY6&PnrzPJQ1`6=k3jFz=?L(WKG8jt7xtR#0 z`^$SfB1nNpzaCjzv2u!-eoen#Uaa|>2>$ci7WECw6tu6;plvAvB6|QH8IL727zd07 zX~Dth`qg6UKmX(!iV5D1Bk~Tt4KE701c1TBhnN4W|MDMZ{jF7geMd(7&;`SG_GJ3= z7N17FIB<+l|0?u+n z)ZZQXvo$^sX9Lsd>iCt8mfo+#m%=G(f9bqm#AuGXlO=CH{}Jbx_C#*_H57S#$lQ|I z>@1`jQ$!v|K+E#Z-~asAYJk@a0jdYNc4Bfu>pQ_g}0M0NaR_Oz2;pgF3w_q);mc(=bl zX~KN617KokaQn+lQxKsEy#9Wg1qr(DqPRy0?8Lv9{P#gG?JPZju2(p?*3$mT-u)kObZkN-LT z@Ex6lru@=>N&>zn?zC7Io_v)%xjZVNLk21lInYi~Bzp`}GX;Nj|kf zNamR%B1)rU`Hv<2=cf-3>egex1$f6}xG1jBd9Qyj(*GPkzeP7|otAy?TU0nc!GzLQ z3!rOzI62=xbp3a6elz?FppWIL^)?IkxH5*(iRa%bJq9mNe=u8O^*<8f1BUB81yb98 zZjxb&xX+>qDplRJCP+R})BQb|IDG659uiE$olt`QIFdYyUkAt)!T!bM zC$trIV7jFh+WX}{dobI9_VPz-lsu99zaDBIP3!dm2bJsI7O<0os`%Z6HYlNA=3jqJ z_=gDp*8z~?fQ|!$HYHv4CUqGAGule1Q~h_U;EwVE#+g_hf7fYT8okj?KD>La&g?+F zPi=fH*81;jXv=}3MhFSTXZ4NCPPch?7JjpD-~xV^?;6TFS|pw4`$MPH|4V~D(sylD zezT%{T(uBO6$Y@^U1-$r{`cpiVMb9SfP}ibrMW8fPZPMr-$~1^O~N1k?K}S;UXi1k z4MHwyvg0Vdp*kR##ZM)mf$o2!fG6T#hrmAh(ru$_!>R|Cfl1ap^=?OX*4$MF?B)bTXWLtX&n#zfkqrF*{}&mq zz@%3?@q1Pbu72ZTD&~J*7uTmKYWI-qBD8a`n7C4150o3JbjWXma&`_)yTL=!Ka4cS zMI%=PbMF{#X*$Bj#&$51N{vrK#5hjG`08CLI@tSkCc~8&Pq3zd`xZaCP9w`WKE2k*Abgf+%Yd}>^wk+* z|D{vLx(mY7lM|W=Xfz(hIYN-v{gVH6qnFZ7P>6@nn;LsX7=KNRH--0(0Q!w(`@jnQ zd;ct5hx}Ip5&7P^jvD@g2SG#{)b9{rwj$luuX&7sY3|e5UbM=ZUm9uF9Da-tUafaw zBw4#y>7&k7sfw7a)Y&Fi&NFrr_MEC$u+$>mJU8N!O=b&*33wVi@olRXO1y5kc<;Nn zIBdmdwsw1buPJ6tt7xrTqOQr3drVmKcg)>1jgqEjO@ zVA7k}Bp$A@sb2qz;Aff}9>WL0#-U2ne<*Ku3{C3=(sD7GDan`yP}Ph_VZThVYiN_P z&!z4{#u6*UQ&8|pNQ9@S1vo5iy*m?VgQrXg3CLud#6vUd&n}DILS9^)y*$iKp;-Ye z#NR3GAO7PQayH(~`}#{chxgHKa8n&$FTw2-zsl`%(YnjV&X~^{QLb~-S&~2Q|JC$g zmoRCnR?I|p0P23vaaJYBz(wL_7(!ndkVeV%Z}z`sgI3(Hhr%m+RlvJPnXg$94c6tj zy4+dO&d!cQx83%q!Ie&KR#BW+=(JlcKLMkn$h%5^Kfk`v#46K^R%XYAWm%PpmVJ|8 zmzlL>7*z`h7Cc|26CZ9542+j7fcV*Oo#K;-D`5>LR0mHU%vbM}0ui?} zr)$QG#(%Dfkp#~2u>i{6Y~pm|YW>F4m+^NZu21&Elowzl@4Vi7G>5U^^8)Hxj)lkl9qg2SqOREwq_x{wP((NqeR zU${IWlxUMpqT0)o8vfZuc%YJ~ntOB?vdM?_&%8+&nh!}pm9fg>v)Z?5#?)B=T~>Dv zado~liY1WxHG$g|8RTe%N1}Jf7Bs55S@))}zmAq_u2ZQv+&KMbcPg< z#mKbmp&lB&x0RcXnOH=ufgp#F%et5L>M6_j!SgO>$Fp9tzI=VZDzo`fga?96jI1&eJ$4W2ukR$w==qd*J)4-wG-+=D8vOsp!;wvKaAQ-HXW+ zioVsJ5Oa%w={Y(U;ivBSrw``uLbnB8yA26aQAPUt?!A0-?neJuo?I&GQKmPri?q;l%iivsXu2mvnR3YqPYv63O|`8L7T|pE=u!=3Nw_k4$tFnF;BKOVc_R|o zjmvegq#32B5P5m*OhJdEq|dkDb5m~iHx+l&L`6^PU}`zel#^HUnO#)pXV#{VPk)}g zeE$5}QadbE3ZK=ugo-cNU-6-DQBje(>CxwmYVJMRn4qUd-H&0rS}sCDE9a-d2P?Y< z46ibt5U?nJI6ptXz1wGzAuIjFs~aO%OJSxsO5fRah zC^kH0s4*S%-)Hph2P5s>jsNP!5YN)UrIQLs5sHrJ`kM-!li_h&E<+ zsr6+sKSGg)|2$fgOYwXq?BzH+D>zs?^RR4_r|1!4gu@&#aU=wz242~-Zpje9;qpy? zyl$u*jPP*y(V4l+Uk)mf!UZ!Zo7#R<%+8{9b7drq4v zG2q<}73jm~tsAEyxZQg1cv4Oghs0g7Jjag^v*pGc4|uJzR<&wuhnh>MTj~{|p=+V3 zbh(%AD^Ko_KGkxu;lF_(+MKQ^yGv8yeA2G4Hr_;IS=P3?GAC?$ROeT2x35~0!`ZlA zf=6f8()sKF?(N@gU1q%r4`@0%T#n{_T>}km4M`C(fhWs9!45vusj?E?0~6U``%VAXoUSZ*hHq?^R! zr0DA_aW2CXTk)*8x|2Lx3a8Eecw4DntY@ikg0p}C_TIA7r0&L1-Q@m+K=8mykDk@* zeg{HGCY6|nV3zy?`LQx1_VL$4y@Y*H^&CXZ8ggA~1tq*h4)>paqNvxbNy{xLUGq5Q z>Qwl7dle7=D-aspb}IpWs)&Uf-TJ*D^riO;HVh4uZrL=7(#fa9Zr^ur+7pO&-aB}( zCTROPHz0a?-jx}UOL;B#$J9IN_x{MJQOSpP3rvu|SO9=`26lb>k{RMn8p(@;X4=0t z*ITqCW+*l>XXMe=nXvX9ENBcvov_>fa%vtBTRU^z-^nbP@XG2^D{P0~#95IZ%Jq=pvYAdxak&&?)vm*jsi!rt zd5I10iGc78<>9d!z7lGs>|lOX5tUe6{0f*5Z=(yi*y)r@Kd3Gr_BYQh`(smNrniN4 zy#*2`AeQ;5pPjVx@}!&jOiA!LyMJk!iB3bETZ_d=p-y&>W@Qe)Y6W>10L^a}5)Tzg zkCmFF&(5xYA5b=<_P;}gOpCY(#UFGINsuQQ}|7b-m-McVD=n5zg9>YZ(1x^J9xpsl~E)emIC zb`Z>5$pKvf6LX?ku_CTc_YumqFYsc1^h_u5QxMgCA%TWFZ;hw(UJ=TYD79hhI?uR0&u-5sk3}Qc1iH006h2 zvZh7Yn;bKXg{286+58)I>qCotb-*#d)WO5g6GwL&Z38)kmfRfK>lD~XvoN%b=3wVS zZBPT){uIHnGwVZTX?tCG3nmG+= zptJ63C3ksdF%sAE5V9MB{_TaZIIfzi38Q8fM^6H$2DyA9iTzY=CZ~I*S-n`c9I3QT zjQ0S>BVns@z<};}ts%JX5g`L;GVz>Bz2Y53Hd?hpF>3a`Kw6k@Li_yC>rczF=`bK( zdk6GRCzW}myhR1J)_E%(Le7oTQ-OXjI!2|74G`N)DLhQcO#@cX2+R1KSB;0)V?^h; zI%88uq+&ACM1xn}n_3{sRoeqj2Q3C=U}c7Z^BivHAJIrp8<6G+IZU|T|MmvsRX!Mq z^>V68jf>Kl0bO(R;#H+7tUWN7Lr(K6<$C0&Zg&`$MMAFZ$X30I4wY#Wf;xpiwPb;g z4nu7OROI=Ge3;lV`97-9gl3Sr8~5Nm^Sxo%kyqtm;U~x+Mq9ZjVUCPIRQvZLr%8^z zWBW4~3FNwiV1Z*FL0xpo+G5Utz64>|au2LY+np&hx* zk=DR?M0(q5?N)trSLfw<8MO%fPGaSc@_2_uGc{J7jh9871uJH~Q3rEP{(#s#8hYZD z=oD*8jTW6sFz5ms$tJ4O340P&$X6qm7)4JwN!)v}$p&?-O@dcCri0d1nbVbX6lp(- zuP)^gq*>LN(8qg!vegtLFuJk2jeN72J-XFeW+=wwt zTBei*@2@9%)fctBndZjsv_H(SP2>-gCoZB`?u9j+cabEPO5j~}pNiKWuhA8?XkjZ~ zMt-u}XAKPYmCVA2@D!OM3L%Rocx_?0Jd%eYjrp^z%o&I0bx!G9ub()&n*I}%fA}H# zmzDRff^Y7F(&&e~5T(&K2DQJvmLojd*p&`9ZVL}>9qs#w(8V;)Wf_eFzX{M1MN;_& z4vsfXE=;RLd{j2!kxxChst&P z6n8`FEL zm0*PS#y1A27J$KQt8Q-?+6-*^`W7B_veJvfSId?=S0CmF4rv)Rn01{zag|2RDKH5; z`s3ftVxWR*MB;n=Zi0`s7Xy@J#|*%<#4$wp)bHTu==0}{&`OJaTDv|1>Z6rDgEg+l z0(Ho`Ayn*O@yN}wl2BmWbz_qd5QtK!1QUH;hR0Lx-7KxJym0bmSSDq;K>9-m7PKmE z_=k^d->uX*O~`ZDbvnA89OlL4e6I=JDIFA}hd#YBi<12w3!~3#mp+wd4(&NZFia&~ zMLP&z{+tj{!En?EJ}7-BJlXlfq{Djx*oW>600x%^$ zmFf4n!)9k`&}{wtwkK&y-jD?GTe;V@1G{+H&5Ox=l|($sW94ZGSh67Tt;RU#Z`+u? zFNleu;al(D_XkF0tJGLkfL;eQ^;YYwpRMv(yZ4sM@jb$6l!9Oo=y6_9z^pUeeJvQlht70_b&-+ji!YRWM$V*k&kGB%DfSNqWoLG z%*n6lnA~|E`nJ!mGEm<72a{Pe_4Meygg#+tsoN)FQH-;O^ej6q7VGtHwo~>*zRXQ( zx|f#C={*-hj~C#e!P^;~Mu%Wus>@pL4gljd$`UKB)1&C*H47S&@Ao-$y&p2p%VBiT z=2bixw4dXTVe+z%AUFEPn3K#&@P@W83Vmq&a6t(*|8-3?aJ?&CyS40I=js;m^C6)r zkgwa~*bfkCDxDrRP%I#wI$3Y#2hNSNK8Q|30JBiDo_*P;{IryImx%6H3I-Snwgjk> zl*o*)iX5L&J{Tn3$!)UnE5-KwD+*c?EtJ9G!cG8=?l8}&m#A5(XxX=1UPnkO6C7Lk zQpIo49`*A5%k0hQw?1=~UR`%g~b8{Td-wl7Re|>RkWTeOB z)fc(q_QyV$RMVUl52y1_bQt9!QRAiet^s^MBqlS$~{4=dVYScD+qUyo< zjQG%E;X+%;LpgN4-s;E0XSc#t%~G_IHWt=W9@KDtvEue;LPYxgNh$c`-7OY9;3xEY zAB&zCWD(n=6;$2Hdu7t|vaX$&lHY{}xSnAdfhE6P3jPG2G7)(r?&=7SFs;msw+zpW zhDoOjh>WjhzX?-5y6C#PaF-aN4iz2N7_w(&+j^#oU9XNS+a&xQ3<- z!Tf2+f-4}4NqzI&E?BE$`W~Qs6Ea>W*}Gdk6FjtloSFTRZlTsH*{#kf@pH9m+ZSKl_G7sXR@J$yGO_=4$20?p1M$9)tQe`b*8WurJgnU8iIg{VZ_k_j^L*`;T!Fq zWR%cS7lRa(w@Yjq8|#B5^@ViH-)Xx{SNBxX3i5(r^=Eg|uHU@6XJNP-s&wmq1#_+?_3|f&z#txiDx%(GN!lPHc0OjrwIy5EqbEj6gwCMTgL$wM0f_9i zhLB3}I4nv$DYBO-$j(;u*gUtoX+Hk>&6v~n)xHk{MUi5i_0Sf{7muZ&l`|0F7zBY1 zv66Iv6#5l{sU}4MwF~a9&V1HVU535gm~kL5tWej!ROqM4ijkJBNkKvQJ2mzp`qx@P zI+lA99x+HImN}7d4up(3EVa42*kmg=G*Bg7w3jT*A4(C-u#uFWaZ~xe@)(mNOljEO zV}6!z13;6H`=a(m!%{d@v<-UJRLq{wXul7$=)w`i#Y&7kTpg2jWtCdS0F@71Y6Y)Z z-!MV+Vs?j7NL*^tfuK9fH!IIi$hOP6XuCDOLY ziXMor=O3FGNTL_D0o@58{-LDOwcZ!9j-K07okBj(OiD^hIBdS@Lw|PT+rzhc(sj`8 z^Gb01?&%K(>miX(#Jj`Mv8niip$Bi{OsH_~PT>Jc3zYtwaJhlWEqxFY7M3Ny&hbGw zgxT+{gQ@$(QL^Fo(@fnm^B&&$A3B`9BRnRp_+@-bs>xe>n_Pn#3Z8w#3P3dTS^mh# zWXOOu#cO(xMbS7~J@T0J=*%R^5))cG};tkO8_izE5(8f?zwf z1Kqy*W!N)55XhP(NTE| z_egexz|LN|AHz5?(w)GdEiVbYcGIwRZ18$0((wRz46t&GQ#PaJep&Rlj!j$WtefV( z;XRU9Qkl~JV#`86uO)G9@O=k7glFWJSFK6%0A~#s2<}&A&U#{bPB}FY39;MSiZ394c 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 literal 0 HcmV?d00001 From b2c252940daeede358952bee75cc2b51fccf7683 Mon Sep 17 00:00:00 2001 From: Rano | Ranadeep Date: Tue, 12 Mar 2024 14:18:10 +0100 Subject: [PATCH 4/6] fix: bump MSRV to 1.71.1 (#1119) * bump msrv to 1.71.1 * add ci check * rm windows * refactor msrv check * use cargo-msrv * add names to steps * apply clippy suggestions * rm redundant import * clippy::use_self * clippy::cast_lossless * clippy::match_same_arms * apply clippy suggestions * reuse old var * refactor old code * badge for license, version, downloads * rm broken tokei loc badge * add changelog * clippy::derive_partial_eq_without_eq * clippy::string_lit_as_bytes * clippy::empty_line_after_doc_comments * clippy::cloned_instead_of_copied * clippy::unreadable_literal * clippy::single_match_else * expect over unwrap * use let-else * use ok_or_else * propagate None * clippy::match_same_arms * return precise error * rm downcast in favor of let-else * chore: add unclog for 1101 + move 1118 under breaking-changes * add msrv in clippy config * update changelog * use into over from * clippy::redundant_closure_for_method_calls --------- Co-authored-by: Farhad Shabani --- .../breaking-changes/1118-bump-msrv.md | 2 + .../1101-use-let-else-over-downcast.md | 2 + .../1120-rm-redundant-shadowing.md | 2 + .github/workflows/rust.yml | 45 +++++ Cargo.toml | 2 +- README.md | 14 +- clippy.toml | 1 + ibc-apps/ics20-transfer/src/module.rs | 59 +++---- ibc-apps/ics20-transfer/types/src/error.rs | 8 +- ibc-apps/ics721-nft-transfer/src/module.rs | 58 +++--- .../ics721-nft-transfer/types/src/data.rs | 2 +- .../ics721-nft-transfer/types/src/error.rs | 8 +- .../ics07-tendermint/src/client_state.rs | 3 +- .../src/client_state/execution.rs | 2 +- .../src/client_state/update_client.rs | 65 ++++--- .../ics07-tendermint/src/consensus_state.rs | 2 +- .../types/src/client_state.rs | 12 +- .../ics07-tendermint/types/src/error.rs | 6 +- ibc-core/ics02-client/types/src/error.rs | 14 +- ibc-core/ics02-client/types/src/height.rs | 17 +- ibc-core/ics03-connection/types/src/error.rs | 12 +- ibc-core/ics03-connection/types/src/events.rs | 4 +- .../types/src/msgs/conn_open_init.rs | 4 +- .../src/handler/acknowledgement.rs | 11 +- ibc-core/ics04-channel/src/handler/timeout.rs | 11 +- .../src/handler/timeout_on_close.rs | 6 +- .../types/src/acknowledgement.rs | 3 +- .../ics04-channel/types/src/commitment.rs | 2 +- .../ics04-channel/types/src/events/mod.rs | 6 +- ibc-core/ics23-commitment/types/src/merkle.rs | 2 +- ibc-core/ics23-commitment/types/src/specs.rs | 9 +- .../cosmos/src/upgrade_proposal/proposal.rs | 10 +- .../types/src/identifiers/chain_id.rs | 32 ++-- ibc-core/ics24-host/types/src/path.rs | 167 ++++-------------- ibc-core/ics25-handler/src/entrypoint.rs | 4 +- ibc-derive/src/client_state.rs | 23 ++- .../traits/client_state_common.rs | 6 +- ibc-derive/src/consensus_state.rs | 4 +- ibc-derive/src/utils.rs | 9 +- ibc-primitives/src/types/timestamp.rs | 69 ++++---- ibc-primitives/src/utils/macros.rs | 51 ------ ibc-primitives/src/utils/mod.rs | 1 - ibc-query/src/core/client/query.rs | 2 +- .../src/fixtures/clients/tendermint.rs | 6 +- .../src/fixtures/core/channel/packet.rs | 4 +- .../fixtures/core/client/msg_update_client.rs | 2 +- .../core/client/msg_upgrade_client.rs | 2 +- .../testapp/ibc/clients/mock/client_state.rs | 5 +- ibc-testkit/src/testapp/ibc/clients/mod.rs | 2 +- .../src/testapp/ibc/core/client_ctx.rs | 8 +- ibc-testkit/src/testapp/ibc/core/types.rs | 6 +- .../tests/core/ics02_client/update_client.rs | 17 +- .../tests/core/ics02_client/upgrade_client.rs | 6 +- .../core/ics03_connection/conn_open_ack.rs | 5 +- .../ics03_connection/conn_open_confirm.rs | 5 +- .../core/ics03_connection/conn_open_init.rs | 5 +- .../core/ics03_connection/conn_open_try.rs | 5 +- ibc/README.md | 12 +- 58 files changed, 348 insertions(+), 514 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/1118-bump-msrv.md create mode 100644 .changelog/unreleased/improvements/1101-use-let-else-over-downcast.md create mode 100644 .changelog/unreleased/improvements/1120-rm-redundant-shadowing.md delete mode 100644 ibc-primitives/src/utils/macros.rs diff --git a/.changelog/unreleased/breaking-changes/1118-bump-msrv.md b/.changelog/unreleased/breaking-changes/1118-bump-msrv.md new file mode 100644 index 0000000000..631b644d39 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1118-bump-msrv.md @@ -0,0 +1,2 @@ +- [ibc] Increase minimum supported Rust version to 1.71.1 + ([\#1118](https://github.com/cosmos/ibc-rs/issues/1118)) diff --git a/.changelog/unreleased/improvements/1101-use-let-else-over-downcast.md b/.changelog/unreleased/improvements/1101-use-let-else-over-downcast.md new file mode 100644 index 0000000000..876e19c91f --- /dev/null +++ b/.changelog/unreleased/improvements/1101-use-let-else-over-downcast.md @@ -0,0 +1,2 @@ +- [ibc-primitives] Use `let-else` over `downcast!()` and remove `utils/macros` + module as a result ([\#1118](https://github.com/cosmos/ibc-rs/issues/1118)) diff --git a/.changelog/unreleased/improvements/1120-rm-redundant-shadowing.md b/.changelog/unreleased/improvements/1120-rm-redundant-shadowing.md new file mode 100644 index 0000000000..80eb173072 --- /dev/null +++ b/.changelog/unreleased/improvements/1120-rm-redundant-shadowing.md @@ -0,0 +1,2 @@ +- [ibc-core] Remove unnecessary shadowing with same value + ([\#1120](https://github.com/cosmos/ibc-rs/issues/1120)) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8d25e77165..45ff97c05a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -157,3 +157,48 @@ jobs: with: command: test args: --no-default-features --no-fail-fast --no-run + + test-msrv: + timeout-minutes: 30 + env: + CARGO_MSRV_VERSION: 0.16.0-beta.20 + MSRV: 1.71.1 + strategy: + matrix: + param: + [ + { os: ubuntu-latest, system: unknown-linux-gnu }, + { os: macos-latest, system: apple-darwin }, + ] + runs-on: ${{ matrix.param.os }} + steps: + - uses: actions/checkout@v2 + + - name: Download cargo-msrv + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: foresterre/cargo-msrv + version: "tags/v${{ env.CARGO_MSRV_VERSION }}" + file: "cargo-msrv-x86_64-${{ matrix.param.system }}-v${{ env.CARGO_MSRV_VERSION }}.tgz" + + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.MSRV }} + override: true + - uses: Swatinem/rust-cache@v1 + + - name: Install cargo-msrv + run: | + tar -xzf "cargo-msrv-x86_64-${{ matrix.param.system }}-v${{ env.CARGO_MSRV_VERSION }}.tgz" + mv "cargo-msrv-x86_64-${{ matrix.param.system }}-v${{ env.CARGO_MSRV_VERSION }}/cargo-msrv" ~/.cargo/bin + cargo msrv --version + + - name: Calculate MSRV + run: cargo msrv --output-format minimal --min 1.64.0 + + - name: Build with MSRV + uses: actions-rs/cargo@v1 + with: + toolchain: ${{ env.MSRV }} + command: build + args: --all-features diff --git a/Cargo.toml b/Cargo.toml index c4590de590..e3efe6af31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ exclude = [ version = "0.50.0" license = "Apache-2.0" edition = "2021" -rust-version = "1.64" +rust-version = "1.71.1" readme = "README.md" repository = "https://github.com/cosmos/ibc-rs" authors = ["Informal Systems "] diff --git a/README.md b/README.md index 4bdeeafcf5..226f0a49e2 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ [![Build Status][build-image]][build-link] [![Code Coverage][codecov-image]][codecov-link] [![Apache 2.0 Licensed][license-image]][license-link] -![Rust Stable][rustc-image] -![Rust 1.64+][rustc-version] -![Lines of Code][loc-image] +![Version][crates-io-version] +![Downloads][crates-io-downloads] +![Rust Stable][msrv-image] @@ -94,11 +94,11 @@ specific language governing permissions and limitations under the License. [build-link]: https://github.com/cosmos/ibc-rs/actions?query=workflow%3ARust [codecov-image]: https://codecov.io/gh/cosmos/ibc-rs/branch/main/graph/badge.svg?token=wUm2aLCOu [codecov-link]: https://codecov.io/gh/cosmos/ibc-rs -[loc-image]: https://tokei.rs/b1/github/cosmos/ibc-rs -[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-image]: https://img.shields.io/crates/l/ibc [license-link]: https://github.com/cosmos/ibc-rs/blob/main/LICENSE -[rustc-image]: https://img.shields.io/badge/rustc-stable-blue.svg -[rustc-version]: https://img.shields.io/badge/rustc-1.60+-blue.svg +[crates-io-version]: https://img.shields.io/crates/v/ibc.svg +[crates-io-downloads]: https://img.shields.io/crates/d/ibc.svg +[msrv-image]: https://img.shields.io/crates/msrv/ibc [//]: # (general links) [ibc]: https://github.com/cosmos/ibc diff --git a/clippy.toml b/clippy.toml index 87ee0dae3d..76e4dff643 100644 --- a/clippy.toml +++ b/clippy.toml @@ -7,3 +7,4 @@ disallowed-types = [ disallowed-methods = [ "std::time::Duration::as_secs_f64", ] +msrv = "1.71.1" diff --git a/ibc-apps/ics20-transfer/src/module.rs b/ibc-apps/ics20-transfer/src/module.rs index f1f7d3a618..392ca5939c 100644 --- a/ibc-apps/ics20-transfer/src/module.rs +++ b/ibc-apps/ics20-transfer/src/module.rs @@ -170,13 +170,10 @@ pub fn on_recv_packet_execute( ctx_b: &mut impl TokenTransferExecutionContext, packet: &Packet, ) -> (ModuleExtras, Acknowledgement) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - let ack = - AcknowledgementStatus::error(TokenTransferError::PacketDataDeserialization.into()); - return (ModuleExtras::empty(), ack.into()); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + let ack = + AcknowledgementStatus::error(TokenTransferError::PacketDataDeserialization.into()); + return (ModuleExtras::empty(), ack.into()); }; let (mut extras, ack) = match process_recv_packet_execute(ctx_b, packet, data.clone()) { @@ -225,26 +222,21 @@ pub fn on_acknowledgement_packet_execute( acknowledgement: &Acknowledgement, _relayer: &Signer, ) -> (ModuleExtras, Result<(), TokenTransferError>) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(TokenTransferError::PacketDataDeserialization), - ); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + return ( + ModuleExtras::empty(), + Err(TokenTransferError::PacketDataDeserialization), + ); }; - let acknowledgement = - match serde_json::from_slice::(acknowledgement.as_ref()) { - Ok(ack) => ack, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(TokenTransferError::AckDeserialization), - ); - } - }; + let Ok(acknowledgement) = + serde_json::from_slice::(acknowledgement.as_ref()) + else { + return ( + ModuleExtras::empty(), + Err(TokenTransferError::AckDeserialization), + ); + }; if !acknowledgement.is_successful() { if let Err(err) = refund_packet_token_execute(ctx, packet, &data) { @@ -290,14 +282,11 @@ pub fn on_timeout_packet_execute( packet: &Packet, _relayer: &Signer, ) -> (ModuleExtras, Result<(), TokenTransferError>) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(TokenTransferError::PacketDataDeserialization), - ); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + return ( + ModuleExtras::empty(), + Err(TokenTransferError::PacketDataDeserialization), + ); }; if let Err(err) = refund_packet_token_execute(ctx, packet, &data) { @@ -347,7 +336,7 @@ mod test { // Check that it's the same output as ibc-go // Note: this also implicitly checks that the ack bytes are non-empty, // which would make the conversion to `Acknowledgement` panic - assert_eq!(ack_success, r#"{"result":"AQ=="}"#.as_bytes()); + assert_eq!(ack_success, br#"{"result":"AQ=="}"#); } #[test] @@ -361,7 +350,7 @@ mod test { // which would make the conversion to `Acknowledgement` panic assert_eq!( ack_error, - r#"{"error":"failed to deserialize packet data"}"#.as_bytes() + br#"{"error":"failed to deserialize packet data"}"# ); } diff --git a/ibc-apps/ics20-transfer/types/src/error.rs b/ibc-apps/ics20-transfer/types/src/error.rs index 2ef3f732ac..257fbc29df 100644 --- a/ibc-apps/ics20-transfer/types/src/error.rs +++ b/ibc-apps/ics20-transfer/types/src/error.rs @@ -84,12 +84,12 @@ impl std::error::Error for TokenTransferError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { Self::ContextError(e) => Some(e), - Self::InvalidIdentifier(e) => Some(e), - Self::InvalidTracePortId { + Self::InvalidIdentifier(e) + | Self::InvalidTracePortId { validation_error: e, .. - } => Some(e), - Self::InvalidTraceChannelId { + } + | Self::InvalidTraceChannelId { validation_error: e, .. } => Some(e), diff --git a/ibc-apps/ics721-nft-transfer/src/module.rs b/ibc-apps/ics721-nft-transfer/src/module.rs index 87fdfc6374..337bffcad3 100644 --- a/ibc-apps/ics721-nft-transfer/src/module.rs +++ b/ibc-apps/ics721-nft-transfer/src/module.rs @@ -172,13 +172,9 @@ pub fn on_recv_packet_execute( ctx_b: &mut impl NftTransferExecutionContext, packet: &Packet, ) -> (ModuleExtras, Acknowledgement) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - let ack = - AcknowledgementStatus::error(NftTransferError::PacketDataDeserialization.into()); - return (ModuleExtras::empty(), ack.into()); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + let ack = AcknowledgementStatus::error(NftTransferError::PacketDataDeserialization.into()); + return (ModuleExtras::empty(), ack.into()); }; let (mut extras, ack) = match process_recv_packet_execute(ctx_b, packet, data.clone()) { @@ -227,26 +223,21 @@ pub fn on_acknowledgement_packet_execute( acknowledgement: &Acknowledgement, _relayer: &Signer, ) -> (ModuleExtras, Result<(), NftTransferError>) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(NftTransferError::PacketDataDeserialization), - ); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + return ( + ModuleExtras::empty(), + Err(NftTransferError::PacketDataDeserialization), + ); }; - let acknowledgement = - match serde_json::from_slice::(acknowledgement.as_ref()) { - Ok(ack) => ack, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(NftTransferError::AckDeserialization), - ); - } - }; + let Ok(acknowledgement) = + serde_json::from_slice::(acknowledgement.as_ref()) + else { + return ( + ModuleExtras::empty(), + Err(NftTransferError::AckDeserialization), + ); + }; if !acknowledgement.is_successful() { if let Err(err) = refund_packet_nft_execute(ctx, packet, &data) { @@ -289,14 +280,11 @@ pub fn on_timeout_packet_execute( packet: &Packet, _relayer: &Signer, ) -> (ModuleExtras, Result<(), NftTransferError>) { - let data = match serde_json::from_slice::(&packet.data) { - Ok(data) => data, - Err(_) => { - return ( - ModuleExtras::empty(), - Err(NftTransferError::PacketDataDeserialization), - ); - } + let Ok(data) = serde_json::from_slice::(&packet.data) else { + return ( + ModuleExtras::empty(), + Err(NftTransferError::PacketDataDeserialization), + ); }; if let Err(err) = refund_packet_nft_execute(ctx, packet, &data) { @@ -346,7 +334,7 @@ mod test { // Check that it's the same output as ibc-go // Note: this also implicitly checks that the ack bytes are non-empty, // which would make the conversion to `Acknowledgement` panic - assert_eq!(ack_success, r#"{"result":"AQ=="}"#.as_bytes()); + assert_eq!(ack_success, br#"{"result":"AQ=="}"#); } #[test] @@ -359,7 +347,7 @@ mod test { // which would make the conversion to `Acknowledgement` panic assert_eq!( ack_error, - r#"{"error":"failed to deserialize packet data"}"#.as_bytes() + br#"{"error":"failed to deserialize packet data"}"# ); } diff --git a/ibc-apps/ics721-nft-transfer/types/src/data.rs b/ibc-apps/ics721-nft-transfer/types/src/data.rs index c94e9ee313..e6e4afbeea 100644 --- a/ibc-apps/ics721-nft-transfer/types/src/data.rs +++ b/ibc-apps/ics721-nft-transfer/types/src/data.rs @@ -161,7 +161,7 @@ impl borsh::BorshSerialize for DataValue { Some(mime) => mime.to_string(), None => String::default(), }; - borsh::BorshSerialize::serialize(&mime.to_string(), writer)?; + borsh::BorshSerialize::serialize(&mime, writer)?; Ok(()) } } diff --git a/ibc-apps/ics721-nft-transfer/types/src/error.rs b/ibc-apps/ics721-nft-transfer/types/src/error.rs index 08ab598e5c..7651e7cb7e 100644 --- a/ibc-apps/ics721-nft-transfer/types/src/error.rs +++ b/ibc-apps/ics721-nft-transfer/types/src/error.rs @@ -97,16 +97,16 @@ impl std::error::Error for NftTransferError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { Self::ContextError(e) => Some(e), - Self::InvalidIdentifier(e) => Some(e), Self::InvalidUri { validation_error: e, .. } => Some(e), - Self::InvalidTracePortId { + Self::InvalidIdentifier(e) + | Self::InvalidTracePortId { validation_error: e, .. - } => Some(e), - Self::InvalidTraceChannelId { + } + | Self::InvalidTraceChannelId { validation_error: e, .. } => Some(e), diff --git a/ibc-clients/ics07-tendermint/src/client_state.rs b/ibc-clients/ics07-tendermint/src/client_state.rs index bb94154879..13c10b963e 100644 --- a/ibc-clients/ics07-tendermint/src/client_state.rs +++ b/ibc-clients/ics07-tendermint/src/client_state.rs @@ -85,7 +85,6 @@ mod tests { use ibc_core_client::types::Height; use ibc_core_commitment_types::specs::ProofSpecs; use ibc_core_host::types::identifiers::ChainId; - use tests::common::validate_proof_height; use super::*; @@ -109,7 +108,7 @@ mod tests { id: ChainId::new("ibc-1").unwrap(), trust_level: TrustThreshold::ONE_THIRD, trusting_period: Duration::new(64000, 0), - unbonding_period: Duration::new(128000, 0), + unbonding_period: Duration::new(128_000, 0), max_clock_drift: Duration::new(3, 0), latest_height: Height::new(1, 10).expect("Never fails"), proof_specs: ProofSpecs::default(), diff --git a/ibc-clients/ics07-tendermint/src/client_state/execution.rs b/ibc-clients/ics07-tendermint/src/client_state/execution.rs index c42950ca0f..58e1d337be 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/execution.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/execution.rs @@ -259,7 +259,7 @@ where // this consensus state should not be used for packet verification as // the root is empty. The next consensus state submitted using update // will be usable for packet-verification. - let sentinel_root = "sentinel_root".as_bytes().to_vec(); + let sentinel_root = b"sentinel_root".to_vec(); let new_consensus_state = ConsensusStateType::new( sentinel_root.into(), upgraded_tm_cons_state.timestamp(), diff --git a/ibc-clients/ics07-tendermint/src/client_state/update_client.rs b/ibc-clients/ics07-tendermint/src/client_state/update_client.rs index d6dbaa2c86..56afd0b7cb 100644 --- a/ibc-clients/ics07-tendermint/src/client_state/update_client.rs +++ b/ibc-clients/ics07-tendermint/src/client_state/update_client.rs @@ -117,52 +117,49 @@ where ctx.consensus_state(&path_at_header_height).ok() }; - match maybe_existing_consensus_state { - Some(existing_consensus_state) => { - let existing_consensus_state = existing_consensus_state.try_into()?; + if let Some(existing_consensus_state) = maybe_existing_consensus_state { + let existing_consensus_state = existing_consensus_state.try_into()?; - let header_consensus_state = ConsensusStateType::from(header.clone()); + let header_consensus_state = ConsensusStateType::from(header); - // There is evidence of misbehaviour if the stored consensus state - // is different from the new one we received. - Ok(existing_consensus_state != header_consensus_state) - } - None => { - // If no header was previously installed, we ensure the monotonicity of timestamps. + // There is evidence of misbehaviour if the stored consensus state + // is different from the new one we received. + Ok(existing_consensus_state != header_consensus_state) + } else { + // If no header was previously installed, we ensure the monotonicity of timestamps. - // 1. for all headers, the new header needs to have a larger timestamp than - // the “previous header” - { - let maybe_prev_cs = ctx.prev_consensus_state(client_id, &header.height())?; + // 1. for all headers, the new header needs to have a larger timestamp than + // the “previous header” + { + let maybe_prev_cs = ctx.prev_consensus_state(client_id, &header.height())?; - if let Some(prev_cs) = maybe_prev_cs { - // New header timestamp cannot occur *before* the - // previous consensus state's height - let prev_cs = prev_cs.try_into()?; + if let Some(prev_cs) = maybe_prev_cs { + // New header timestamp cannot occur *before* the + // previous consensus state's height + let prev_cs = prev_cs.try_into()?; - if header.signed_header.header().time <= prev_cs.timestamp() { - return Ok(true); - } + if header.signed_header.header().time <= prev_cs.timestamp() { + return Ok(true); } } + } - // 2. if a header comes in and is not the “last” header, then we also ensure - // that its timestamp is less than the “next header” - if header.height() < client_state.latest_height { - let maybe_next_cs = ctx.next_consensus_state(client_id, &header.height())?; + // 2. if a header comes in and is not the “last” header, then we also ensure + // that its timestamp is less than the “next header” + if header.height() < client_state.latest_height { + let maybe_next_cs = ctx.next_consensus_state(client_id, &header.height())?; - if let Some(next_cs) = maybe_next_cs { - // New (untrusted) header timestamp cannot occur *after* next - // consensus state's height - let next_cs = next_cs.try_into()?; + if let Some(next_cs) = maybe_next_cs { + // New (untrusted) header timestamp cannot occur *after* next + // consensus state's height + let next_cs = next_cs.try_into()?; - if header.signed_header.header().time >= next_cs.timestamp() { - return Ok(true); - } + if header.signed_header.header().time >= next_cs.timestamp() { + return Ok(true); } } - - Ok(false) } + + Ok(false) } } diff --git a/ibc-clients/ics07-tendermint/src/consensus_state.rs b/ibc-clients/ics07-tendermint/src/consensus_state.rs index a09fc705a1..242c82be46 100644 --- a/ibc-clients/ics07-tendermint/src/consensus_state.rs +++ b/ibc-clients/ics07-tendermint/src/consensus_state.rs @@ -22,7 +22,7 @@ use tendermint::{Hash, Time}; /// bypass Rust's orphan rules and implement traits from /// `ibc::core::client::context` on the `ConsensusState` type. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, PartialEq, derive_more::From)] +#[derive(Clone, Debug, PartialEq, Eq, derive_more::From)] pub struct ConsensusState(ConsensusStateType); impl ConsensusState { diff --git a/ibc-clients/ics07-tendermint/types/src/client_state.rs b/ibc-clients/ics07-tendermint/types/src/client_state.rs index 98ed611322..a533858a23 100644 --- a/ibc-clients/ics07-tendermint/types/src/client_state.rs +++ b/ibc-clients/ics07-tendermint/types/src/client_state.rs @@ -320,12 +320,10 @@ impl From for RawTmClientState { // decode the `ClientState` value. In `RawClientState`, a // `frozen_height` of `0` means "not frozen". See: // https://github.com/cosmos/ibc-go/blob/8422d0c4c35ef970539466c5bdec1cd27369bab3/modules/light-clients/07-tendermint/types/client_state.go#L74 - frozen_height: Some(value.frozen_height.map(|height| height.into()).unwrap_or( - RawHeight { - revision_number: 0, - revision_height: 0, - }, - )), + frozen_height: Some(value.frozen_height.map(Into::into).unwrap_or(RawHeight { + revision_number: 0, + revision_height: 0, + })), latest_height: Some(value.latest_height.into()), proof_specs: value.proof_specs.into(), upgrade_path: value.upgrade_path, @@ -437,7 +435,7 @@ mod tests { id: ChainId::new("ibc-0").unwrap(), trust_level: TrustThreshold::ONE_THIRD, trusting_period: Duration::new(64000, 0), - unbonding_period: Duration::new(128000, 0), + unbonding_period: Duration::new(128_000, 0), max_clock_drift: Duration::new(3, 0), latest_height: Height::new(0, 10).expect("Never fails"), proof_specs: ProofSpecs::default(), diff --git a/ibc-clients/ics07-tendermint/types/src/error.rs b/ibc-clients/ics07-tendermint/types/src/error.rs index 3e2a511126..ad53989fb1 100644 --- a/ibc-clients/ics07-tendermint/types/src/error.rs +++ b/ibc-clients/ics07-tendermint/types/src/error.rs @@ -100,9 +100,9 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { Self::InvalidIdentifier(e) => Some(e), - Self::InvalidHeader { error: e, .. } => Some(e), - Self::InvalidTendermintTrustThreshold(e) => Some(e), - Self::InvalidRawHeader(e) => Some(e), + Self::InvalidHeader { error: e, .. } + | Self::InvalidTendermintTrustThreshold(e) + | Self::InvalidRawHeader(e) => Some(e), _ => None, } } diff --git a/ibc-core/ics02-client/types/src/error.rs b/ibc-core/ics02-client/types/src/error.rs index c34eb04367..181d2ca5fb 100644 --- a/ibc-core/ics02-client/types/src/error.rs +++ b/ibc-core/ics02-client/types/src/error.rs @@ -107,12 +107,11 @@ impl From<&'static str> for ClientError { impl std::error::Error for ClientError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { - Self::InvalidMsgUpdateClientId(e) => Some(e), - Self::InvalidClientIdentifier(e) => Some(e), - Self::InvalidRawMisbehaviour(e) => Some(e), - Self::InvalidCommitmentProof(e) => Some(e), + Self::InvalidMsgUpdateClientId(e) + | Self::InvalidClientIdentifier(e) + | Self::InvalidRawMisbehaviour(e) => Some(e), + Self::InvalidCommitmentProof(e) | Self::Ics23Verification(e) => Some(e), Self::InvalidPacketTimestamp(e) => Some(e), - Self::Ics23Verification(e) => Some(e), _ => None, } } @@ -148,8 +147,9 @@ impl From for ClientError { impl std::error::Error for UpgradeClientError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { - Self::InvalidUpgradeClientProof(e) => Some(e), - Self::InvalidUpgradeConsensusStateProof(e) => Some(e), + Self::InvalidUpgradeClientProof(e) | Self::InvalidUpgradeConsensusStateProof(e) => { + Some(e) + } _ => None, } } diff --git a/ibc-core/ics02-client/types/src/height.rs b/ibc-core/ics02-client/types/src/height.rs index 889c47c812..db809591a4 100644 --- a/ibc-core/ics02-client/types/src/height.rs +++ b/ibc-core/ics02-client/types/src/height.rs @@ -149,7 +149,7 @@ impl core::fmt::Display for Height { } /// Encodes all errors related to chain heights -#[derive(Debug, Display, PartialEq)] +#[derive(Debug, Display, PartialEq, Eq)] pub enum HeightError { /// cannot convert into a `Height` type from string `{height}` HeightConversion { @@ -167,8 +167,7 @@ impl std::error::Error for HeightError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { HeightError::HeightConversion { error: e, .. } => Some(e), - HeightError::ZeroHeight => None, - HeightError::InvalidFormat { .. } => None, + HeightError::ZeroHeight | HeightError::InvalidFormat { .. } => None, } } } @@ -177,14 +176,12 @@ impl TryFrom<&str> for Height { type Error = HeightError; fn try_from(value: &str) -> Result { - let (rev_number_str, rev_height_str) = match value.split_once('-') { - Some((rev_number_str, rev_height_str)) => (rev_number_str, rev_height_str), - None => { - return Err(HeightError::InvalidFormat { + let (rev_number_str, rev_height_str) = + value + .split_once('-') + .ok_or_else(|| HeightError::InvalidFormat { raw_height: value.to_owned(), - }) - } - }; + })?; let revision_number = rev_number_str diff --git a/ibc-core/ics03-connection/types/src/error.rs b/ibc-core/ics03-connection/types/src/error.rs index b9cb0460be..bdd7e1299f 100644 --- a/ibc-core/ics03-connection/types/src/error.rs +++ b/ibc-core/ics03-connection/types/src/error.rs @@ -91,15 +91,15 @@ pub enum ConnectionError { impl std::error::Error for ConnectionError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { - Self::Client(e) => Some(e), - Self::InvalidIdentifier(e) => Some(e), - Self::VerifyConnectionState(e) => Some(e), - Self::ConsensusStateVerificationFailure { + Self::Client(e) + | Self::VerifyConnectionState(e) + | Self::ConsensusStateVerificationFailure { client_error: e, .. - } => Some(e), - Self::ClientStateVerificationFailure { + } + | Self::ClientStateVerificationFailure { client_error: e, .. } => Some(e), + Self::InvalidIdentifier(e) => Some(e), Self::TimestampOverflow(e) => Some(e), _ => None, } diff --git a/ibc-core/ics03-connection/types/src/events.rs b/ibc-core/ics03-connection/types/src/events.rs index 0d10512a4f..1866a39828 100644 --- a/ibc-core/ics03-connection/types/src/events.rs +++ b/ibc-core/ics03-connection/types/src/events.rs @@ -366,7 +366,7 @@ mod tests { ) .into(), expected_keys: expected_keys.clone(), - expected_values: expected_values.iter().rev().cloned().collect(), + expected_values: expected_values.iter().rev().copied().collect(), }, Test { kind: CONNECTION_OPEN_ACK_EVENT, @@ -385,7 +385,7 @@ mod tests { event: OpenConfirm::new(conn_id_on_b, client_id_on_b, conn_id_on_a, client_id_on_a) .into(), expected_keys: expected_keys.clone(), - expected_values: expected_values.iter().rev().cloned().collect(), + expected_values: expected_values.iter().rev().copied().collect(), }, ]; diff --git a/ibc-core/ics03-connection/types/src/msgs/conn_open_init.rs b/ibc-core/ics03-connection/types/src/msgs/conn_open_init.rs index f077945be9..2a93b1bd3a 100644 --- a/ibc-core/ics03-connection/types/src/msgs/conn_open_init.rs +++ b/ibc-core/ics03-connection/types/src/msgs/conn_open_init.rs @@ -100,7 +100,7 @@ impl TryFrom for MsgConnectionOpenInit { .parse() .map_err(ConnectionError::InvalidIdentifier)?, counterparty, - version: msg.version.map(|version| version.try_into()).transpose()?, + version: msg.version.map(TryInto::try_into).transpose()?, delay_period: Duration::from_nanos(msg.delay_period), signer: msg.signer.into(), }) @@ -112,7 +112,7 @@ impl From for RawMsgConnectionOpenInit { RawMsgConnectionOpenInit { client_id: ics_msg.client_id_on_a.as_str().to_string(), counterparty: Some(ics_msg.counterparty.into()), - version: ics_msg.version.map(|version| version.into()), + version: ics_msg.version.map(Into::into), delay_period: ics_msg.delay_period.as_nanos() as u64, signer: ics_msg.signer.to_string(), } diff --git a/ibc-core/ics04-channel/src/handler/acknowledgement.rs b/ibc-core/ics04-channel/src/handler/acknowledgement.rs index ec6aa41bcb..c58edc74a1 100644 --- a/ibc-core/ics04-channel/src/handler/acknowledgement.rs +++ b/ibc-core/ics04-channel/src/handler/acknowledgement.rs @@ -74,11 +74,6 @@ where // apply state changes { - let commitment_path_on_a = CommitmentPath { - port_id: msg.packet.port_id_on_a.clone(), - channel_id: msg.packet.chan_id_on_a.clone(), - sequence: msg.packet.seq_on_a, - }; ctx_a.delete_packet_commitment(&commitment_path_on_a)?; if let Order::Ordered = chan_end_on_a.ordering { @@ -136,14 +131,12 @@ where CommitmentPath::new(&packet.port_id_on_a, &packet.chan_id_on_a, packet.seq_on_a); // Verify packet commitment - let commitment_on_a = match ctx_a.get_packet_commitment(&commitment_path_on_a) { - Ok(commitment_on_a) => commitment_on_a, - + let Ok(commitment_on_a) = ctx_a.get_packet_commitment(&commitment_path_on_a) else { // This error indicates that the timeout has already been relayed // or there is a misconfigured relayer attempting to prove a timeout // for a packet never sent. Core IBC will treat this error as a no-op in order to // prevent an entire relay transaction from failing and consuming unnecessary fees. - Err(_) => return Ok(()), + return Ok(()); }; if commitment_on_a diff --git a/ibc-core/ics04-channel/src/handler/timeout.rs b/ibc-core/ics04-channel/src/handler/timeout.rs index aca19b9f8b..ef478ed4ed 100644 --- a/ibc-core/ics04-channel/src/handler/timeout.rs +++ b/ibc-core/ics04-channel/src/handler/timeout.rs @@ -82,11 +82,6 @@ where // apply state changes let chan_end_on_a = { - let commitment_path_on_a = CommitmentPath { - port_id: packet.port_id_on_a.clone(), - channel_id: packet.chan_id_on_a.clone(), - sequence: packet.seq_on_a, - }; ctx_a.delete_packet_commitment(&commitment_path_on_a)?; if let Order::Ordered = chan_end_on_a.ordering { @@ -160,14 +155,12 @@ where &msg.packet.chan_id_on_a, msg.packet.seq_on_a, ); - let commitment_on_a = match ctx_a.get_packet_commitment(&commitment_path_on_a) { - Ok(commitment_on_a) => commitment_on_a, - + let Ok(commitment_on_a) = ctx_a.get_packet_commitment(&commitment_path_on_a) else { // This error indicates that the timeout has already been relayed // or there is a misconfigured relayer attempting to prove a timeout // for a packet never sent. Core IBC will treat this error as a no-op in order to // prevent an entire relay transaction from failing and consuming unnecessary fees. - Err(_) => return Ok(()), + return Ok(()); }; let expected_commitment_on_a = compute_packet_commitment( diff --git a/ibc-core/ics04-channel/src/handler/timeout_on_close.rs b/ibc-core/ics04-channel/src/handler/timeout_on_close.rs index 49dcc9a05d..0aa5a4027f 100644 --- a/ibc-core/ics04-channel/src/handler/timeout_on_close.rs +++ b/ibc-core/ics04-channel/src/handler/timeout_on_close.rs @@ -36,14 +36,12 @@ where ); //verify the packet was sent, check the store - let commitment_on_a = match ctx_a.get_packet_commitment(&commitment_path_on_a) { - Ok(commitment_on_a) => commitment_on_a, - + let Ok(commitment_on_a) = ctx_a.get_packet_commitment(&commitment_path_on_a) else { // This error indicates that the timeout has already been relayed // or there is a misconfigured relayer attempting to prove a timeout // for a packet never sent. Core IBC will treat this error as a no-op in order to // prevent an entire relay transaction from failing and consuming unnecessary fees. - Err(_) => return Ok(()), + return Ok(()); }; let expected_commitment_on_a = compute_packet_commitment( diff --git a/ibc-core/ics04-channel/types/src/acknowledgement.rs b/ibc-core/ics04-channel/types/src/acknowledgement.rs index b30f14454b..7364857d18 100644 --- a/ibc-core/ics04-channel/types/src/acknowledgement.rs +++ b/ibc-core/ics04-channel/types/src/acknowledgement.rs @@ -114,8 +114,7 @@ impl AcknowledgementStatus { impl Display for AcknowledgementStatus { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { - AcknowledgementStatus::Success(v) => write!(f, "{v}"), - AcknowledgementStatus::Error(v) => write!(f, "{v}"), + AcknowledgementStatus::Success(v) | AcknowledgementStatus::Error(v) => write!(f, "{v}"), } } } diff --git a/ibc-core/ics04-channel/types/src/commitment.rs b/ibc-core/ics04-channel/types/src/commitment.rs index ba5da70c58..dbea7e9680 100644 --- a/ibc-core/ics04-channel/types/src/commitment.rs +++ b/ibc-core/ics04-channel/types/src/commitment.rs @@ -124,7 +124,7 @@ mod test { 0x4f, 0x84, 0xcc, 0x15, ]; let actual = compute_packet_commitment( - "packet data".as_bytes(), + b"packet data", &TimeoutHeight::At(ibc_core_client_types::Height::new(42, 24).unwrap()), &Timestamp::from_nanoseconds(0x42).unwrap(), ); diff --git a/ibc-core/ics04-channel/types/src/events/mod.rs b/ibc-core/ics04-channel/types/src/events/mod.rs index 606d248db5..fbb78d6e76 100644 --- a/ibc-core/ics04-channel/types/src/events/mod.rs +++ b/ibc-core/ics04-channel/types/src/events/mod.rs @@ -532,7 +532,7 @@ impl ChannelClosed { port_id_attr_on_a: port_id_on_a.into(), chan_id_attr_on_a: chan_id_on_a.into(), port_id_attr_on_b: port_id_on_b.into(), - maybe_chan_id_attr_on_b: maybe_chan_id_on_b.map(|c| c.into()), + maybe_chan_id_attr_on_b: maybe_chan_id_on_b.map(Into::into), conn_id_attr_on_a: conn_id_on_a.into(), channel_ordering_attr: channel_ordering.into(), } @@ -547,7 +547,7 @@ impl ChannelClosed { &self.port_id_attr_on_b.counterparty_port_id } pub fn chan_id_on_a(&self) -> Option<&ChannelId> { - self.maybe_chan_id_attr_on_b.as_ref().map(|c| c.as_ref()) + self.maybe_chan_id_attr_on_b.as_ref().map(AsRef::as_ref) } pub fn conn_id_on_b(&self) -> &ConnectionId { &self.conn_id_attr_on_a.connection_id @@ -571,7 +571,7 @@ impl From for abci::Event { ev.port_id_attr_on_b.into(), ev.maybe_chan_id_attr_on_b.map_or_else( || (COUNTERPARTY_CHANNEL_ID_ATTRIBUTE_KEY, "").into(), - |c| c.into(), + Into::into, ), ev.conn_id_attr_on_a.into(), ev.channel_ordering_attr.into(), diff --git a/ibc-core/ics23-commitment/types/src/merkle.rs b/ibc-core/ics23-commitment/types/src/merkle.rs index 3e0624968d..308cb424e4 100644 --- a/ibc-core/ics23-commitment/types/src/merkle.rs +++ b/ibc-core/ics23-commitment/types/src/merkle.rs @@ -109,7 +109,7 @@ impl MerkleProof { ) { return Err(CommitmentError::VerificationFailure); } - value = subroot.clone(); + value.clone_from(&subroot); } _ => return Err(CommitmentError::InvalidMerkleProof), } diff --git a/ibc-core/ics23-commitment/types/src/specs.rs b/ibc-core/ics23-commitment/types/src/specs.rs index 2e18094a08..b7789cebc1 100644 --- a/ibc-core/ics23-commitment/types/src/specs.rs +++ b/ibc-core/ics23-commitment/types/src/specs.rs @@ -53,18 +53,13 @@ impl Default for ProofSpecs { impl From> for ProofSpecs { fn from(ics23_specs: Vec) -> Self { - Self( - ics23_specs - .into_iter() - .map(|ics23_spec| ics23_spec.into()) - .collect(), - ) + Self(ics23_specs.into_iter().map(Into::into).collect()) } } impl From for Vec { fn from(specs: ProofSpecs) -> Self { - specs.0.into_iter().map(|spec| spec.into()).collect() + specs.0.into_iter().map(Into::into).collect() } } diff --git a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/proposal.rs b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/proposal.rs index 5f24c9523d..d9e855a485 100644 --- a/ibc-core/ics24-host/cosmos/src/upgrade_proposal/proposal.rs +++ b/ibc-core/ics24-host/cosmos/src/upgrade_proposal/proposal.rs @@ -49,13 +49,11 @@ impl TryFrom for UpgradeProposal { }); }; - let upgraded_client_state = if let Some(upgraded_client_state) = raw.upgraded_client_state { - upgraded_client_state - } else { - return Err(UpgradeClientError::InvalidUpgradeProposal { + let upgraded_client_state = raw.upgraded_client_state.ok_or_else(|| { + UpgradeClientError::InvalidUpgradeProposal { reason: "upgraded client state cannot be empty".to_string(), - }); - }; + } + })?; Ok(Self { title: raw.title, diff --git a/ibc-core/ics24-host/types/src/identifiers/chain_id.rs b/ibc-core/ics24-host/types/src/identifiers/chain_id.rs index c40904b1b4..38a61f5af0 100644 --- a/ibc-core/ics24-host/types/src/identifiers/chain_id.rs +++ b/ibc-core/ics24-host/types/src/identifiers/chain_id.rs @@ -265,24 +265,20 @@ impl FromStr for ChainId { // Validates the chain name for allowed characters according to ICS-24. validate_identifier_chars(id)?; - match parse_chain_id_string(id) { - Ok((chain_name, revision_number)) => { - // Validate if the chain name with revision number has a valid length. - validate_prefix_length(chain_name, 1, 64)?; - Ok(Self { - id: id.into(), - revision_number, - }) - } - - _ => { - // Validate if the identifier has a valid length. - validate_identifier_length(id, 1, 64)?; - Ok(Self { - id: id.into(), - revision_number: 0, - }) - } + if let Ok((chain_name, revision_number)) = parse_chain_id_string(id) { + // Validate if the chain name with revision number has a valid length. + validate_prefix_length(chain_name, 1, 64)?; + Ok(Self { + id: id.into(), + revision_number, + }) + } else { + // Validate if the identifier has a valid length. + validate_identifier_length(id, 1, 64)?; + Ok(Self { + id: id.into(), + revision_number: 0, + }) } } } diff --git a/ibc-core/ics24-host/types/src/path.rs b/ibc-core/ics24-host/types/src/path.rs index c016f772b0..3247f5a634 100644 --- a/ibc-core/ics24-host/types/src/path.rs +++ b/ibc-core/ics24-host/types/src/path.rs @@ -744,19 +744,13 @@ fn parse_next_sequence(components: &[&str]) -> Option { } fn parse_client_paths(components: &[&str]) -> Option { - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != CLIENT_PREFIX { return None; } - let client_id = match ClientId::from_str(components[1]) { - Ok(s) => s, - Err(_) => return None, - }; + let client_id = ClientId::from_str(components[1]).ok()?; if components.len() == 3 { match components[2] { @@ -779,15 +773,9 @@ fn parse_client_paths(components: &[&str]) -> Option { let revision_number = epoch_height[0]; let revision_height = epoch_height[1]; - let revision_number = match revision_number.parse::() { - Ok(ep) => ep, - Err(_) => return None, - }; + let revision_number = revision_number.parse::().ok()?; - let revision_height = match revision_height.parse::() { - Ok(h) => h, - Err(_) => return None, - }; + let revision_height = revision_height.parse::().ok()?; match components.len() { 4 => Some( @@ -829,24 +817,15 @@ fn parse_connections(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != CONNECTION_PREFIX { return None; } - let connection_id = match components.last() { - Some(c) => *c, - None => return None, - }; + let connection_id = *components.last()?; - let connection_id = match ConnectionId::from_str(connection_id) { - Ok(c) => c, - Err(_) => return None, - }; + let connection_id = ConnectionId::from_str(connection_id).ok()?; Some(ConnectionPath(connection_id).into()) } @@ -856,24 +835,15 @@ fn parse_ports(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != PORT_PREFIX { return None; } - let port_id = match components.last() { - Some(p) => *p, - None => return None, - }; + let port_id = *components.last()?; - let port_id = match PortId::from_str(port_id) { - Ok(p) => p, - Err(_) => return None, - }; + let port_id = PortId::from_str(port_id).ok()?; Some(PortPath(port_id).into()) } @@ -883,24 +853,15 @@ fn parse_channels(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != CHANNEL_PREFIX { return None; } - let channel_id = match components.last() { - Some(c) => *c, - None => return None, - }; + let channel_id = *components.last()?; - let channel_id = match ChannelId::from_str(channel_id) { - Ok(c) => c, - Err(_) => return None, - }; + let channel_id = ChannelId::from_str(channel_id).ok()?; Some(SubPath::Channels(channel_id)) } @@ -910,19 +871,13 @@ fn parse_sequences(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != SEQUENCE_PREFIX { return None; } - let sequence_number = match components.last() { - Some(s) => *s, - None => return None, - }; + let sequence_number = *components.last()?; match Sequence::from_str(sequence_number) { Ok(seq) => Some(SubPath::Sequences(seq)), @@ -935,10 +890,7 @@ fn parse_channel_ends(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != CHANNEL_END_PREFIX { return None; @@ -947,15 +899,11 @@ fn parse_channel_ends(components: &[&str]) -> Option { let port = parse_ports(&components[1..=2]); let channel = parse_channels(&components[3..=4]); - let port_id = if let Some(Path::Ports(PortPath(port_id))) = port { - port_id - } else { + let Some(Path::Ports(PortPath(port_id))) = port else { return None; }; - let channel_id = if let Some(SubPath::Channels(channel_id)) = channel { - channel_id - } else { + let Some(SubPath::Channels(channel_id)) = channel else { return None; }; @@ -967,23 +915,16 @@ fn parse_seqs(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; let port = parse_ports(&components[1..=2]); let channel = parse_channels(&components[3..=4]); - let port_id = if let Some(Path::Ports(PortPath(port_id))) = port { - port_id - } else { + let Some(Path::Ports(PortPath(port_id))) = port else { return None; }; - let channel_id = if let Some(SubPath::Channels(channel_id)) = channel { - channel_id - } else { + let Some(SubPath::Channels(channel_id)) = channel else { return None; }; @@ -1000,10 +941,7 @@ fn parse_commitments(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != PACKET_COMMITMENT_PREFIX { return None; @@ -1013,21 +951,15 @@ fn parse_commitments(components: &[&str]) -> Option { let channel = parse_channels(&components[3..=4]); let sequence = parse_sequences(&components[5..]); - let port_id = if let Some(Path::Ports(PortPath(port_id))) = port { - port_id - } else { + let Some(Path::Ports(PortPath(port_id))) = port else { return None; }; - let channel_id = if let Some(SubPath::Channels(channel_id)) = channel { - channel_id - } else { + let Some(SubPath::Channels(channel_id)) = channel else { return None; }; - let sequence = if let Some(SubPath::Sequences(seq)) = sequence { - seq - } else { + let Some(SubPath::Sequences(sequence)) = sequence else { return None; }; @@ -1046,10 +978,7 @@ fn parse_acks(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != PACKET_ACK_PREFIX { return None; @@ -1059,21 +988,15 @@ fn parse_acks(components: &[&str]) -> Option { let channel = parse_channels(&components[3..=4]); let sequence = parse_sequences(&components[5..]); - let port_id = if let Some(Path::Ports(PortPath(port_id))) = port { - port_id - } else { + let Some(Path::Ports(PortPath(port_id))) = port else { return None; }; - let channel_id = if let Some(SubPath::Channels(channel_id)) = channel { - channel_id - } else { + let Some(SubPath::Channels(channel_id)) = channel else { return None; }; - let sequence = if let Some(SubPath::Sequences(seq)) = sequence { - seq - } else { + let Some(SubPath::Sequences(sequence)) = sequence else { return None; }; @@ -1092,10 +1015,7 @@ fn parse_receipts(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != PACKET_RECEIPT_PREFIX { return None; @@ -1105,21 +1025,15 @@ fn parse_receipts(components: &[&str]) -> Option { let channel = parse_channels(&components[3..=4]); let sequence = parse_sequences(&components[5..]); - let port_id = if let Some(Path::Ports(PortPath(port_id))) = port { - port_id - } else { + let Some(Path::Ports(PortPath(port_id))) = port else { return None; }; - let channel_id = if let Some(SubPath::Channels(channel_id)) = channel { - channel_id - } else { + let Some(SubPath::Channels(channel_id)) = channel else { return None; }; - let sequence = if let Some(SubPath::Sequences(seq)) = sequence { - seq - } else { + let Some(SubPath::Sequences(sequence)) = sequence else { return None; }; @@ -1138,24 +1052,15 @@ fn parse_upgrades(components: &[&str]) -> Option { return None; } - let first = match components.first() { - Some(f) => *f, - None => return None, - }; + let first = *components.first()?; if first != UPGRADED_IBC_STATE { return None; } - let last = match components.last() { - Some(l) => *l, - None => return None, - }; + let last = *components.last()?; - let height = match components[1].parse::() { - Ok(h) => h, - Err(_) => return None, - }; + let height = components[1].parse::().ok()?; match last { UPGRADED_CLIENT_STATE => Some(UpgradeClientPath::UpgradedClientState(height).into()), diff --git a/ibc-core/ics25-handler/src/entrypoint.rs b/ibc-core/ics25-handler/src/entrypoint.rs index f41ad4a532..b3391280a4 100644 --- a/ibc-core/ics25-handler/src/entrypoint.rs +++ b/ibc-core/ics25-handler/src/entrypoint.rs @@ -61,7 +61,7 @@ where ConnectionMsg::OpenInit(msg) => conn_open_init::validate(ctx, msg), ConnectionMsg::OpenTry(msg) => conn_open_try::validate(ctx, msg), ConnectionMsg::OpenAck(msg) => conn_open_ack::validate(ctx, msg), - ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::validate(ctx, msg), + ConnectionMsg::OpenConfirm(msg) => conn_open_confirm::validate(ctx, &msg), }, MsgEnvelope::Channel(msg) => { let port_id = channel_msg_to_port_id(&msg); @@ -132,7 +132,7 @@ where ConnectionMsg::OpenInit(msg) => conn_open_init::execute(ctx, msg), ConnectionMsg::OpenTry(msg) => conn_open_try::execute(ctx, msg), ConnectionMsg::OpenAck(msg) => conn_open_ack::execute(ctx, msg), - ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::execute(ctx, msg), + ConnectionMsg::OpenConfirm(msg) => conn_open_confirm::execute(ctx, &msg), }, MsgEnvelope::Channel(msg) => { let port_id = channel_msg_to_port_id(&msg); diff --git a/ibc-derive/src/client_state.rs b/ibc-derive/src/client_state.rs index b33862fb24..4e30551990 100644 --- a/ibc-derive/src/client_state.rs +++ b/ibc-derive/src/client_state.rs @@ -24,7 +24,7 @@ pub(crate) struct ClientCtx { impl ClientCtx { fn new(ident: Ident, generics: Vec, predicates: Vec) -> Self { - ClientCtx { + Self { ident, generics, predicates, @@ -73,8 +73,8 @@ impl Opts { return Err(Error::new_spanned(ast, MISSING_ATTR)); } - for attr in ast.attrs.iter() { - if let syn::Meta::List(ref meta_list) = attr.meta { + for attr in &ast.attrs { + if let syn::Meta::List(meta_list) = &attr.meta { let path: syn::Path = syn::parse2(meta_list.tokens.clone())?; let path_segment = match path.segments.last() { @@ -104,7 +104,7 @@ impl Opts { let client_execution_context = client_execution_context .ok_or_else(|| Error::new_spanned(ast, MISSING_EXECUTION_ATTR))?; - Ok(Opts { + Ok(Self { client_validation_context, client_execution_context, }) @@ -117,16 +117,13 @@ fn split_for_impl( let mut generics = vec![]; let mut predicates = vec![]; - if let syn::PathArguments::AngleBracketed(ref gen) = args { - for arg in gen.args.clone() { + if let syn::PathArguments::AngleBracketed(gen) = args { + for arg in gen.args { match arg.clone() { - syn::GenericArgument::Type(_) => { + GenericArgument::Type(_) | GenericArgument::Lifetime(_) => { generics.push(arg); } - syn::GenericArgument::Lifetime(_) => { - generics.push(arg); - } - syn::GenericArgument::Constraint(c) => { + GenericArgument::Constraint(c) => { let ident = c.ident.into_token_stream(); let gen = syn::parse2(ident.into_token_stream())?; @@ -153,8 +150,8 @@ pub fn client_state_derive_impl(ast: DeriveInput, imports: &Imports) -> TokenStr }; let enum_name = &ast.ident; - let enum_variants = match ast.data { - syn::Data::Enum(ref enum_data) => &enum_data.variants, + let enum_variants = match &ast.data { + syn::Data::Enum(enum_data) => &enum_data.variants, _ => panic!("ClientState only supports enums"), }; diff --git a/ibc-derive/src/client_state/traits/client_state_common.rs b/ibc-derive/src/client_state/traits/client_state_common.rs index 42a144b2e2..56bfe9349d 100644 --- a/ibc-derive/src/client_state/traits/client_state_common.rs +++ b/ibc-derive/src/client_state/traits/client_state_common.rs @@ -136,9 +136,9 @@ pub(crate) fn impl_ClientStateCommon( /// /// Generates the per-enum variant function call delegation token streams. /// -/// enum_name: The user's enum identifier (e.g. `HostClientState`) -/// enum_variants: An iterator of all enum variants (e.g. `[HostClientState::Tendermint, HostClientState::Mock]`) -/// fn_call: The tokens for the function call. Fully-qualified syntax is assumed, where the name for `self` +/// `enum_name`: The user's enum identifier (e.g. `HostClientState`) +/// `enum_variants`: An iterator of all enum variants (e.g. `[HostClientState::Tendermint, HostClientState::Mock]`) +/// `fn_call`: The tokens for the function call. Fully-qualified syntax is assumed, where the name for `self` /// is `cs` (e.g. `client_type(cs)`). /// /// For example, diff --git a/ibc-derive/src/consensus_state.rs b/ibc-derive/src/consensus_state.rs index 811f69fc6c..4ac97a5785 100644 --- a/ibc-derive/src/consensus_state.rs +++ b/ibc-derive/src/consensus_state.rs @@ -7,8 +7,8 @@ use crate::utils::{get_enum_variant_type_path, Imports}; pub fn consensus_state_derive_impl(ast: DeriveInput, imports: &Imports) -> TokenStream { let enum_name = &ast.ident; - let enum_variants = match ast.data { - syn::Data::Enum(ref enum_data) => &enum_data.variants, + let enum_variants = match &ast.data { + syn::Data::Enum(enum_data) => &enum_data.variants, _ => panic!("ConsensusState only supports enums"), }; diff --git a/ibc-derive/src/utils.rs b/ibc-derive/src/utils.rs index e33f13974e..ab3986951e 100644 --- a/ibc-derive/src/utils.rs +++ b/ibc-derive/src/utils.rs @@ -29,9 +29,7 @@ impl Imports { pub fn prefix(&self) -> &TokenStream { &self.prefix } -} -impl Imports { pub fn commitment_root(&self) -> TokenStream { let Prefix = self.prefix(); quote! {#Prefix::commitment_types::commitment::CommitmentRoot} @@ -123,10 +121,9 @@ impl Imports { /// pub fn get_enum_variant_type_path(enum_variant: &Variant) -> &Path { let variant_name = &enum_variant.ident; - let variant_unnamed_fields = match &enum_variant.fields { - syn::Fields::Unnamed(fields) => fields, - _ => panic!("\"{variant_name}\" variant must be unnamed, such as `{variant_name}({variant_name}ClientState)`") - }; + let syn::Fields::Unnamed(variant_unnamed_fields) = &enum_variant.fields else { + panic!("\"{variant_name}\" variant must be unnamed, such as `{variant_name}({variant_name}ClientState)`") + }; if variant_unnamed_fields.unnamed.iter().len() != 1 { panic!("\"{variant_name}\" must contain exactly one field, such as `{variant_name}({variant_name}ClientState)`"); diff --git a/ibc-primitives/src/types/timestamp.rs b/ibc-primitives/src/types/timestamp.rs index c7d8e5aaeb..9454b2b913 100644 --- a/ibc-primitives/src/types/timestamp.rs +++ b/ibc-primitives/src/types/timestamp.rs @@ -48,8 +48,7 @@ impl borsh::BorshDeserialize for Timestamp { reader: &mut R, ) -> borsh::maybestd::io::Result { let timestamp = u64::deserialize_reader(reader)?; - Ok(Timestamp::from_nanoseconds(timestamp) - .map_err(|_| borsh::maybestd::io::ErrorKind::Other)?) + Ok(Self::from_nanoseconds(timestamp).map_err(|_| borsh::maybestd::io::ErrorKind::Other)?) } } @@ -66,7 +65,7 @@ impl parity_scale_codec::Decode for Timestamp { input: &mut I, ) -> Result { let timestamp = u64::decode(input)?; - Timestamp::from_nanoseconds(timestamp) + Self::from_nanoseconds(timestamp) .map_err(|_| parity_scale_codec::Error::from("from nanoseconds error")) } } @@ -108,14 +107,14 @@ impl Timestamp { /// is not set. In this case, our domain type takes the /// value of None. /// - pub fn from_nanoseconds(nanoseconds: u64) -> Result { + pub fn from_nanoseconds(nanoseconds: u64) -> Result { if nanoseconds == 0 { - Ok(Timestamp { time: None }) + Ok(Self { time: None }) } else { // As the `u64` representation can only represent times up to // about year 2554, there is no risk of overflowing `Time` // or `OffsetDateTime`. - let ts = OffsetDateTime::from_unix_timestamp_nanos(nanoseconds as i128) + let ts = OffsetDateTime::from_unix_timestamp_nanos(nanoseconds.into()) .map_err(|e: time::error::ComponentRange| { ParseTimestampError::DataOutOfRange(e.to_string()) })? @@ -123,26 +122,26 @@ impl Timestamp { .map_err(|e: tendermint::error::Error| { ParseTimestampError::DataOutOfRange(e.to_string()) })?; - Ok(Timestamp { time: Some(ts) }) + Ok(Self { time: Some(ts) }) } } /// Returns a `Timestamp` representation of the current time. #[cfg(feature = "std")] - pub fn now() -> Timestamp { + pub fn now() -> Self { Time::now().into() } /// Returns a `Timestamp` representation of a timestamp not being set. pub fn none() -> Self { - Timestamp { time: None } + Self { time: None } } /// Computes the duration difference of another `Timestamp` from the current one. /// Returns the difference in time as an [`core::time::Duration`]. /// Returns `None` if the other `Timestamp` is more advanced /// than the current or if either of the `Timestamp`s is not set. - pub fn duration_since(&self, other: &Timestamp) -> Option { + pub fn duration_since(&self, other: &Self) -> Option { match (self.time, other.time) { (Some(time1), Some(time2)) => time1.duration_since(time2).ok(), _ => None, @@ -197,7 +196,7 @@ impl Timestamp { /// Checks whether the timestamp has expired when compared to the /// `other` timestamp. Returns an [`Expiry`] result. - pub fn check_expiry(&self, other: &Timestamp) -> Expiry { + pub fn check_expiry(&self, other: &Self) -> Expiry { match (self.time, other.time) { (Some(time1), Some(time2)) => { if time1 > time2 { @@ -237,32 +236,26 @@ pub enum TimestampOverflowError { impl std::error::Error for TimestampOverflowError {} impl Add for Timestamp { - type Output = Result; - - fn add(self, duration: Duration) -> Result { - match self.time { - Some(time) => { - let time = - (time + duration).map_err(|_| TimestampOverflowError::TimestampOverflow)?; - Ok(Timestamp { time: Some(time) }) - } - None => Ok(self), - } + type Output = Result; + + fn add(self, duration: Duration) -> Result { + self.time + .map(|time| time + duration) + .transpose() + .map(|time| Self { time }) + .map_err(|_| TimestampOverflowError::TimestampOverflow) } } impl Sub for Timestamp { - type Output = Result; - - fn sub(self, duration: Duration) -> Result { - match self.time { - Some(time) => { - let time = - (time - duration).map_err(|_| TimestampOverflowError::TimestampOverflow)?; - Ok(Timestamp { time: Some(time) }) - } - None => Ok(self), - } + type Output = Result; + + fn sub(self, duration: Duration) -> Result { + self.time + .map(|time| time - duration) + .transpose() + .map(|time| Self { time }) + .map_err(|_| TimestampOverflowError::TimestampOverflow) } } @@ -278,8 +271,8 @@ pub enum ParseTimestampError { impl std::error::Error for ParseTimestampError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { - ParseTimestampError::ParseInt(e) => Some(e), - ParseTimestampError::DataOutOfRange(_) => None, + Self::ParseInt(e) => Some(e), + Self::DataOutOfRange(_) => None, } } } @@ -290,13 +283,13 @@ impl FromStr for Timestamp { fn from_str(s: &str) -> Result { let nanoseconds = u64::from_str(s).map_err(ParseTimestampError::ParseInt)?; - Timestamp::from_nanoseconds(nanoseconds) + Self::from_nanoseconds(nanoseconds) } } impl From