From 1ef5d613582987b53b882d045e24b1e453482216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Wed, 5 Jul 2023 10:48:59 -0400 Subject: [PATCH] Static `ClientState` API (#683) * StaticClientState * Static{Validation,Execution}Context * improve static clientstate & contexts * StaticTmClientState scaffold * tentative separation of `ClientState` * remove chain_id * move `zero_custom_fields()` * adjust comment * changelog * Remove methods from staticclientstate * static client state for tendermint WIP * `ClientStateValidation` for tendermint `ClientState` * `StaticClientStateExecution` tendermint * use static contexts in handlers * `StaticConsensusState` trait for tm `ConsensusState` * Move `MockClientRecord` to context file * mock: make records use enums instead of dyn * mock context works * bunch of errors fixed * fix tm consensus state * clippy * fix todos * use derive_more * Remove old types * Remove old `ClientState` * move things around * Rename tendermint's ClientState * Rename ValidationContext * Rename ExecutionContext * rename ClientState traits * fix doc * Rename ConsensusState * ClientState derive macro scaffold * macro improvements + test setup * fmt * remove ibc dev-dependency * implement validate_proof_height * fix previous merge from main * crate-level `allow(non_snake_case)` * `Imports` struct * move ClientStateBase impl to a module * extern crate trick * outdated comments * latest_height * implement more `ClientStateBase` methods * finish `ClientStateBase` impl * rustdoc ignore * comment line * introduce darling * ClientStateInitializer impl * export attribute * ClientStateInitializer done * fmt * add mock mode * use `ClientState` macro in tests * no long path * clippy rust 1.70 * remove useless cruft * comment * impl ClientStateValidation * use core::result::Result * ClientStateExecution impl in macro * Supported -> Any * host -> generics * rename derive macro attrs * Remove `PartialEq` from `ClientState` traits * Remove `Debug` supertrait from `ClientState` * Remove `Clone` requirement on `ClientState` traits * Remove `Send + Sync` from `ClientStateBase` * Remove `Debug + Clone` from `ConsensusState` * Remove `EncodeError` associated type (ConsensusState) * client-state-derive: move to submodule client_state * client-state-derive -> ibc-derive * `ConsensusState` macro * move `ClientState` re-export * Fix `ConsensusState` macro * add `ConsensuState` to ibc-derive-test * remove ibc-derive-test * Remove `ClientStateInitializer` * Finish removing `ClientStateInitializer` * docs * Remove `store_update_{height,time}` from `ExecutionContext` * Revert "Remove `store_update_{height,time}` from `ExecutionContext`" This reverts commit 282424f80ed3f5219bc832365ad1eaa887d48621. * update client: store update time/height in core * create client: store update height/time in core * Remove store_update_time/height from Tm Client Execution context * remove `store_{client,consensus}_state` from `ExecutionContext` * docs * tm client: `context` module * Rename tm client types * Rename TmConsensusState * Remove `dyn-clone` dependency * Remove erased-serde dependency * Remove `ErasedPartialEq` from `Header` * ClientStateCommon * `ClientExecutionContext` trait * Complete ClientExecutionContext * Rename Host{Consensus,Client}State * Move `ClientExecutionContext` * Use `ContextError` in `ClientExecutionContext` methods * mock: remove stale time/height update * mock: clients submodule * mock: application impls submodule * mock context: file reorg * ClientExecutionContext docs * `ClientState` derive macro docs * `ConsensusState` docs * `ClientState` docstring * docs * Remove unused method * tm docs * core context traits docs * refine tm client's contexts * move `get_client_execution_context` * Move `verify_consensus_state` * Remove useless match in recv_packet * fix typo in `UpgradeValidationContext` * blanket impl for TmExecutionContext * ClientState derive macro: document generic limitation * Upgrade Contexts associated types * fix * add ClientType test * chore: organize import calls * changelog * Add `CommonContext::ConversionError` --------- Co-authored-by: Farhad Shabani --- .../296-static-clientstate.md | 3 + Cargo.toml | 3 +- crates/ibc-derive/Cargo.toml | 13 + crates/ibc-derive/src/client_state.rs | 78 +++ crates/ibc-derive/src/client_state/traits.rs | 5 + .../traits/client_state_common.rs | 197 +++++++ .../traits/client_state_execution.rs | 129 +++++ .../traits/client_state_validation.rs | 94 ++++ crates/ibc-derive/src/consensus_state.rs | 64 +++ crates/ibc-derive/src/lib.rs | 36 ++ crates/ibc-derive/src/utils.rs | 111 ++++ crates/ibc/Cargo.toml | 10 +- .../clients/ics07_tendermint/client_state.rs | 463 ++++++++-------- .../client_state/misbehaviour.rs | 32 +- .../client_state/update_client.rs | 53 +- .../ics07_tendermint/consensus_state.rs | 34 +- .../src/clients/ics07_tendermint/context.rs | 56 ++ .../src/clients/ics07_tendermint/header.rs | 4 +- .../ibc/src/clients/ics07_tendermint/mod.rs | 14 + crates/ibc/src/core/context.rs | 98 ++-- crates/ibc/src/core/handler.rs | 4 +- .../ibc/src/core/ics02_client/client_state.rs | 275 +++++----- .../src/core/ics02_client/consensus_state.rs | 85 +-- crates/ibc/src/core/ics02_client/context.rs | 33 ++ .../ics02_client/handler/create_client.rs | 42 +- .../ics02_client/handler/update_client.rs | 87 +-- .../ics02_client/handler/upgrade_client.rs | 39 +- crates/ibc/src/core/ics02_client/mod.rs | 3 + crates/ibc/src/core/ics03_connection/error.rs | 3 + .../ics03_connection/handler/conn_open_ack.rs | 7 +- .../handler/conn_open_confirm.rs | 6 +- .../handler/conn_open_init.rs | 3 +- .../ics03_connection/handler/conn_open_try.rs | 9 +- crates/ibc/src/core/ics04_channel/context.rs | 19 +- .../ics04_channel/events/packet_attributes.rs | 2 +- .../ics04_channel/handler/acknowledgement.rs | 5 +- .../handler/chan_close_confirm.rs | 2 + .../ics04_channel/handler/chan_close_init.rs | 1 + .../ics04_channel/handler/chan_open_ack.rs | 2 + .../handler/chan_open_confirm.rs | 2 + .../ics04_channel/handler/chan_open_init.rs | 1 + .../ics04_channel/handler/chan_open_try.rs | 2 + .../core/ics04_channel/handler/recv_packet.rs | 2 + .../core/ics04_channel/handler/send_packet.rs | 2 + .../src/core/ics04_channel/handler/timeout.rs | 2 + .../ics04_channel/handler/timeout_on_close.rs | 2 + crates/ibc/src/erased.rs | 9 - .../tendermint/upgrade_proposal/context.rs | 16 +- .../tendermint/upgrade_proposal/handler.rs | 4 +- .../hosts/tendermint/validate_self_client.rs | 2 +- crates/ibc/src/lib.rs | 4 +- crates/ibc/src/mock/client_state.rs | 127 +++-- crates/ibc/src/mock/consensus_state.rs | 8 + crates/ibc/src/mock/context.rs | 502 +++++++----------- crates/ibc/src/mock/context/applications.rs | 89 ++++ crates/ibc/src/mock/context/clients.rs | 153 ++++++ crates/ibc/src/mock/host.rs | 17 +- crates/ibc/src/mock/ics18_relayer/context.rs | 6 +- 58 files changed, 2052 insertions(+), 1022 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/296-static-clientstate.md create mode 100644 crates/ibc-derive/Cargo.toml create mode 100644 crates/ibc-derive/src/client_state.rs create mode 100644 crates/ibc-derive/src/client_state/traits.rs create mode 100644 crates/ibc-derive/src/client_state/traits/client_state_common.rs create mode 100644 crates/ibc-derive/src/client_state/traits/client_state_execution.rs create mode 100644 crates/ibc-derive/src/client_state/traits/client_state_validation.rs create mode 100644 crates/ibc-derive/src/consensus_state.rs create mode 100644 crates/ibc-derive/src/lib.rs create mode 100644 crates/ibc-derive/src/utils.rs create mode 100644 crates/ibc/src/clients/ics07_tendermint/context.rs create mode 100644 crates/ibc/src/core/ics02_client/context.rs delete mode 100644 crates/ibc/src/erased.rs create mode 100644 crates/ibc/src/mock/context/applications.rs create mode 100644 crates/ibc/src/mock/context/clients.rs diff --git a/.changelog/unreleased/breaking-changes/296-static-clientstate.md b/.changelog/unreleased/breaking-changes/296-static-clientstate.md new file mode 100644 index 000000000..2aac2b952 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/296-static-clientstate.md @@ -0,0 +1,3 @@ +- Implement ADR 7, where `ClientState` objects are now statically dispatched instead + of dynamically dispatched. +([#296](https://github.com/cosmos/ibc-rs/issues/296)) diff --git a/Cargo.toml b/Cargo.toml index 4e0d78280..9e05bd35d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,9 @@ resolver = "2" members = [ "crates/ibc", + "crates/ibc-derive", ] exclude = [ "ci/no-std-check", -] \ No newline at end of file +] diff --git a/crates/ibc-derive/Cargo.toml b/crates/ibc-derive/Cargo.toml new file mode 100644 index 000000000..51586372f --- /dev/null +++ b/crates/ibc-derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ibc-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2" +proc-macro2 = "1" +quote = "1" +darling = "0.20" diff --git a/crates/ibc-derive/src/client_state.rs b/crates/ibc-derive/src/client_state.rs new file mode 100644 index 000000000..a6bb0e83c --- /dev/null +++ b/crates/ibc-derive/src/client_state.rs @@ -0,0 +1,78 @@ +mod traits; + +use darling::FromDeriveInput; +use proc_macro2::TokenStream; +use quote::quote; +use syn::DeriveInput; + +use traits::{ + client_state_common::impl_ClientStateCommon, client_state_execution::impl_ClientStateExecution, + client_state_validation::impl_ClientStateValidation, +}; + +#[derive(FromDeriveInput)] +#[darling(attributes(generics))] +pub(crate) struct Opts { + #[darling(rename = "ClientValidationContext")] + client_validation_context: syn::ExprPath, + #[darling(rename = "ClientExecutionContext")] + client_execution_context: syn::ExprPath, +} + +pub fn client_state_derive_impl(ast: DeriveInput) -> TokenStream { + let opts = match Opts::from_derive_input(&ast) { + Ok(opts) => opts, + Err(e) => panic!( + "{} must be annotated with #[generics(ClientValidationContext = , ClientExecutionContext: )]: {e}", + ast.ident + ), + }; + + let enum_name = &ast.ident; + let enum_variants = match ast.data { + syn::Data::Enum(ref enum_data) => &enum_data.variants, + _ => panic!("ClientState only supports enums"), + }; + + let ClientStateCommon_impl_block = impl_ClientStateCommon(enum_name, enum_variants); + let ClientStateValidation_impl_block = + impl_ClientStateValidation(enum_name, enum_variants, &opts); + let ClientStateExecution_impl_block = + impl_ClientStateExecution(enum_name, enum_variants, &opts); + + let maybe_extern_crate_stmt = if is_mock(&ast) { + // Note: we must add this statement when in "mock mode" + // (i.e. in ibc-rs itself) because we don't have `ibc` as a dependency, + // so we need to define the `ibc` symbol to mean "the `self` crate". + quote! {extern crate self as ibc;} + } else { + quote! {} + }; + + quote! { + #maybe_extern_crate_stmt + + #ClientStateCommon_impl_block + #ClientStateValidation_impl_block + #ClientStateExecution_impl_block + } +} + +/// We are in "mock mode" (i.e. within ibc-rs crate itself) if the user added +/// a #[mock] attribute +fn is_mock(ast: &DeriveInput) -> bool { + for attr in &ast.attrs { + let path = match attr.meta { + syn::Meta::Path(ref path) => path, + _ => continue, + }; + + for path_segment in path.segments.iter() { + if path_segment.ident == "mock" { + return true; + } + } + } + + false +} diff --git a/crates/ibc-derive/src/client_state/traits.rs b/crates/ibc-derive/src/client_state/traits.rs new file mode 100644 index 000000000..dd537deda --- /dev/null +++ b/crates/ibc-derive/src/client_state/traits.rs @@ -0,0 +1,5 @@ +//! Hosts the code generation of the `impl`s for the `ClientState` traits + +pub mod client_state_common; +pub mod client_state_execution; +pub mod client_state_validation; diff --git a/crates/ibc-derive/src/client_state/traits/client_state_common.rs b/crates/ibc-derive/src/client_state/traits/client_state_common.rs new file mode 100644 index 000000000..934877d8d --- /dev/null +++ b/crates/ibc-derive/src/client_state/traits/client_state_common.rs @@ -0,0 +1,197 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + punctuated::{Iter, Punctuated}, + token::Comma, + Variant, +}; + +use crate::utils::{get_enum_variant_type_path, Imports}; + +pub(crate) fn impl_ClientStateCommon( + client_state_enum_name: &Ident, + enum_variants: &Punctuated, +) -> TokenStream { + let verify_consensus_state_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! { verify_consensus_state(cs, consensus_state) }, + ); + let client_type_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {client_type(cs)}, + ); + let latest_height_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {latest_height(cs)}, + ); + let validate_proof_height_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {validate_proof_height(cs, proof_height)}, + ); + let confirm_not_frozen_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {confirm_not_frozen(cs)}, + ); + let expired_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {expired(cs, elapsed)}, + ); + let verify_upgrade_client_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {verify_upgrade_client(cs, upgraded_client_state, upgraded_consensus_state, proof_upgrade_client, proof_upgrade_consensus_state, root)}, + ); + let verify_membership_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {verify_membership(cs, prefix, proof, root, path, value)}, + ); + let verify_non_membership_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + quote! {verify_non_membership(cs, prefix, proof, root, path)}, + ); + + let HostClientState = client_state_enum_name; + + let Any = Imports::Any(); + let CommitmentRoot = Imports::CommitmentRoot(); + let CommitmentPrefix = Imports::CommitmentPrefix(); + let CommitmentProofBytes = Imports::CommitmentProofBytes(); + let ClientStateCommon = Imports::ClientStateCommon(); + let ClientType = Imports::ClientType(); + let ClientError = Imports::ClientError(); + let Height = Imports::Height(); + let MerkleProof = Imports::MerkleProof(); + let Path = Imports::Path(); + + quote! { + impl #ClientStateCommon for #HostClientState { + fn verify_consensus_state(&self, consensus_state: #Any) -> Result<(), #ClientError> { + match self { + #(#verify_consensus_state_impl),* + } + } + fn client_type(&self) -> #ClientType { + match self { + #(#client_type_impl),* + } + } + + fn latest_height(&self) -> #Height { + match self { + #(#latest_height_impl),* + } + } + + fn validate_proof_height(&self, proof_height: #Height) -> core::result::Result<(), #ClientError> { + match self { + #(#validate_proof_height_impl),* + } + } + + fn confirm_not_frozen(&self) -> core::result::Result<(), #ClientError> { + match self { + #(#confirm_not_frozen_impl),* + } + } + + fn expired(&self, elapsed: core::time::Duration) -> bool { + match self { + #(#expired_impl),* + } + } + + fn verify_upgrade_client( + &self, + upgraded_client_state: #Any, + upgraded_consensus_state: #Any, + proof_upgrade_client: #MerkleProof, + proof_upgrade_consensus_state: #MerkleProof, + root: &#CommitmentRoot, + ) -> core::result::Result<(), #ClientError> { + match self { + #(#verify_upgrade_client_impl),* + } + } + + fn verify_membership( + &self, + prefix: &#CommitmentPrefix, + proof: &#CommitmentProofBytes, + root: &#CommitmentRoot, + path: #Path, + value: Vec, + ) -> core::result::Result<(), #ClientError> { + match self { + #(#verify_membership_impl),* + } + } + + fn verify_non_membership( + &self, + prefix: &#CommitmentPrefix, + proof: &#CommitmentProofBytes, + root: &#CommitmentRoot, + path: #Path, + ) -> core::result::Result<(), #ClientError> { + match self { + #(#verify_non_membership_impl),* + } + } + } + + } +} + +/// +/// 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` +/// is `cs` (e.g. `client_type(cs)`). +/// +/// For example, +/// +/// ```ignore +/// impl ClientStateCommon for HostClientState { +/// fn client_type(&self) -> ClientType { +/// match self { +/// // BEGIN code generated +/// +/// // 1st TokenStream returned +/// HostClientState::Tendermint(cs) => ::client_type(cs), +/// // 2nd TokenStream returned +/// HostClientState::Mock(cs) => ::client_type(cs), +/// +/// // END code generated +/// } +/// } +/// } +/// ``` +/// +fn delegate_call_in_match( + enum_name: &Ident, + enum_variants: Iter<'_, Variant>, + fn_call: TokenStream, +) -> Vec { + let ClientStateCommon = Imports::ClientStateCommon(); + + enum_variants + .map(|variant| { + let variant_name = &variant.ident; + let variant_type_name = get_enum_variant_type_path(variant); + + quote! { + #enum_name::#variant_name(cs) => <#variant_type_name as #ClientStateCommon>::#fn_call + } + }) + .collect() +} diff --git a/crates/ibc-derive/src/client_state/traits/client_state_execution.rs b/crates/ibc-derive/src/client_state/traits/client_state_execution.rs new file mode 100644 index 000000000..5be7b0fab --- /dev/null +++ b/crates/ibc-derive/src/client_state/traits/client_state_execution.rs @@ -0,0 +1,129 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + punctuated::{Iter, Punctuated}, + token::Comma, + Variant, +}; + +use crate::{ + client_state::Opts, + utils::{get_enum_variant_type_path, Imports}, +}; + +pub(crate) fn impl_ClientStateExecution( + client_state_enum_name: &Ident, + enum_variants: &Punctuated, + opts: &Opts, +) -> TokenStream { + let initialise_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { initialise(cs, ctx, client_id, consensus_state) }, + ); + let update_state_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { update_state(cs, ctx, client_id, header) }, + ); + let update_state_on_misbehaviour_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { update_state_on_misbehaviour(cs, ctx, client_id, client_message, update_kind) }, + ); + + let update_state_with_upgrade_client_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { update_state_with_upgrade_client(cs, ctx, client_id, upgraded_client_state, upgraded_consensus_state) }, + ); + + let HostClientState = client_state_enum_name; + let ClientExecutionContext = &opts.client_execution_context; + + let Any = Imports::Any(); + let ClientId = Imports::ClientId(); + let ClientError = Imports::ClientError(); + let ClientStateExecution = Imports::ClientStateExecution(); + let UpdateKind = Imports::UpdateKind(); + let Height = Imports::Height(); + + quote! { + impl #ClientStateExecution<#ClientExecutionContext> for #HostClientState { + fn initialise( + &self, + ctx: &mut #ClientExecutionContext, + client_id: &#ClientId, + consensus_state: #Any, + ) -> Result<(), #ClientError> { + match self { + #(#initialise_impl),* + } + } + + fn update_state( + &self, + ctx: &mut #ClientExecutionContext, + client_id: &#ClientId, + header: #Any, + ) -> core::result::Result, #ClientError> { + match self { + #(#update_state_impl),* + } + } + + fn update_state_on_misbehaviour( + &self, + ctx: &mut #ClientExecutionContext, + client_id: &#ClientId, + client_message: #Any, + update_kind: &#UpdateKind, + ) -> core::result::Result<(), #ClientError> { + match self { + #(#update_state_on_misbehaviour_impl),* + } + } + + fn update_state_with_upgrade_client( + &self, + ctx: &mut #ClientExecutionContext, + client_id: &#ClientId, + upgraded_client_state: #Any, + upgraded_consensus_state: #Any, + ) -> core::result::Result<#Height, #ClientError> { + match self { + #(#update_state_with_upgrade_client_impl),* + } + } + } + + } +} + +fn delegate_call_in_match( + enum_name: &Ident, + enum_variants: Iter<'_, Variant>, + opts: &Opts, + fn_call: TokenStream, +) -> Vec { + let ClientStateExecution = Imports::ClientStateExecution(); + + enum_variants + .map(|variant| { + let HostClientState = enum_name; + let Tendermint = &variant.ident; + let TmClientState = get_enum_variant_type_path(variant); + let ClientExecutionContext = &opts.client_execution_context; + + // Note: We use `HostClientState` and `Tendermint`, etc as *variable names*. They're + // only meant to improve readability of the `quote`; it's not literally what's generated! + quote! { + #HostClientState::#Tendermint(cs) => <#TmClientState as #ClientStateExecution<#ClientExecutionContext>>::#fn_call + } + }) + .collect() +} diff --git a/crates/ibc-derive/src/client_state/traits/client_state_validation.rs b/crates/ibc-derive/src/client_state/traits/client_state_validation.rs new file mode 100644 index 000000000..3bc90958c --- /dev/null +++ b/crates/ibc-derive/src/client_state/traits/client_state_validation.rs @@ -0,0 +1,94 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + punctuated::{Iter, Punctuated}, + token::Comma, + Variant, +}; + +use crate::{ + client_state::Opts, + utils::{get_enum_variant_type_path, Imports}, +}; + +pub(crate) fn impl_ClientStateValidation( + client_state_enum_name: &Ident, + enum_variants: &Punctuated, + opts: &Opts, +) -> TokenStream { + let verify_client_message_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { verify_client_message(cs, ctx, client_id, client_message, update_kind) }, + ); + + let check_for_misbehaviour_impl = delegate_call_in_match( + client_state_enum_name, + enum_variants.iter(), + opts, + quote! { check_for_misbehaviour(cs, ctx, client_id, client_message, update_kind) }, + ); + + let HostClientState = client_state_enum_name; + let ClientValidationContext = &opts.client_validation_context; + + let Any = Imports::Any(); + let ClientId = Imports::ClientId(); + let ClientError = Imports::ClientError(); + let ClientStateValidation = Imports::ClientStateValidation(); + let UpdateKind = Imports::UpdateKind(); + + quote! { + impl #ClientStateValidation<#ClientValidationContext> for #HostClientState { + fn verify_client_message( + &self, + ctx: &#ClientValidationContext, + client_id: &#ClientId, + client_message: #Any, + update_kind: &#UpdateKind, + ) -> core::result::Result<(), #ClientError> { + match self { + #(#verify_client_message_impl),* + } + } + + fn check_for_misbehaviour( + &self, + ctx: &#ClientValidationContext, + client_id: &#ClientId, + client_message: #Any, + update_kind: &#UpdateKind, + ) -> core::result::Result { + match self { + #(#check_for_misbehaviour_impl),* + } + } + } + + } +} + +fn delegate_call_in_match( + enum_name: &Ident, + enum_variants: Iter<'_, Variant>, + opts: &Opts, + fn_call: TokenStream, +) -> Vec { + let ClientStateValidation = Imports::ClientStateValidation(); + + enum_variants + .map(|variant| { + let HostClientState = enum_name; + let Tendermint = &variant.ident; + let TmClientState = get_enum_variant_type_path(variant); + let ClientValidationContext = &opts.client_validation_context; + + // Note: We use `HostClientState` and `Tendermint`, etc as *variable names*. They're + // only meant to improve readability of the `quote`; it's not literally what's generated! + quote! { + #HostClientState::#Tendermint(cs) => <#TmClientState as #ClientStateValidation<#ClientValidationContext>>::#fn_call + } + }) + .collect() +} diff --git a/crates/ibc-derive/src/consensus_state.rs b/crates/ibc-derive/src/consensus_state.rs new file mode 100644 index 000000000..070c85daf --- /dev/null +++ b/crates/ibc-derive/src/consensus_state.rs @@ -0,0 +1,64 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{punctuated::Iter, DeriveInput, Ident, Variant}; + +use crate::utils::{get_enum_variant_type_path, Imports}; + +pub fn consensus_state_derive_impl(ast: DeriveInput) -> TokenStream { + let enum_name = &ast.ident; + let enum_variants = match ast.data { + syn::Data::Enum(ref enum_data) => &enum_data.variants, + _ => panic!("ConsensusState only supports enums"), + }; + + let root_impl = delegate_call_in_match(enum_name, enum_variants.iter(), quote! {root(cs)}); + let timestamp_impl = + delegate_call_in_match(enum_name, enum_variants.iter(), quote! {timestamp(cs)}); + let encode_vec_impl = + delegate_call_in_match(enum_name, enum_variants.iter(), quote! {encode_vec(cs)}); + + let CommitmentRoot = Imports::CommitmentRoot(); + let ConsensusState = Imports::ConsensusState(); + let Timestamp = Imports::Timestamp(); + + quote! { + impl #ConsensusState for #enum_name { + fn root(&self) -> &#CommitmentRoot { + match self { + #(#root_impl),* + } + } + + fn timestamp(&self) -> #Timestamp { + match self { + #(#timestamp_impl),* + } + } + + fn encode_vec(&self) -> core::result::Result, tendermint_proto::Error> { + match self { + #(#encode_vec_impl),* + } + } + } + } +} + +fn delegate_call_in_match( + enum_name: &Ident, + enum_variants: Iter<'_, Variant>, + fn_call: TokenStream, +) -> Vec { + let ConsensusState = Imports::ConsensusState(); + + enum_variants + .map(|variant| { + let variant_name = &variant.ident; + let variant_type_name = get_enum_variant_type_path(variant); + + quote! { + #enum_name::#variant_name(cs) => <#variant_type_name as #ConsensusState>::#fn_call + } + }) + .collect() +} diff --git a/crates/ibc-derive/src/lib.rs b/crates/ibc-derive/src/lib.rs new file mode 100644 index 000000000..9c165082c --- /dev/null +++ b/crates/ibc-derive/src/lib.rs @@ -0,0 +1,36 @@ +#![deny( + warnings, + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + rust_2018_idioms +)] +#![allow(non_snake_case)] + +mod client_state; +mod consensus_state; +mod utils; + +use client_state::client_state_derive_impl; +use consensus_state::consensus_state_derive_impl; +use proc_macro::TokenStream as RawTokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(ClientState, attributes(generics, mock))] +pub fn client_state_macro_derive(input: RawTokenStream) -> RawTokenStream { + let ast: DeriveInput = parse_macro_input!(input); + + let output = client_state_derive_impl(ast); + + RawTokenStream::from(output) +} + +#[proc_macro_derive(ConsensusState)] +pub fn consensus_state_macro_derive(input: RawTokenStream) -> RawTokenStream { + let ast: DeriveInput = parse_macro_input!(input); + + let output = consensus_state_derive_impl(ast); + + RawTokenStream::from(output) +} diff --git a/crates/ibc-derive/src/utils.rs b/crates/ibc-derive/src/utils.rs new file mode 100644 index 000000000..080f3fe4a --- /dev/null +++ b/crates/ibc-derive/src/utils.rs @@ -0,0 +1,111 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Path, Variant}; + +/// Encodes the ibc-rs types that will be used in the macro +/// +/// Note: we use `ibc` as our top-level crate, due to the +/// `extern crate ibc as ibc;` statement we inject. +pub struct Imports; + +impl Imports { + pub fn CommitmentRoot() -> TokenStream { + quote! {ibc::core::ics23_commitment::commitment::CommitmentRoot} + } + + pub fn CommitmentPrefix() -> TokenStream { + quote! {ibc::core::ics23_commitment::commitment::CommitmentPrefix} + } + + pub fn CommitmentProofBytes() -> TokenStream { + quote! {ibc::core::ics23_commitment::commitment::CommitmentProofBytes} + } + + pub fn Path() -> TokenStream { + quote! {ibc::core::ics24_host::path::Path} + } + + pub fn ConsensusState() -> TokenStream { + quote! {ibc::core::ics02_client::consensus_state::ConsensusState} + } + + pub fn ClientStateCommon() -> TokenStream { + quote! {ibc::core::ics02_client::client_state::ClientStateCommon} + } + + pub fn ClientStateValidation() -> TokenStream { + quote! {ibc::core::ics02_client::client_state::ClientStateValidation} + } + + pub fn ClientStateExecution() -> TokenStream { + quote! {ibc::core::ics02_client::client_state::ClientStateExecution} + } + + pub fn ClientId() -> TokenStream { + quote! {ibc::core::ics24_host::identifier::ClientId} + } + + pub fn ClientType() -> TokenStream { + quote! {ibc::core::ics02_client::client_type::ClientType} + } + + pub fn ClientError() -> TokenStream { + quote! {ibc::core::ics02_client::error::ClientError} + } + + pub fn Height() -> TokenStream { + quote! {ibc::Height} + } + + pub fn Any() -> TokenStream { + quote! {ibc_proto::google::protobuf::Any} + } + + pub fn MerkleProof() -> TokenStream { + quote! {ibc::core::ics23_commitment::merkle::MerkleProof} + } + + pub fn Timestamp() -> TokenStream { + quote! {ibc::core::timestamp::Timestamp} + } + + pub fn UpdateKind() -> TokenStream { + quote! {ibc::core::ics02_client::client_state::UpdateKind} + } +} + +/// Retrieves the field of a given enum variant. Outputs an error message if the enum variant +/// is in the wrong format (i.e. isn't an unnamed enum, or contains more than one field). +/// +/// For example, given +/// ```ignore +/// +/// #[derive(ClientState)] +/// enum HostClientState { +/// Tendermint(TmClientState), +/// } +/// ``` +/// when acting on the `Tendermint` variant, this will return `TmClientState`. +/// +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)`") + }; + + if variant_unnamed_fields.unnamed.iter().len() != 1 { + panic!("\"{variant_name}\" must contain exactly one field, such as `{variant_name}({variant_name}ClientState)`"); + } + + // A representation of the variant's field (e.g. `TmClientState`). We must dig into + // the field to get the `TmClientState` path. + let unnamed_field = variant_unnamed_fields.unnamed.first().unwrap(); + + match &unnamed_field.ty { + syn::Type::Path(path) => &path.path, + _ => { + panic!("Invalid enum variant {variant_name} field. Please use an explicit, named type.") + } + } +} diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index 941206df1..91f1679b8 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -23,7 +23,6 @@ std = [ "ics23/std", "serde/std", "serde_json/std", - "erased-serde/std", "tracing/std", "prost/std", "bytes/std", @@ -40,7 +39,7 @@ parity-scale-codec = ["dep:parity-scale-codec", "dep:scale-info", "ibc-proto/par borsh = ["dep:borsh", "ibc-proto/borsh"] # This feature is required for token transfer (ICS-20) -serde = ["dep:serde", "dep:serde_derive", "serde_json", "erased-serde", "ics23/serde"] +serde = ["dep:serde", "dep:serde_derive", "serde_json", "ics23/serde"] # This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`. # Depends on the `testgen` suite for generating Tendermint light blocks. @@ -54,7 +53,6 @@ time = { version = ">=0.3.0, <0.3.23", default-features = false } serde_derive = { version = "1.0.104", default-features = false, optional = true } serde = { version = "1.0", default-features = false, optional = true } serde_json = { version = "1", default-features = false, optional = true } -erased-serde = { version = "0.3", default-features = false, features = ["alloc"], optional = true } tracing = { version = "0.1.36", default-features = false } prost = { version = "0.11", default-features = false } bytes = { version = "1.2.1", default-features = false } @@ -63,10 +61,10 @@ subtle-encoding = { version = "0.5", default-features = false } sha2 = { version = "0.10.6", default-features = false } displaydoc = { version = "0.2", default-features = false } num-traits = { version = "0.2.15", default-features = false } -derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display"] } +derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display", "try_into"] } uint = { version = "0.9", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["serde_no_std"] } -dyn-clone = "1.0.8" + ## for codec encode or decode parity-scale-codec = { version = "3.0.0", default-features = false, features = ["full"], optional = true } scale-info = { version = "2.1.2", default-features = false, features = ["derive"], optional = true } @@ -74,6 +72,8 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" borsh = {version = "0.9.0", default-features = false, optional = true } parking_lot = { version = "0.12.1", default-features = false, optional = true } +ibc-derive = { path = "../ibc-derive"} + [dependencies.tendermint] version = "0.32" default-features = false diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state.rs b/crates/ibc/src/clients/ics07_tendermint/client_state.rs index 32c1f37fd..3d7e72a15 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state.rs @@ -22,17 +22,17 @@ use tendermint::trust_threshold::TrustThresholdFraction as TendermintTrustThresh use tendermint_light_client_verifier::options::Options; use tendermint_light_client_verifier::ProdVerifier; -use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::clients::ics07_tendermint::error::Error; use crate::clients::ics07_tendermint::header::Header as TmHeader; use crate::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; use crate::core::ics02_client::client_state::{ - ClientState as Ics2ClientState, UpdateKind, UpdatedState, + ClientStateCommon, ClientStateExecution, ClientStateValidation, UpdateKind, }; use crate::core::ics02_client::client_type::ClientType; use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::{ClientError, UpgradeClientError}; +use crate::core::ics02_client::ClientExecutionContext; use crate::core::ics23_commitment::commitment::{ CommitmentPrefix, CommitmentProofBytes, CommitmentRoot, }; @@ -44,11 +44,20 @@ use crate::core::ics24_host::path::{ClientConsensusStatePath, ClientStatePath, U use crate::core::timestamp::ZERO_DURATION; use crate::Height; -use super::client_type as tm_client_type; use super::trust_threshold::TrustThreshold; -use crate::core::{ExecutionContext, ValidationContext}; +use super::{ + client_type as tm_client_type, ExecutionContext as TmExecutionContext, + ValidationContext as TmValidationContext, +}; -const TENDERMINT_CLIENT_STATE_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.ClientState"; +pub const TENDERMINT_CLIENT_STATE_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.ClientState"; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct AllowUpdate { + pub after_expiry: bool, + pub after_misbehaviour: bool, +} /// Contains the core implementation of the Tendermint light client #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -59,7 +68,7 @@ pub struct ClientState { pub trusting_period: Duration, pub unbonding_period: Duration, max_clock_drift: Duration, - latest_height: Height, + pub latest_height: Height, pub proof_specs: ProofSpecs, pub upgrade_path: Vec, allow_update: AllowUpdate, @@ -68,13 +77,6 @@ pub struct ClientState { verifier: ProdVerifier, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AllowUpdate { - pub after_expiry: bool, - pub after_misbehaviour: bool, -} - impl ClientState { #[allow(clippy::too_many_arguments)] fn new_without_validation( @@ -87,7 +89,7 @@ impl ClientState { proof_specs: ProofSpecs, upgrade_path: Vec, allow_update: AllowUpdate, - ) -> ClientState { + ) -> Self { Self { chain_id, trust_level, @@ -114,7 +116,7 @@ impl ClientState { proof_specs: ProofSpecs, upgrade_path: Vec, allow_update: AllowUpdate, - ) -> Result { + ) -> Result { let client_state = Self::new_without_validation( chain_id, trust_level, @@ -131,7 +133,7 @@ impl ClientState { } pub fn with_header(self, header: TmHeader) -> Result { - Ok(ClientState { + Ok(Self { latest_height: max(header.height(), self.latest_height), ..self }) @@ -246,6 +248,7 @@ impl ClientState { clock_drift: self.max_clock_drift, }) } + fn chain_id(&self) -> ChainId { self.chain_id.clone() } @@ -261,7 +264,18 @@ impl ClientState { } } -impl Ics2ClientState for ClientState { +impl ClientStateCommon for ClientState { + fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError> { + let tm_consensus_state = TmConsensusState::try_from(consensus_state)?; + if tm_consensus_state.root().is_empty() { + return Err(ClientError::Other { + description: "empty commitment root".into(), + }); + }; + + Ok(()) + } + fn client_type(&self) -> ClientType { tm_client_type() } @@ -293,116 +307,6 @@ impl Ics2ClientState for ClientState { elapsed > self.trusting_period } - fn initialise(&self, consensus_state: Any) -> Result, ClientError> { - let tm_consensus_state = TmConsensusState::try_from(consensus_state)?; - if tm_consensus_state.root().is_empty() { - return Err(ClientError::Other { - description: "empty commitment root".into(), - }); - }; - Ok(Box::new(tm_consensus_state)) - } - - fn verify_client_message( - &self, - ctx: &dyn ValidationContext, - client_id: &ClientId, - client_message: Any, - update_kind: &UpdateKind, - ) -> Result<(), ClientError> { - match update_kind { - UpdateKind::UpdateClient => { - let header = TmHeader::try_from(client_message)?; - self.verify_header(ctx, client_id, header) - } - UpdateKind::SubmitMisbehaviour => { - let misbehaviour = TmMisbehaviour::try_from(client_message)?; - self.verify_misbehaviour(ctx, client_id, misbehaviour) - } - } - } - - // The misbehaviour checked for depends on the kind of message submitted: - // + For a submitted `Header`, detects duplicate height misbehaviour and BFT time violation misbehaviour - // + For a submitted `Misbehaviour`, verifies the correctness of the message - fn check_for_misbehaviour( - &self, - ctx: &dyn ValidationContext, - client_id: &ClientId, - client_message: Any, - update_kind: &UpdateKind, - ) -> Result { - match update_kind { - UpdateKind::UpdateClient => { - let header = TmHeader::try_from(client_message)?; - self.check_for_misbehaviour_update_client(ctx, client_id, header) - } - UpdateKind::SubmitMisbehaviour => { - let misbehaviour = TmMisbehaviour::try_from(client_message)?; - self.check_for_misbehaviour_misbehavior(&misbehaviour) - } - } - } - fn update_state( - &self, - ctx: &mut dyn ExecutionContext, - client_id: &ClientId, - header: Any, - ) -> Result, ClientError> { - let header = TmHeader::try_from(header)?; - let header_height = header.height(); - - let maybe_existing_consensus_state = { - let path_at_header_height = ClientConsensusStatePath::new(client_id, &header_height); - - ctx.consensus_state(&path_at_header_height).ok() - }; - - if maybe_existing_consensus_state.is_some() { - // if we already had the header installed by a previous relayer - // then this is a no-op. - // - // Do nothing. - } else { - let new_consensus_state = TmConsensusState::from(header.clone()).into_box(); - let new_client_state = self.clone().with_header(header)?.into_box(); - - ctx.store_update_time( - client_id.clone(), - new_client_state.latest_height(), - ctx.host_timestamp()?, - )?; - ctx.store_update_height( - client_id.clone(), - new_client_state.latest_height(), - ctx.host_height()?, - )?; - - ctx.store_consensus_state( - ClientConsensusStatePath::new(client_id, &new_client_state.latest_height()), - new_consensus_state, - )?; - ctx.store_client_state(ClientStatePath::new(client_id), new_client_state)?; - } - - let updated_heights = vec![header_height]; - Ok(updated_heights) - } - - fn update_state_on_misbehaviour( - &self, - ctx: &mut dyn ExecutionContext, - client_id: &ClientId, - _client_message: Any, - _update_kind: &UpdateKind, - ) -> Result<(), ClientError> { - let frozen_client_state = self.clone().with_frozen_height(Height::min(0)).into_box(); - - ctx.store_client_state(ClientStatePath::new(client_id), frozen_client_state)?; - - Ok(()) - } - /// Perform client-specific verifications and check all data in the new /// client state to be the same across all valid Tendermint clients for the /// new chain. @@ -419,7 +323,7 @@ impl Ics2ClientState for ClientState { root: &CommitmentRoot, ) -> Result<(), ClientError> { // Make sure that the client type is of Tendermint type `ClientState` - let mut upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state.clone())?; + let mut upgraded_tm_client_state = Self::try_from(upgraded_client_state.clone())?; // Make sure that the consensus type is of Tendermint type `ConsensusState` TmConsensusState::try_from(upgraded_consensus_state.clone())?; @@ -427,10 +331,10 @@ impl Ics2ClientState for ClientState { // Make sure the latest height of the current client is not greater then // the upgrade height This condition checks both the revision number and // the height - if self.latest_height() >= upgraded_tm_client_state.latest_height() { + if self.latest_height() >= upgraded_tm_client_state.latest_height { return Err(UpgradeClientError::LowUpgradeHeight { upgraded_height: self.latest_height(), - client_height: upgraded_tm_client_state.latest_height(), + client_height: upgraded_tm_client_state.latest_height, })?; } @@ -497,20 +401,179 @@ impl Ics2ClientState for ClientState { Ok(()) } + fn verify_membership( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: Path, + value: Vec, + ) -> Result<(), ClientError> { + let merkle_path = apply_prefix(prefix, vec![path.to_string()]); + let merkle_proof: MerkleProof = RawMerkleProof::try_from(proof.clone()) + .map_err(ClientError::InvalidCommitmentProof)? + .into(); + + merkle_proof + .verify_membership( + &self.proof_specs, + root.clone().into(), + merkle_path, + value, + 0, + ) + .map_err(ClientError::Ics23Verification) + } + + fn verify_non_membership( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: Path, + ) -> Result<(), ClientError> { + let merkle_path = apply_prefix(prefix, vec![path.to_string()]); + let merkle_proof: MerkleProof = RawMerkleProof::try_from(proof.clone()) + .map_err(ClientError::InvalidCommitmentProof)? + .into(); + + merkle_proof + .verify_non_membership(&self.proof_specs, root.clone().into(), merkle_path) + .map_err(ClientError::Ics23Verification) + } +} + +impl ClientStateValidation for ClientState +where + ClientValidationContext: TmValidationContext, +{ + fn verify_client_message( + &self, + ctx: &ClientValidationContext, + client_id: &ClientId, + client_message: Any, + update_kind: &UpdateKind, + ) -> Result<(), ClientError> { + match update_kind { + UpdateKind::UpdateClient => { + let header = TmHeader::try_from(client_message)?; + self.verify_header(ctx, client_id, header) + } + UpdateKind::SubmitMisbehaviour => { + let misbehaviour = TmMisbehaviour::try_from(client_message)?; + self.verify_misbehaviour(ctx, client_id, misbehaviour) + } + } + } + + fn check_for_misbehaviour( + &self, + ctx: &ClientValidationContext, + client_id: &ClientId, + client_message: Any, + update_kind: &UpdateKind, + ) -> Result { + match update_kind { + UpdateKind::UpdateClient => { + let header = TmHeader::try_from(client_message)?; + self.check_for_misbehaviour_update_client(ctx, client_id, header) + } + UpdateKind::SubmitMisbehaviour => { + let misbehaviour = TmMisbehaviour::try_from(client_message)?; + self.check_for_misbehaviour_misbehavior(&misbehaviour) + } + } + } +} + +impl ClientStateExecution for ClientState +where + E: TmExecutionContext, + ::AnyClientState: From, + ::AnyConsensusState: From, +{ + fn initialise( + &self, + ctx: &mut E, + client_id: &ClientId, + consensus_state: Any, + ) -> Result<(), ClientError> { + let tm_consensus_state = TmConsensusState::try_from(consensus_state)?; + + ctx.store_client_state(ClientStatePath::new(client_id), self.clone().into())?; + ctx.store_consensus_state( + ClientConsensusStatePath::new(client_id, &self.latest_height), + tm_consensus_state.into(), + )?; + + Ok(()) + } + + fn update_state( + &self, + ctx: &mut E, + client_id: &ClientId, + header: Any, + ) -> Result, ClientError> { + let header = TmHeader::try_from(header)?; + let header_height = header.height(); + + let maybe_existing_consensus_state = { + let path_at_header_height = ClientConsensusStatePath::new(client_id, &header_height); + + ctx.consensus_state(&path_at_header_height).ok() + }; + + if maybe_existing_consensus_state.is_some() { + // if we already had the header installed by a previous relayer + // then this is a no-op. + // + // Do nothing. + } else { + let new_consensus_state = TmConsensusState::from(header.clone()); + let new_client_state = self.clone().with_header(header)?; + + ctx.store_consensus_state( + ClientConsensusStatePath::new(client_id, &new_client_state.latest_height), + new_consensus_state.into(), + )?; + ctx.store_client_state(ClientStatePath::new(client_id), new_client_state.into())?; + } + + let updated_heights = vec![header_height]; + Ok(updated_heights) + } + + fn update_state_on_misbehaviour( + &self, + ctx: &mut E, + client_id: &ClientId, + _client_message: Any, + _update_kind: &UpdateKind, + ) -> Result<(), ClientError> { + let frozen_client_state = self.clone().with_frozen_height(Height::min(0)); + + ctx.store_client_state(ClientStatePath::new(client_id), frozen_client_state.into())?; + + Ok(()) + } + // Commit the new client state and consensus state to the store fn update_state_with_upgrade_client( &self, + ctx: &mut E, + client_id: &ClientId, upgraded_client_state: Any, upgraded_consensus_state: Any, - ) -> Result { - let upgraded_tm_client_state = TmClientState::try_from(upgraded_client_state)?; + ) -> Result { + let upgraded_tm_client_state = Self::try_from(upgraded_client_state)?; let upgraded_tm_cons_state = TmConsensusState::try_from(upgraded_consensus_state)?; // Construct new client state and consensus state relayer chosen client // parameters are ignored. All chain-chosen parameters come from // committed client, all client-chosen parameters come from current // client. - let new_client_state = TmClientState::new( + let new_client_state = ClientState::new( upgraded_tm_client_state.chain_id, self.trust_level, self.trusting_period, @@ -542,92 +605,18 @@ impl Ics2ClientState for ClientState { upgraded_tm_cons_state.next_validators_hash, ); - Ok(UpdatedState { - client_state: new_client_state.into_box(), - consensus_state: new_consensus_state.into_box(), - }) - } + let latest_height = new_client_state.latest_height; - fn verify_membership( - &self, - prefix: &CommitmentPrefix, - proof: &CommitmentProofBytes, - root: &CommitmentRoot, - path: Path, - value: Vec, - ) -> Result<(), ClientError> { - let client_state = downcast_tm_client_state(self)?; - - let merkle_path = apply_prefix(prefix, vec![path.to_string()]); - let merkle_proof: MerkleProof = RawMerkleProof::try_from(proof.clone()) - .map_err(ClientError::InvalidCommitmentProof)? - .into(); - - merkle_proof - .verify_membership( - &client_state.proof_specs, - root.clone().into(), - merkle_path, - value, - 0, - ) - .map_err(ClientError::Ics23Verification) - } - - fn verify_non_membership( - &self, - prefix: &CommitmentPrefix, - proof: &CommitmentProofBytes, - root: &CommitmentRoot, - path: Path, - ) -> Result<(), ClientError> { - let client_state = downcast_tm_client_state(self)?; - - let merkle_path = apply_prefix(prefix, vec![path.to_string()]); - let merkle_proof: MerkleProof = RawMerkleProof::try_from(proof.clone()) - .map_err(ClientError::InvalidCommitmentProof)? - .into(); - - merkle_proof - .verify_non_membership(&client_state.proof_specs, root.clone().into(), merkle_path) - .map_err(ClientError::Ics23Verification) - } -} + ctx.store_client_state(ClientStatePath::new(client_id), new_client_state.into())?; + ctx.store_consensus_state( + ClientConsensusStatePath::new(client_id, &latest_height), + new_consensus_state.into(), + )?; -// `header.trusted_validator_set` was given to us by the relayer. Thus, we -// need to ensure that the relayer gave us the right set, i.e. by ensuring -// that it matches the hash we have stored on chain. -fn check_header_trusted_next_validator_set( - header: &TmHeader, - trusted_consensus_state: &TmConsensusState, -) -> Result<(), ClientError> { - if header.trusted_next_validator_set.hash() == trusted_consensus_state.next_validators_hash { - Ok(()) - } else { - Err(ClientError::HeaderVerificationFailure { - reason: "header trusted next validator set hash does not match hash stored on chain" - .to_string(), - }) + Ok(latest_height) } } -fn downcast_tm_client_state(cs: &dyn Ics2ClientState) -> Result<&ClientState, ClientError> { - cs.as_any() - .downcast_ref::() - .ok_or_else(|| ClientError::ClientArgsTypeMismatch { - client_type: tm_client_type(), - }) -} - -fn downcast_tm_consensus_state(cs: &dyn ConsensusState) -> Result { - cs.as_any() - .downcast_ref::() - .ok_or_else(|| ClientError::ClientArgsTypeMismatch { - client_type: tm_client_type(), - }) - .map(Clone::clone) -} - impl Protobuf for ClientState {} impl TryFrom for ClientState { @@ -691,7 +680,7 @@ impl TryFrom for ClientState { after_misbehaviour: raw.allow_update_after_misbehaviour, }; - let client_state = ClientState::new_without_validation( + let client_state = Self::new_without_validation( chain_id, trust_level, trusting_period, @@ -766,6 +755,23 @@ impl From for Any { } } +// `header.trusted_validator_set` was given to us by the relayer. Thus, we +// need to ensure that the relayer gave us the right set, i.e. by ensuring +// that it matches the hash we have stored on chain. +fn check_header_trusted_next_validator_set( + header: &TmHeader, + trusted_consensus_state: &TmConsensusState, +) -> Result<(), ClientError> { + if header.trusted_next_validator_set.hash() == trusted_consensus_state.next_validators_hash { + Ok(()) + } else { + Err(ClientError::HeaderVerificationFailure { + reason: "header trusted next validator set hash does not match hash stored on chain" + .to_string(), + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -779,11 +785,8 @@ mod tests { use ibc_proto::ibc::core::client::v1::Height as RawHeight; use ibc_proto::ics23::ProofSpec as Ics23ProofSpec; - use crate::clients::ics07_tendermint::client_state::{ - AllowUpdate, ClientState as TmClientState, - }; + use crate::clients::ics07_tendermint::client_state::AllowUpdate; use crate::clients::ics07_tendermint::error::Error; - use crate::core::ics02_client::client_state::ClientState; use crate::core::ics23_commitment::specs::ProofSpecs; use crate::core::ics24_host::identifier::ChainId; use crate::core::timestamp::ZERO_DURATION; @@ -944,7 +947,7 @@ mod tests { for test in tests { let p = test.params.clone(); - let cs_result = TmClientState::new( + let cs_result = ClientState::new( p.id, p.trust_level, p.trusting_period, @@ -988,7 +991,7 @@ mod tests { struct Test { name: String, height: Height, - setup: Option TmClientState>>, + setup: Option ClientState>>, want_pass: bool, } @@ -1009,7 +1012,7 @@ mod tests { for test in tests { let p = default_params.clone(); - let client_state = TmClientState::new( + let client_state = ClientState::new( p.id, p.trust_level, p.trusting_period, @@ -1041,7 +1044,7 @@ mod tests { #[test] fn tm_client_state_conversions_healthy() { // check client state creation path from a proto type - let tm_client_state_from_raw = TmClientState::new_dummy_from_raw(RawHeight { + let tm_client_state_from_raw = ClientState::new_dummy_from_raw(RawHeight { revision_number: 0, revision_height: 0, }); @@ -1053,7 +1056,7 @@ mod tests { .expect("Never fails") .clone(), ); - let tm_client_state_from_any = TmClientState::try_from(any_from_tm_client_state); + let tm_client_state_from_any = ClientState::try_from(any_from_tm_client_state); assert!(tm_client_state_from_any.is_ok()); assert_eq!( tm_client_state_from_raw.expect("Never fails"), @@ -1062,9 +1065,9 @@ mod tests { // check client state creation path from a tendermint header let tm_header = get_dummy_tendermint_header(); - let tm_client_state_from_header = TmClientState::new_dummy_from_header(tm_header); + let tm_client_state_from_header = ClientState::new_dummy_from_header(tm_header); let any_from_header = Any::from(tm_client_state_from_header.clone()); - let tm_client_state_from_any = TmClientState::try_from(any_from_header); + let tm_client_state_from_any = ClientState::try_from(any_from_header); assert!(tm_client_state_from_any.is_ok()); assert_eq!( tm_client_state_from_header, @@ -1074,7 +1077,7 @@ mod tests { #[test] fn tm_client_state_malformed_with_frozen_height() { - let tm_client_state_from_raw = TmClientState::new_dummy_from_raw(RawHeight { + let tm_client_state_from_raw = ClientState::new_dummy_from_raw(RawHeight { revision_number: 0, revision_height: 10, }); @@ -1123,11 +1126,11 @@ pub mod test_util { impl ClientState { pub fn new_dummy_from_raw(frozen_height: RawHeight) -> Result { - ClientState::try_from(get_dummy_raw_tm_client_state(frozen_height)) + Self::try_from(get_dummy_raw_tm_client_state(frozen_height)) } - pub fn new_dummy_from_header(tm_header: Header) -> ClientState { - ClientState::new( + pub fn new_dummy_from_header(tm_header: Header) -> Self { + Self::new( tm_header.chain_id.to_string().into(), Default::default(), Duration::from_secs(64000), diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state/misbehaviour.rs b/crates/ibc/src/clients/ics07_tendermint/client_state/misbehaviour.rs index 074ef260a..5dc2aa02e 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state/misbehaviour.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state/misbehaviour.rs @@ -1,28 +1,32 @@ use tendermint_light_client_verifier::Verifier; -use crate::core::ics02_client::consensus_state::ConsensusState; use crate::prelude::*; use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::clients::ics07_tendermint::error::{Error, IntoResult}; use crate::clients::ics07_tendermint::header::Header as TmHeader; use crate::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; +use crate::clients::ics07_tendermint::ValidationContext as TmValidationContext; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; +use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::path::ClientConsensusStatePath; use crate::core::timestamp::Timestamp; -use crate::core::{ics24_host::identifier::ClientId, ValidationContext}; -use super::{check_header_trusted_next_validator_set, downcast_tm_consensus_state, ClientState}; +use super::{check_header_trusted_next_validator_set, ClientState}; impl ClientState { // verify_misbehaviour determines whether or not two conflicting headers at // the same height would have convinced the light client. - pub fn verify_misbehaviour( + pub fn verify_misbehaviour( &self, - ctx: &dyn ValidationContext, + ctx: &ClientValidationContext, client_id: &ClientId, misbehaviour: TmMisbehaviour, - ) -> Result<(), ClientError> { + ) -> Result<(), ClientError> + where + ClientValidationContext: TmValidationContext, + { misbehaviour.validate_basic()?; let header_1 = misbehaviour.header1(); @@ -31,8 +35,12 @@ impl ClientState { ClientConsensusStatePath::new(client_id, &header_1.trusted_height); let consensus_state = ctx.consensus_state(&consensus_state_path)?; - downcast_tm_consensus_state(consensus_state.as_ref()) - }?; + consensus_state + .try_into() + .map_err(|err| ClientError::Other { + description: err.to_string(), + })? + }; let header_2 = misbehaviour.header2(); let trusted_consensus_state_2 = { @@ -40,8 +48,12 @@ impl ClientState { ClientConsensusStatePath::new(client_id, &header_2.trusted_height); let consensus_state = ctx.consensus_state(&consensus_state_path)?; - downcast_tm_consensus_state(consensus_state.as_ref()) - }?; + consensus_state + .try_into() + .map_err(|err| ClientError::Other { + description: err.to_string(), + })? + }; let current_timestamp = ctx.host_timestamp()?; self.verify_misbehaviour_header(header_1, &trusted_consensus_state_1, current_timestamp)?; diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs b/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs index 0fe3880a4..86050de97 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state/update_client.rs @@ -6,20 +6,23 @@ use tendermint_light_client_verifier::Verifier; use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::clients::ics07_tendermint::error::{Error, IntoResult}; use crate::clients::ics07_tendermint::header::Header as TmHeader; -use crate::core::ics02_client::client_state::ClientState as Ics2ClientState; +use crate::clients::ics07_tendermint::ValidationContext as TmValidationContext; use crate::core::ics02_client::error::ClientError; +use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::path::ClientConsensusStatePath; -use crate::core::{ics24_host::identifier::ClientId, ValidationContext}; -use super::{check_header_trusted_next_validator_set, downcast_tm_consensus_state, ClientState}; +use super::{check_header_trusted_next_validator_set, ClientState}; impl ClientState { - pub fn verify_header( + pub fn verify_header( &self, - ctx: &dyn ValidationContext, + ctx: &ClientValidationContext, client_id: &ClientId, header: TmHeader, - ) -> Result<(), ClientError> { + ) -> Result<(), ClientError> + where + ClientValidationContext: TmValidationContext, + { // Checks that the header fields are valid. header.validate_basic()?; @@ -34,10 +37,12 @@ impl ClientState { { let trusted_client_cons_state_path = ClientConsensusStatePath::new(client_id, &header.trusted_height); - let trusted_consensus_state = downcast_tm_consensus_state( - ctx.consensus_state(&trusted_client_cons_state_path)? - .as_ref(), - )?; + let trusted_consensus_state: TmConsensusState = ctx + .consensus_state(&trusted_client_cons_state_path)? + .try_into() + .map_err(|err| ClientError::Other { + description: err.to_string(), + })?; check_header_trusted_next_validator_set(&header, &trusted_consensus_state)?; @@ -86,12 +91,15 @@ impl ClientState { Ok(()) } - pub fn check_for_misbehaviour_update_client( + pub fn check_for_misbehaviour_update_client( &self, - ctx: &dyn ValidationContext, + ctx: &ClientValidationContext, client_id: &ClientId, header: TmHeader, - ) -> Result { + ) -> Result + where + ClientValidationContext: TmValidationContext, + { let header_consensus_state = TmConsensusState::from(header.clone()); let maybe_existing_consensus_state = { @@ -102,8 +110,11 @@ impl ClientState { match maybe_existing_consensus_state { Some(existing_consensus_state) => { - let existing_consensus_state = - downcast_tm_consensus_state(existing_consensus_state.as_ref())?; + let existing_consensus_state: TmConsensusState = existing_consensus_state + .try_into() + .map_err(|err| ClientError::Other { + description: err.to_string(), + })?; // There is evidence of misbehaviour if the stored consensus state // is different from the new one we received. @@ -120,7 +131,10 @@ impl ClientState { if let Some(prev_cs) = maybe_prev_cs { // New header timestamp cannot occur *before* the // previous consensus state's height - let prev_cs = downcast_tm_consensus_state(prev_cs.as_ref())?; + let prev_cs: TmConsensusState = + prev_cs.try_into().map_err(|err| ClientError::Other { + description: err.to_string(), + })?; if header.signed_header.header().time <= prev_cs.timestamp { return Ok(true); @@ -130,13 +144,16 @@ impl ClientState { // 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() < self.latest_height() { + if header.height() < self.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 = downcast_tm_consensus_state(next_cs.as_ref())?; + let next_cs: TmConsensusState = + next_cs.try_into().map_err(|err| ClientError::Other { + description: err.to_string(), + })?; if header.signed_header.header().time >= next_cs.timestamp { return Ok(true); diff --git a/crates/ibc/src/clients/ics07_tendermint/consensus_state.rs b/crates/ibc/src/clients/ics07_tendermint/consensus_state.rs index 20384923c..ae528b008 100644 --- a/crates/ibc/src/clients/ics07_tendermint/consensus_state.rs +++ b/crates/ibc/src/clients/ics07_tendermint/consensus_state.rs @@ -4,17 +4,20 @@ use crate::prelude::*; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::lightclients::tendermint::v1::ConsensusState as RawConsensusState; -use ibc_proto::protobuf::Protobuf; use tendermint::{hash::Algorithm, time::Time, Hash}; use tendermint_proto::google::protobuf as tpb; +use tendermint_proto::Error as TmProtoError; +use tendermint_proto::Protobuf; use crate::clients::ics07_tendermint::error::Error; use crate::clients::ics07_tendermint::header::Header; +use crate::core::ics02_client::consensus_state::ConsensusState as ConsensusStateTrait; use crate::core::ics02_client::error::ClientError; use crate::core::ics23_commitment::commitment::CommitmentRoot; use crate::core::timestamp::Timestamp; -const TENDERMINT_CONSENSUS_STATE_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.ConsensusState"; +pub const TENDERMINT_CONSENSUS_STATE_TYPE_URL: &str = + "/ibc.lightclients.tendermint.v1.ConsensusState"; /// Defines the Tendermint light client's consensus state #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -35,16 +38,6 @@ impl ConsensusState { } } -impl crate::core::ics02_client::consensus_state::ConsensusState for ConsensusState { - fn root(&self) -> &CommitmentRoot { - &self.root - } - - fn timestamp(&self) -> Timestamp { - self.timestamp.into() - } -} - impl Protobuf for ConsensusState {} impl TryFrom for ConsensusState { @@ -132,7 +125,8 @@ impl From for Any { fn from(consensus_state: ConsensusState) -> Self { Any { type_url: TENDERMINT_CONSENSUS_STATE_TYPE_URL.to_string(), - value: Protobuf::::encode_vec(&consensus_state), + value: Protobuf::::encode_vec(&consensus_state) + .expect("Out of memory"), } } } @@ -153,6 +147,20 @@ impl From
for ConsensusState { } } +impl ConsensusStateTrait for ConsensusState { + fn root(&self) -> &CommitmentRoot { + &self.root + } + + fn timestamp(&self) -> Timestamp { + self.timestamp.into() + } + + fn encode_vec(&self) -> Result, TmProtoError> { + >::encode_vec(self) + } +} + #[cfg(test)] #[cfg(feature = "serde")] mod tests { diff --git a/crates/ibc/src/clients/ics07_tendermint/context.rs b/crates/ibc/src/clients/ics07_tendermint/context.rs new file mode 100644 index 000000000..3c3674389 --- /dev/null +++ b/crates/ibc/src/clients/ics07_tendermint/context.rs @@ -0,0 +1,56 @@ +use alloc::string::ToString; + +use crate::{ + core::{ + ics02_client::ClientExecutionContext, + ics24_host::{identifier::ClientId, path::ClientConsensusStatePath}, + timestamp::Timestamp, + ContextError, + }, + Height, +}; + +use super::consensus_state::ConsensusState as TmConsensusState; + +/// Client's context required during both validation and execution +pub trait CommonContext { + type ConversionError: ToString; + type AnyConsensusState: TryInto; + + /// 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; +} + +/// Client's context required during validation +pub trait ValidationContext: CommonContext { + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Result; + + /// Search for the lowest consensus state higher than `height`. + fn next_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError>; + + /// Search for the highest consensus state lower than `height`. + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> 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 {} + +impl ExecutionContext for T where T: CommonContext + ClientExecutionContext {} diff --git a/crates/ibc/src/clients/ics07_tendermint/header.rs b/crates/ibc/src/clients/ics07_tendermint/header.rs index ab82de24c..8fd5c3e81 100644 --- a/crates/ibc/src/clients/ics07_tendermint/header.rs +++ b/crates/ibc/src/clients/ics07_tendermint/header.rs @@ -14,7 +14,7 @@ use tendermint::chain::Id as TmChainId; use tendermint::validator::Set as ValidatorSet; use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState}; -use crate::clients::ics07_tendermint::consensus_state::ConsensusState; +use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::clients::ics07_tendermint::error::Error; use crate::core::ics02_client::error::ClientError; use crate::core::ics24_host::identifier::ChainId; @@ -70,7 +70,7 @@ impl Header { pub(crate) fn as_trusted_block_state<'a>( &'a self, - consensus_state: &ConsensusState, + consensus_state: &TmConsensusState, chain_id: &'a TmChainId, ) -> Result, Error> { Ok(TrustedBlockState { diff --git a/crates/ibc/src/clients/ics07_tendermint/mod.rs b/crates/ibc/src/clients/ics07_tendermint/mod.rs index 2dc390e56..2bb58ec4b 100644 --- a/crates/ibc/src/clients/ics07_tendermint/mod.rs +++ b/crates/ibc/src/clients/ics07_tendermint/mod.rs @@ -11,9 +11,23 @@ pub mod header; pub mod misbehaviour; pub mod trust_threshold; +mod context; +pub use context::*; + pub(crate) const TENDERMINT_CLIENT_TYPE: &str = "07-tendermint"; /// Returns the tendermint `ClientType` pub fn client_type() -> ClientType { ClientType::from_str(TENDERMINT_CLIENT_TYPE).expect("Never fails because it's valid") } + +#[cfg(test)] +mod tests { + use super::*; + + // Ensures that the validation in `ClientType::from_str` doesn't fail for the tendermint client type + #[test] + pub fn test_tm_client_type() { + let _ = ClientType::from_str(TENDERMINT_CLIENT_TYPE).unwrap(); + } +} diff --git a/crates/ibc/src/core/context.rs b/crates/ibc/src/core/context.rs index 3cd2ff80e..c1409449f 100644 --- a/crates/ibc/src/core/context.rs +++ b/crates/ibc/src/core/context.rs @@ -3,12 +3,11 @@ use crate::prelude::*; use crate::signer::Signer; use alloc::string::String; use core::time::Duration; +use derive_more::From; use displaydoc::Display; use ibc_proto::google::protobuf::Any; use crate::core::events::IbcEvent; -use crate::core::ics02_client::client_state::ClientState; -use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; use crate::core::ics03_connection::connection::ConnectionEnd; use crate::core::ics03_connection::error::ConnectionError; @@ -25,15 +24,19 @@ use crate::core::ics23_commitment::commitment::CommitmentPrefix; use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::identifier::ConnectionId; use crate::core::ics24_host::path::{ - AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, CommitmentPath, + ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::core::router::Router; use crate::core::timestamp::Timestamp; use crate::Height; +use super::ics02_client::client_state::ClientState; +use super::ics02_client::consensus_state::ConsensusState; +use super::ics02_client::ClientExecutionContext; + /// Top-level error -#[derive(Debug, Display)] +#[derive(Debug, Display, From)] pub enum ContextError { /// ICS02 Client error: {0} ClientError(ClientError), @@ -45,30 +48,6 @@ pub enum ContextError { PacketError(PacketError), } -impl From for ContextError { - fn from(err: ClientError) -> ContextError { - Self::ClientError(err) - } -} - -impl From for ContextError { - fn from(err: ConnectionError) -> ContextError { - Self::ConnectionError(err) - } -} - -impl From for ContextError { - fn from(err: ChannelError) -> ContextError { - Self::ChannelError(err) - } -} - -impl From for ContextError { - fn from(err: PacketError) -> ContextError { - Self::PacketError(err) - } -} - #[cfg(feature = "std")] impl std::error::Error for ContextError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { @@ -114,34 +93,46 @@ impl std::error::Error for RouterError { /// /// Trait used for the top-level [`validate`](crate::core::validate) pub trait ValidationContext: Router { + type 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::ClientValidationContext; + /// Returns the ClientState for the given identifier `client_id`. - fn client_state(&self, client_id: &ClientId) -> Result, ContextError>; + /// + /// 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, ContextError>; + 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, ContextError>; + ) -> Result; - /// Search for the lowest consensus state higher than `height`. - fn next_consensus_state( + /// Returns the time when the client state for the given [`ClientId`] was updated with a header for the given [`Height`] + fn client_update_time( &self, client_id: &ClientId, height: &Height, - ) -> Result>, ContextError>; + ) -> Result; - /// Search for the highest consensus state lower than `height`. - fn prev_consensus_state( + /// Returns the height when the client state for the given [`ClientId`] was updated with a header for the given [`Height`] + fn client_update_height( &self, client_id: &ClientId, height: &Height, - ) -> Result>, ContextError>; + ) -> Result; /// Returns the current height of the local chain. fn host_height(&self) -> Result; @@ -153,7 +144,7 @@ pub trait ValidationContext: Router { fn host_consensus_state( &self, height: &Height, - ) -> Result, ContextError>; + ) -> Result; /// Returns a natural number, counting how many clients have been created /// thus far. The value of this counter should increase only via method @@ -230,20 +221,6 @@ pub trait ValidationContext: Router { ack_path: &AckPath, ) -> Result; - /// Returns the time when the client state for the given [`ClientId`] was updated with a header for the given [`Height`] - fn client_update_time( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result; - - /// Returns the height when the client state for the given [`ClientId`] was updated with a header for the given [`Height`] - fn client_update_height( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result; - /// Returns a counter on the number of channel ids have been created thus far. /// The value of this counter should increase only via method /// `ExecutionContext::increase_channel_counter`. @@ -267,19 +244,8 @@ pub trait ValidationContext: Router { /// /// Trait used for the top-level [`execute`](crate::core::execute) and [`dispatch`](crate::core::dispatch) pub trait ExecutionContext: ValidationContext { - /// Called upon successful client creation and update - fn store_client_state( - &mut self, - client_state_path: ClientStatePath, - client_state: Box, - ) -> Result<(), ContextError>; - - /// Called upon successful client creation and update - fn store_consensus_state( - &mut self, - consensus_state_path: ClientConsensusStatePath, - consensus_state: Box, - ) -> Result<(), ContextError>; + /// Retrieve the context that implements all clients' `ExecutionContext`. + fn get_client_execution_context(&mut self) -> &mut Self::E; /// Called upon client creation. /// Increases the counter which keeps track of how many clients have been created. diff --git a/crates/ibc/src/core/handler.rs b/crates/ibc/src/core/handler.rs index b170ec98e..8d359650b 100644 --- a/crates/ibc/src/core/handler.rs +++ b/crates/ibc/src/core/handler.rs @@ -27,8 +27,8 @@ use super::ics04_channel::handler::timeout::{ timeout_packet_execute, timeout_packet_validate, TimeoutMsgType, }; use super::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use super::ContextError; -use super::{msgs::MsgEnvelope, ExecutionContext, ValidationContext}; +use super::msgs::MsgEnvelope; +use super::{ContextError, ExecutionContext, ValidationContext}; /// Entrypoint which performs both validation and message execution pub fn dispatch(ctx: &mut impl ExecutionContext, msg: MsgEnvelope) -> Result<(), RouterError> { diff --git a/crates/ibc/src/core/ics02_client/client_state.rs b/crates/ibc/src/core/ics02_client/client_state.rs index b34a8a9a2..e895ed8ac 100644 --- a/crates/ibc/src/core/ics02_client/client_state.rs +++ b/crates/ibc/src/core/ics02_client/client_state.rs @@ -1,40 +1,47 @@ //! Defines `ClientState`, the core type to be implemented by light clients +use core::fmt::Debug; use core::marker::{Send, Sync}; use core::time::Duration; -use dyn_clone::DynClone; use ibc_proto::google::protobuf::Any; -use ibc_proto::protobuf::Protobuf as ErasedProtobuf; -use crate::clients::AsAny; use crate::core::ics02_client::client_type::ClientType; use crate::core::ics02_client::error::ClientError; +use crate::core::ics02_client::ClientExecutionContext; use crate::core::ics23_commitment::commitment::{ CommitmentPrefix, CommitmentProofBytes, CommitmentRoot, }; use crate::core::ics23_commitment::merkle::MerkleProof; use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::path::Path; -use crate::erased::ErasedSerialize; use crate::prelude::*; use crate::Height; -use super::consensus_state::ConsensusState; +/// `UpdateKind` represents the 2 ways that a client can be updated +/// in IBC: either through a `MsgUpdateClient`, or a `MsgSubmitMisbehaviour`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum UpdateKind { + /// this is the typical scenario where a new header is submitted to the client + /// to update the client. Note that light clients are free to define the type + /// of the object used to update them (e.g. could be a list of headers). + UpdateClient, + /// this is the scenario where misbehaviour is submitted to the client + /// (e.g 2 headers with the same height in Tendermint) + SubmitMisbehaviour, +} -use crate::core::{ExecutionContext, ValidationContext}; +/// `ClientState` methods needed in both validation and execution. +/// +/// They do not require access to a client `ValidationContext` nor +/// `ExecutionContext`. +pub trait ClientStateCommon { + /// Performs basic validation on the `consensus_state`. + /// + /// Notably, an implementation should verify that it can properly + /// deserialize the object into the expected format. + fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError>; -/// The core light client type. -pub trait ClientState: - AsAny - + sealed::ErasedPartialEqClientState - + DynClone - + ErasedSerialize - + ErasedProtobuf - + core::fmt::Debug - + Send - + Sync -{ /// Type of client associated with this state (eg. Tendermint) fn client_type(&self) -> ClientType; @@ -51,16 +58,66 @@ pub trait ClientState: /// state timestamp fn expired(&self, elapsed: Duration) -> bool; - /// Convert into a boxed trait object - fn into_box(self) -> Box - where - Self: Sized, - { - Box::new(self) - } + /// Verify the upgraded client and consensus states and validate proofs + /// against the given root. + /// + /// NOTE: proof heights are not included as upgrade to a new revision is + /// expected to pass only on the last height committed by the current + /// revision. Clients are responsible for ensuring that the planned last + /// height of the current revision is somehow encoded in the proof + /// verification process. This is to ensure that no premature upgrades + /// occur, since upgrade plans committed to by the counterparty may be + /// cancelled or modified before the last planned height. + fn verify_upgrade_client( + &self, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + proof_upgrade_client: MerkleProof, + proof_upgrade_consensus_state: MerkleProof, + root: &CommitmentRoot, + ) -> Result<(), ClientError>; + + // Verify_membership is a generic proof verification method which verifies a + // proof of the existence of a value at a given Path. + fn verify_membership( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: Path, + value: Vec, + ) -> Result<(), ClientError>; - fn initialise(&self, consensus_state: Any) -> Result, ClientError>; + // Verify_non_membership is a generic proof verification method which + // verifies the absence of a given commitment. + fn verify_non_membership( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: Path, + ) -> Result<(), ClientError>; +} +/// `ClientState` methods which require access to the client's +/// `ValidationContext`. +/// +/// The `ClientValidationContext` enables the light client implementation to +/// define its own `ValidationContext` trait and use it in its implementation. +/// +/// ```ignore +/// impl ClientStateValidation for MyClientState +/// where +/// ClientValidationContext: MyValidationContext, +/// { +/// // `MyValidationContext` methods available +/// } +/// +/// trait MyValidationContext { +/// // My Context methods +/// } +/// ``` +pub trait ClientStateValidation { /// verify_client_message must verify a client_message. A client_message /// could be a Header, Misbehaviour. It must handle each type of /// client_message appropriately. Calls to check_for_misbehaviour, @@ -69,7 +126,7 @@ pub trait ClientState: /// error should be returned if the client_message fails to verify. fn verify_client_message( &self, - ctx: &dyn ValidationContext, + ctx: &ClientValidationContext, client_id: &ClientId, client_message: Any, update_kind: &UpdateKind, @@ -79,11 +136,35 @@ pub trait ClientState: /// assumes the client_message has already been verified. fn check_for_misbehaviour( &self, - ctx: &dyn ValidationContext, + ctx: &ClientValidationContext, client_id: &ClientId, client_message: Any, update_kind: &UpdateKind, ) -> Result; +} + +/// `ClientState` methods which require access to the client's +/// `ExecutionContext`. +/// +/// A client can define its own `ExecutionContext` in a manner analogous to how +/// it can define a `ValidationContext` in [`ClientStateValidation`]. The one +/// difference is every client's `ExecutionContext` must have +/// [`ClientExecutionContext`] as a supertrait, which provides a set of common +/// methods to store a client state and consensus state. +pub trait ClientStateExecution +where + E: ClientExecutionContext, +{ + /// Initialises the client with the initial client and consensus states. + /// + /// Most clients will want to call `E::store_client_state` and + /// `E::store_consensus_state`. + fn initialise( + &self, + ctx: &mut E, + client_id: &ClientId, + consensus_state: Any, + ) -> Result<(), ClientError>; /// Updates and stores as necessary any associated information for an IBC /// client, such as the ClientState and corresponding ConsensusState. Upon @@ -96,7 +177,7 @@ pub trait ClientState: /// height. fn update_state( &self, - ctx: &mut dyn ExecutionContext, + ctx: &mut E, client_id: &ClientId, header: Any, ) -> Result, ClientError>; @@ -105,115 +186,59 @@ pub trait ClientState: /// a client state given that misbehaviour has been detected and verified fn update_state_on_misbehaviour( &self, - ctx: &mut dyn ExecutionContext, + ctx: &mut E, client_id: &ClientId, client_message: Any, update_kind: &UpdateKind, ) -> Result<(), ClientError>; - /// Verify the upgraded client and consensus states and validate proofs - /// against the given root. - /// - /// NOTE: proof heights are not included as upgrade to a new revision is - /// expected to pass only on the last height committed by the current - /// revision. Clients are responsible for ensuring that the planned last - /// height of the current revision is somehow encoded in the proof - /// verification process. This is to ensure that no premature upgrades - /// occur, since upgrade plans committed to by the counterparty may be - /// cancelled or modified before the last planned height. - fn verify_upgrade_client( - &self, - upgraded_client_state: Any, - upgraded_consensus_state: Any, - proof_upgrade_client: MerkleProof, - proof_upgrade_consensus_state: MerkleProof, - root: &CommitmentRoot, - ) -> Result<(), ClientError>; - // Update the client state and consensus state in the store with the upgraded ones. fn update_state_with_upgrade_client( &self, + ctx: &mut E, + client_id: &ClientId, upgraded_client_state: Any, upgraded_consensus_state: Any, - ) -> Result; - - // Verify_membership is a generic proof verification method which verifies a - // proof of the existence of a value at a given Path. - fn verify_membership( - &self, - prefix: &CommitmentPrefix, - proof: &CommitmentProofBytes, - root: &CommitmentRoot, - path: Path, - value: Vec, - ) -> Result<(), ClientError>; - - // Verify_non_membership is a generic proof verification method which - // verifies the absence of a given commitment. - fn verify_non_membership( - &self, - prefix: &CommitmentPrefix, - proof: &CommitmentProofBytes, - root: &CommitmentRoot, - path: Path, - ) -> Result<(), ClientError>; -} - -// Implements `Clone` for `Box` -dyn_clone::clone_trait_object!(ClientState); - -// Implements `serde::Serialize` for all types that have ClientState as supertrait -#[cfg(feature = "serde")] -erased_serde::serialize_trait_object!(ClientState); - -impl PartialEq for dyn ClientState { - fn eq(&self, other: &Self) -> bool { - self.eq_client_state(other) - } -} - -// see https://github.com/rust-lang/rust/issues/31740 -impl PartialEq<&Self> for Box { - fn eq(&self, other: &&Self) -> bool { - self.eq_client_state(other.as_ref()) - } + ) -> Result; } -/// `UpdateKind` represents the 2 ways that a client can be updated -/// in IBC: either through a `MsgUpdateClient`, or a `MsgSubmitMisbehaviour`. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum UpdateKind { - /// this is the typical scenario where a new header is submitted to the client - /// to update the client. Note that light clients are free to define the type - /// of the object used to update them (e.g. could be a list of headers). - UpdateClient, - /// this is the scenario where misbehaviour is submitted to the client - /// (e.g 2 headers with the same height in Tendermint) - SubmitMisbehaviour, -} - -/// Represents the updated client and consensus states after a client upgrade -pub struct UpdatedState { - pub client_state: Box, - pub consensus_state: Box, +/// Derive macro that implements [`ClientState`] for enums containing variants +/// that implement [`ClientState`]. +/// +/// The macro expects the attribute `#[generics(ClientValidationContext = <...>, +/// ClientExecutionContext = <...>)]` which specifies [`ClientState`]'s generic +/// arguments to be defined. +/// +/// The macro does not support generic types for `ClientValidationContext` and +/// `ClientExecutionContext` (e.g. `MyType` would not be supported). +pub use ibc_derive::ClientState; + +/// Primary client trait. Defines all the methods that clients must implement. +/// +/// `ClientState` is broken up in 3 separate traits to avoid needing to use +/// fully qualified syntax for every method call (see ADR 7 for more details). +/// One only needs to implement [`ClientStateCommon`], [`ClientStateValidation`] +/// and [`ClientStateExecution`]; a blanket implementation will automatically +/// implement `ClientState`. +/// +/// Refer to [`ClientStateValidation`] and [`ClientStateExecution`] to learn +/// more about what both generic parameters represent. +pub trait ClientState: + Send + + Sync + + ClientStateCommon + + ClientStateValidation + + ClientStateExecution +{ } -mod sealed { - use super::*; - - pub trait ErasedPartialEqClientState { - fn eq_client_state(&self, other: &dyn ClientState) -> bool; - } - - impl ErasedPartialEqClientState for CS - where - CS: ClientState + PartialEq, - { - fn eq_client_state(&self, other: &dyn ClientState) -> bool { - other - .as_any() - .downcast_ref::() - .map_or(false, |h| self == h) - } - } +impl ClientState + for T +where + T: Send + + Sync + + ClientStateCommon + + ClientStateValidation + + ClientStateExecution, +{ } diff --git a/crates/ibc/src/core/ics02_client/consensus_state.rs b/crates/ibc/src/core/ics02_client/consensus_state.rs index 0920bc8a4..228525dab 100644 --- a/crates/ibc/src/core/ics02_client/consensus_state.rs +++ b/crates/ibc/src/core/ics02_client/consensus_state.rs @@ -1,87 +1,32 @@ //! Defines the trait to be implemented by all concrete consensus state types -use crate::clients::AsAny; use crate::prelude::*; use core::marker::{Send, Sync}; -use dyn_clone::DynClone; -use ibc_proto::google::protobuf::Any; -use ibc_proto::protobuf::Protobuf as ErasedProtobuf; - -use crate::core::ics02_client::error::ClientError; use crate::core::ics23_commitment::commitment::CommitmentRoot; use crate::core::timestamp::Timestamp; -use crate::erased::ErasedSerialize; -/// Abstract of consensus state information used by the validity predicate -/// to verify new commits & state roots. +/// Derive macro that implements [`ConsensusState`] for enums containing +/// variants that implement [`ConsensusState`] +pub use ibc_derive::ConsensusState; + +/// Defines methods that all `ConsensusState`s should provide. /// -/// Users are not expected to implement sealed::ErasedPartialEqConsensusState. -/// Effectively, that trait bound mandates implementors to derive PartialEq, -/// after which our blanket implementation will implement -/// `ErasedPartialEqConsensusState` for their type. -pub trait ConsensusState: - AsAny - + sealed::ErasedPartialEqConsensusState - + DynClone - + ErasedSerialize - + ErasedProtobuf - + core::fmt::Debug - + Send - + Sync -{ +/// One can think of a "consensus state" as a pruned header, to be stored on chain. In other words, +/// a consensus state only contains the header's information needed by IBC message handlers. +pub trait ConsensusState: Send + Sync { /// Commitment root of the consensus state, which is used for key-value pair verification. fn root(&self) -> &CommitmentRoot; /// The timestamp of the consensus state fn timestamp(&self) -> Timestamp; - /// Convert into a boxed trait object - fn into_box(self) -> Box - where - Self: Sized, - { - Box::new(self) - } -} - -// Implements `Clone` for `Box` -dyn_clone::clone_trait_object!(ConsensusState); - -// Implements `serde::Serialize` for all types that have ConsensusState as supertrait -#[cfg(feature = "serde")] -erased_serde::serialize_trait_object!(ConsensusState); - -impl PartialEq for dyn ConsensusState { - fn eq(&self, other: &Self) -> bool { - self.eq_consensus_state(other) - } -} - -// see https://github.com/rust-lang/rust/issues/31740 -impl PartialEq<&Self> for Box { - fn eq(&self, other: &&Self) -> bool { - self.eq_consensus_state(other.as_ref()) - } -} - -mod sealed { - use super::*; - - pub trait ErasedPartialEqConsensusState { - fn eq_consensus_state(&self, other: &dyn ConsensusState) -> bool; - } - - impl ErasedPartialEqConsensusState for CS - where - CS: ConsensusState + PartialEq, - { - fn eq_consensus_state(&self, other: &dyn ConsensusState) -> bool { - other - .as_any() - .downcast_ref::() - .map_or(false, |h| self == h) - } - } + /// Serializes the `ConsensusState`. This is expected to be implemented as + /// first converting to the raw type (i.e. the protobuf definition), and then + /// serializing that. + /// + /// Note that the `Protobuf` trait in `tendermint-proto` provides convenience methods + /// to do this automatically. + fn encode_vec(&self) -> Result, tendermint_proto::Error>; } diff --git a/crates/ibc/src/core/ics02_client/context.rs b/crates/ibc/src/core/ics02_client/context.rs new file mode 100644 index 000000000..4423d4690 --- /dev/null +++ b/crates/ibc/src/core/ics02_client/context.rs @@ -0,0 +1,33 @@ +use super::client_state::ClientState; +use super::consensus_state::ConsensusState; +use crate::core::ics24_host::path::ClientConsensusStatePath; +use crate::core::ics24_host::path::ClientStatePath; +use crate::core::ContextError; + +/// Defines the methods that all client `ExecutionContext`s (precisely the +/// generic parameter of +/// [`crate::core::ics02_client::client_state::ClientStateExecution`] ) must +/// implement. +/// +/// 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 ClientValidationContext; + type AnyClientState: ClientState; + type AnyConsensusState: ConsensusState; + + /// Called upon successful client creation and update + fn store_client_state( + &mut self, + client_state_path: ClientStatePath, + client_state: Self::AnyClientState, + ) -> Result<(), ContextError>; + + /// Called upon successful client creation and update + fn store_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + consensus_state: Self::AnyConsensusState, + ) -> Result<(), ContextError>; +} diff --git a/crates/ibc/src/core/ics02_client/handler/create_client.rs b/crates/ibc/src/core/ics02_client/handler/create_client.rs index f786081c2..e3ee280ab 100644 --- a/crates/ibc/src/core/ics02_client/handler/create_client.rs +++ b/crates/ibc/src/core/ics02_client/handler/create_client.rs @@ -1,23 +1,18 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgCreateClient`. -use crate::core::events::MessageEvent; use crate::prelude::*; use crate::core::context::ContextError; - -use crate::core::ics24_host::path::ClientConsensusStatePath; - -use crate::core::ics24_host::path::ClientStatePath; - -use crate::core::ExecutionContext; - -use crate::core::ValidationContext; - use crate::core::events::IbcEvent; +use crate::core::events::MessageEvent; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::client_state::ClientStateExecution; use crate::core::ics02_client::error::ClientError; use crate::core::ics02_client::events::CreateClient; use crate::core::ics02_client::msgs::create_client::MsgCreateClient; use crate::core::ics24_host::identifier::ClientId; +use crate::core::ExecutionContext; +use crate::core::ValidationContext; pub(crate) fn validate(ctx: &Ctx, msg: MsgCreateClient) -> Result<(), ContextError> where @@ -36,7 +31,7 @@ where let client_state = ctx.decode_client_state(client_state)?; - client_state.initialise(consensus_state)?; + client_state.verify_consensus_state(consensus_state)?; let client_type = client_state.client_type(); @@ -79,29 +74,23 @@ where validation_error: e, }) })?; - let consensus_state = client_state.initialise(consensus_state)?; - ctx.store_client_state(ClientStatePath::new(&client_id), client_state.clone())?; - ctx.store_consensus_state( - ClientConsensusStatePath::new(&client_id, &client_state.latest_height()), + client_state.initialise( + ctx.get_client_execution_context(), + &client_id, consensus_state, )?; + + let latest_height = client_state.latest_height(); + + ctx.store_update_time(client_id.clone(), latest_height, ctx.host_timestamp()?)?; + ctx.store_update_height(client_id.clone(), latest_height, ctx.host_height()?)?; ctx.increase_client_counter(); - ctx.store_update_time( - client_id.clone(), - client_state.latest_height(), - ctx.host_timestamp()?, - )?; - ctx.store_update_height( - client_id.clone(), - client_state.latest_height(), - ctx.host_height()?, - )?; let event = IbcEvent::CreateClient(CreateClient::new( client_id.clone(), client_type, - client_state.latest_height(), + latest_height, )); ctx.emit_ibc_event(IbcEvent::Message(MessageEvent::Client)); ctx.emit_ibc_event(event); @@ -126,7 +115,6 @@ mod tests { use crate::core::ics02_client::handler::create_client::{execute, validate}; use crate::core::ics02_client::msgs::create_client::MsgCreateClient; use crate::core::ics24_host::identifier::ClientId; - use crate::core::ValidationContext; use crate::mock::client_state::{client_type as mock_client_type, MockClientState}; use crate::mock::consensus_state::MockConsensusState; use crate::mock::context::MockContext; diff --git a/crates/ibc/src/core/ics02_client/handler/update_client.rs b/crates/ibc/src/core/ics02_client/handler/update_client.rs index c44df4e2a..4b88d77cc 100644 --- a/crates/ibc/src/core/ics02_client/handler/update_client.rs +++ b/crates/ibc/src/core/ics02_client/handler/update_client.rs @@ -1,15 +1,16 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgUpdateAnyClient`. -use crate::core::ics02_client::client_state::UpdateKind; -use crate::core::ics02_client::error::ClientError; -use crate::core::ics02_client::msgs::MsgUpdateOrMisbehaviour; use crate::prelude::*; +use crate::core::context::ContextError; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::client_state::ClientStateExecution; +use crate::core::ics02_client::client_state::ClientStateValidation; +use crate::core::ics02_client::client_state::UpdateKind; +use crate::core::ics02_client::error::ClientError; use crate::core::ics02_client::events::{ClientMisbehaviour, UpdateClient}; - -use crate::core::context::ContextError; - +use crate::core::ics02_client::msgs::MsgUpdateOrMisbehaviour; use crate::core::{ExecutionContext, ValidationContext}; pub(crate) fn validate(ctx: &Ctx, msg: MsgUpdateOrMisbehaviour) -> Result<(), ContextError> @@ -31,7 +32,12 @@ where let client_message = msg.client_message(); - client_state.verify_client_message(ctx, &client_id, client_message, &update_kind)?; + client_state.verify_client_message( + ctx.get_client_validation_context(), + &client_id, + client_message, + &update_kind, + )?; Ok(()) } @@ -50,17 +56,22 @@ where let client_state = ctx.client_state(&client_id)?; let found_misbehaviour = client_state.check_for_misbehaviour( - ctx, + ctx.get_client_validation_context(), &client_id, client_message.clone(), &update_kind, )?; if found_misbehaviour { - client_state.update_state_on_misbehaviour(ctx, &client_id, client_message, &update_kind)?; + client_state.update_state_on_misbehaviour( + ctx.get_client_execution_context(), + &client_id, + client_message, + &update_kind, + )?; let event = IbcEvent::ClientMisbehaviour(ClientMisbehaviour::new( - client_id.clone(), + client_id, client_state.client_type(), )); ctx.emit_ibc_event(IbcEvent::Message(MessageEvent::Client)); @@ -75,21 +86,40 @@ where let header = client_message; - let consensus_heights = client_state.update_state(ctx, &client_id, header.clone())?; + let consensus_heights = client_state.update_state( + ctx.get_client_execution_context(), + &client_id, + header.clone(), + )?; - let consensus_height = consensus_heights.get(0).ok_or(ClientError::Other { - description: "client update state returned no updated height".to_string(), - })?; + // Store host height and time for all updated headers + { + let host_timestamp = ctx.host_timestamp()?; + let host_height = ctx.host_height()?; - let event = IbcEvent::UpdateClient(UpdateClient::new( - client_id.clone(), - client_state.client_type(), - *consensus_height, - consensus_heights, - header, - )); - ctx.emit_ibc_event(IbcEvent::Message(MessageEvent::Client)); - ctx.emit_ibc_event(event); + for consensus_height in consensus_heights.iter() { + ctx.store_update_time(client_id.clone(), *consensus_height, host_timestamp)?; + ctx.store_update_height(client_id.clone(), *consensus_height, host_height)?; + } + } + + { + let event = { + let consensus_height = consensus_heights.get(0).ok_or(ClientError::Other { + description: "client update state returned no updated height".to_string(), + })?; + + IbcEvent::UpdateClient(UpdateClient::new( + client_id, + client_state.client_type(), + *consensus_height, + consensus_heights, + header, + )) + }; + ctx.emit_ibc_event(IbcEvent::Message(MessageEvent::Client)); + ctx.emit_ibc_event(event); + } } Ok(()) @@ -109,20 +139,17 @@ mod tests { use crate::clients::ics07_tendermint::header::Header as TmHeader; use crate::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; use crate::core::events::IbcEvent; - use crate::core::ics02_client::client_state::ClientState; use crate::core::ics02_client::client_type::ClientType; - use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::handler::update_client::{execute, validate}; use crate::core::ics02_client::msgs::misbehaviour::MsgSubmitMisbehaviour; use crate::core::ics02_client::msgs::update_client::MsgUpdateClient; use crate::core::ics23_commitment::specs::ProofSpecs; use crate::core::ics24_host::identifier::{ChainId, ClientId}; use crate::core::timestamp::Timestamp; - use crate::core::ValidationContext; use crate::downcast; use crate::mock::client_state::client_type as mock_client_type; use crate::mock::client_state::MockClientState; - use crate::mock::context::MockContext; + use crate::mock::context::{AnyConsensusState, MockContext}; use crate::mock::header::MockHeader; use crate::mock::host::{HostBlock, HostType}; use crate::mock::misbehaviour::Misbehaviour as MockMisbehaviour; @@ -154,7 +181,7 @@ mod tests { assert_eq!( ctx.client_state(&msg.client_id).unwrap(), - MockClientState::new(MockHeader::new(height).with_timestamp(timestamp)).into_box() + MockClientState::new(MockHeader::new(height).with_timestamp(timestamp)).into() ); } @@ -322,7 +349,7 @@ mod tests { { // FIXME: idea: we need to update the light client with the latest block from // chain B - let consensus_state: Box = block.clone().into(); + let consensus_state: AnyConsensusState = block.clone().into(); let tm_block = downcast!(block.clone() => HostBlock::SyntheticTendermint).unwrap(); @@ -354,7 +381,7 @@ mod tests { let client_state = TmClientState::try_from(raw_client_state).unwrap(); - client_state.into_box() + client_state.into() }; let mut ibc_store = ctx_a.ibc_store.lock(); diff --git a/crates/ibc/src/core/ics02_client/handler/upgrade_client.rs b/crates/ibc/src/core/ics02_client/handler/upgrade_client.rs index b004685b4..2788a26cb 100644 --- a/crates/ibc/src/core/ics02_client/handler/upgrade_client.rs +++ b/crates/ibc/src/core/ics02_client/handler/upgrade_client.rs @@ -4,12 +4,14 @@ use crate::prelude::*; use crate::core::context::ContextError; use crate::core::events::{IbcEvent, MessageEvent}; -use crate::core::ics02_client::client_state::UpdatedState; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::client_state::ClientStateExecution; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; use crate::core::ics02_client::events::UpgradeClient; use crate::core::ics02_client::msgs::upgrade_client::MsgUpgradeClient; use crate::core::ics23_commitment::merkle::MerkleProof; -use crate::core::ics24_host::path::{ClientConsensusStatePath, ClientStatePath}; +use crate::core::ics24_host::path::ClientConsensusStatePath; use crate::core::{ExecutionContext, ValidationContext}; pub(crate) fn validate(ctx: &Ctx, msg: MsgUpgradeClient) -> Result<(), ContextError> @@ -81,22 +83,17 @@ where let old_client_state = ctx.client_state(&client_id)?; - let UpdatedState { - client_state, - consensus_state, - } = old_client_state - .update_state_with_upgrade_client(msg.client_state.clone(), msg.consensus_state)?; - - ctx.store_client_state(ClientStatePath::new(&client_id), client_state.clone())?; - ctx.store_consensus_state( - ClientConsensusStatePath::new(&client_id, &client_state.latest_height()), - consensus_state, + let latest_height = old_client_state.update_state_with_upgrade_client( + ctx.get_client_execution_context(), + &client_id, + msg.client_state.clone(), + msg.consensus_state, )?; let event = IbcEvent::UpgradeClient(UpgradeClient::new( client_id, - client_state.client_type(), - client_state.latest_height(), + old_client_state.client_type(), + latest_height, )); ctx.emit_ibc_event(IbcEvent::Message(MessageEvent::Client)); ctx.emit_ibc_event(event); @@ -119,7 +116,7 @@ mod tests { use crate::Height; use crate::mock::client_state::client_type as mock_client_type; - use crate::mock::context::MockContext; + use crate::mock::context::{AnyClientState, AnyConsensusState, MockContext}; enum Ctx { Default, @@ -206,7 +203,10 @@ mod tests { assert_eq!(upgrade_client_event.consensus_height(), &plan_height); let client_state = fxt.ctx.client_state(&fxt.msg.client_id).unwrap(); - assert_eq!(client_state.as_ref().clone_into(), fxt.msg.client_state); + let msg_client_state: AnyClientState = + fxt.msg.client_state.clone().try_into().unwrap(); + assert_eq!(client_state, msg_client_state); + let consensus_state = fxt .ctx .consensus_state(&ClientConsensusStatePath::new( @@ -214,10 +214,9 @@ mod tests { &plan_height, )) .unwrap(); - assert_eq!( - consensus_state.as_ref().clone_into(), - fxt.msg.consensus_state - ); + let msg_consensus_state: AnyConsensusState = + fxt.msg.consensus_state.clone().try_into().unwrap(); + assert_eq!(consensus_state, msg_consensus_state); } }; } diff --git a/crates/ibc/src/core/ics02_client/mod.rs b/crates/ibc/src/core/ics02_client/mod.rs index 658792962..682e72fb3 100644 --- a/crates/ibc/src/core/ics02_client/mod.rs +++ b/crates/ibc/src/core/ics02_client/mod.rs @@ -8,3 +8,6 @@ pub mod events; pub mod handler; pub mod height; pub mod msgs; + +mod context; +pub use context::ClientExecutionContext; diff --git a/crates/ibc/src/core/ics03_connection/error.rs b/crates/ibc/src/core/ics03_connection/error.rs index 28512f9dd..687c0257a 100644 --- a/crates/ibc/src/core/ics03_connection/error.rs +++ b/crates/ibc/src/core/ics03_connection/error.rs @@ -9,6 +9,7 @@ use crate::Height; use alloc::string::String; use displaydoc::Display; use ibc_proto::protobuf::Error as ProtoError; +use tendermint_proto::Error as TmProtoError; #[derive(Debug, Display)] pub enum ConnectionError { @@ -16,6 +17,8 @@ pub enum ConnectionError { Client(client_error::ClientError), /// invalid connection state: expected `{expected}`, actual `{actual}` InvalidState { expected: String, actual: String }, + /// failed to encode consensus state: `{0}` + ConsensusStateEncodeFailure(TmProtoError), /// invalid connection end error: `{0}` InvalidConnectionEnd(ProtoError), /// consensus height claimed by the client on the other party is too advanced: `{target_height}` (host chain current height: `{current_height}`) diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs index 1f7433545..60ec079c1 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs @@ -4,6 +4,8 @@ use ibc_proto::protobuf::Protobuf; use prost::Message; use crate::core::context::ContextError; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::core::ics03_connection::error::ConnectionError; use crate::core::ics03_connection::events::OpenAck; @@ -115,7 +117,9 @@ where &msg.proof_consensus_state_of_a_on_b, consensus_state_of_b_on_a.root(), Path::ClientConsensusState(client_cons_state_path_on_b), - expected_consensus_state_of_a_on_b.encode_vec(), + expected_consensus_state_of_a_on_b + .encode_vec() + .map_err(ConnectionError::ConsensusStateEncodeFailure)?, ) .map_err(|e| ConnectionError::ConsensusStateVerificationFailure { height: msg.proofs_height_on_b, @@ -207,7 +211,6 @@ mod tests { use crate::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; use crate::core::ics23_commitment::commitment::CommitmentPrefix; use crate::core::ics24_host::identifier::{ChainId, ClientId}; - use crate::core::ValidationContext; use crate::core::events::IbcEvent; use crate::core::timestamp::ZERO_DURATION; diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_confirm.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_confirm.rs index 44e76d2c9..7c60077ba 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_confirm.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_confirm.rs @@ -3,6 +3,8 @@ use ibc_proto::protobuf::Protobuf; use crate::core::context::ContextError; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::core::ics03_connection::error::ConnectionError; use crate::core::ics03_connection::events::OpenConfirm; @@ -179,8 +181,6 @@ mod tests { use crate::mock::context::MockContext; use crate::Height; - use crate::core::ValidationContext; - enum Ctx { Default, CorrectConnection, @@ -258,7 +258,7 @@ mod tests { IbcEvent::OpenConfirmConnection(e) => e, _ => unreachable!(), }; - let conn_end = ::connection_end( + let conn_end = ValidationContext::connection_end( &fxt.ctx, conn_open_try_event.connection_id(), ) diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_init.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_init.rs index b8e50798b..598d97ac0 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_init.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_init.rs @@ -3,6 +3,7 @@ use crate::prelude::*; use crate::core::context::ContextError; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::core::ics03_connection::events::OpenInit; use crate::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; @@ -166,7 +167,7 @@ mod tests { IbcEvent::OpenInitConnection(e) => e, _ => unreachable!(), }; - let conn_end = ::connection_end( + let conn_end = ValidationContext::connection_end( &fxt.ctx, conn_open_init_event.connection_id(), ) diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs index 5f48d6e04..bf903dcdd 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs @@ -4,6 +4,8 @@ use ibc_proto::protobuf::Protobuf; use prost::Message; use crate::core::context::ContextError; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::core::ics03_connection::error::ConnectionError; use crate::core::ics03_connection::events::OpenTry; @@ -112,7 +114,9 @@ where &msg.proof_consensus_state_of_b_on_a, consensus_state_of_a_on_b.root(), Path::ClientConsensusState(client_cons_state_path_on_a), - expected_consensus_state_of_b_on_a.encode_vec(), + expected_consensus_state_of_b_on_a + .encode_vec() + .map_err(ConnectionError::ConsensusStateEncodeFailure)?, ) .map_err(|e| ConnectionError::ConsensusStateVerificationFailure { height: msg.proofs_height_on_a, @@ -208,7 +212,6 @@ mod tests { use crate::core::ics03_connection::handler::test_util::{Expect, Fixture}; use crate::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::core::ics24_host::identifier::ChainId; - use crate::core::ValidationContext; use crate::mock::context::MockContext; use crate::mock::host::HostType; use crate::Height; @@ -303,7 +306,7 @@ mod tests { IbcEvent::OpenTryConnection(e) => e, _ => unreachable!(), }; - let conn_end = ::connection_end( + let conn_end = ValidationContext::connection_end( &fxt.ctx, conn_open_try_event.connection_id(), ) diff --git a/crates/ibc/src/core/ics04_channel/context.rs b/crates/ibc/src/core/ics04_channel/context.rs index 34ddfd703..1f95c6521 100644 --- a/crates/ibc/src/core/ics04_channel/context.rs +++ b/crates/ibc/src/core/ics04_channel/context.rs @@ -2,6 +2,7 @@ use crate::core::events::IbcEvent; use crate::core::ics02_client::client_state::ClientState; +use crate::core::ics02_client::ClientExecutionContext; use crate::core::ics24_host::path::{ ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath, }; @@ -20,6 +21,11 @@ use super::packet::Sequence; /// Methods required in send packet validation, to be implemented by the host pub trait SendPacketValidationContext { + type ClientValidationContext; + type E: ClientExecutionContext; + type AnyConsensusState: ConsensusState; + type AnyClientState: ClientState; + /// Returns the ChannelEnd for the given `port_id` and `chan_id`. fn channel_end(&self, channel_end_path: &ChannelEndPath) -> Result; @@ -28,12 +34,12 @@ pub trait SendPacketValidationContext { /// Returns the ClientState for the given identifier `client_id`. Necessary dependency towards /// proof verification. - fn client_state(&self, client_id: &ClientId) -> Result, ContextError>; + fn client_state(&self, client_id: &ClientId) -> Result; fn client_consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, - ) -> Result, ContextError>; + ) -> Result; fn get_next_sequence_send(&self, seq_send_path: &SeqSendPath) -> Result; @@ -43,6 +49,11 @@ impl SendPacketValidationContext for T where T: ValidationContext, { + type ClientValidationContext = T::ClientValidationContext; + type E = T::E; + type AnyConsensusState = T::AnyConsensusState; + type AnyClientState = T::AnyClientState; + fn channel_end(&self, channel_end_path: &ChannelEndPath) -> Result { self.channel_end(channel_end_path) } @@ -51,14 +62,14 @@ where self.connection_end(connection_id) } - fn client_state(&self, client_id: &ClientId) -> Result, ContextError> { + fn client_state(&self, client_id: &ClientId) -> Result { self.client_state(client_id) } fn client_consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, - ) -> Result, ContextError> { + ) -> Result { self.consensus_state(client_cons_state_path) } diff --git a/crates/ibc/src/core/ics04_channel/events/packet_attributes.rs b/crates/ibc/src/core/ics04_channel/events/packet_attributes.rs index 8d3ab37b4..d0ff53757 100644 --- a/crates/ibc/src/core/ics04_channel/events/packet_attributes.rs +++ b/crates/ibc/src/core/ics04_channel/events/packet_attributes.rs @@ -1,6 +1,6 @@ //! This module holds all the abci event attributes for IBC events emitted //! during packet-related datagrams. -//! + use crate::core::timestamp::Timestamp; use crate::prelude::*; diff --git a/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs b/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs index d25c0e114..e65cbecaa 100644 --- a/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs +++ b/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use crate::core::events::MessageEvent; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::{Counterparty, Order, State as ChannelState}; @@ -12,8 +14,7 @@ use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqAckPath, }; -use crate::core::router::ModuleId; -use crate::core::{events::IbcEvent, ics04_channel::events::AcknowledgePacket}; +use crate::core::{events::IbcEvent, ics04_channel::events::AcknowledgePacket, router::ModuleId}; use crate::core::{ContextError, ExecutionContext, ValidationContext}; pub(crate) fn acknowledgement_packet_validate( diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs b/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs index 82c2df7e5..85f5df02f 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs @@ -4,6 +4,8 @@ use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs b/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs index b3be6c82d..448801a15 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::error::ChannelError; diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs index ca2007a85..b1d229cdb 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs @@ -4,6 +4,8 @@ use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs index 89807457d..dd8e52463 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs @@ -4,6 +4,8 @@ use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs index 0408e1173..95e9ba3e9 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs @@ -3,6 +3,7 @@ use crate::prelude::*; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::core::ics04_channel::error::ChannelError; use crate::core::ics04_channel::events::OpenInit; diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs index 819b8cc46..a9ca05e43 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs @@ -4,6 +4,8 @@ use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; diff --git a/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs b/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs index 63dd25445..fe0f0d131 100644 --- a/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs +++ b/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::{Counterparty, Order, State as ChannelState}; diff --git a/crates/ibc/src/core/ics04_channel/handler/send_packet.rs b/crates/ibc/src/core/ics04_channel/handler/send_packet.rs index f1367e637..009f6ee99 100644 --- a/crates/ibc/src/core/ics04_channel/handler/send_packet.rs +++ b/crates/ibc/src/core/ics04_channel/handler/send_packet.rs @@ -2,6 +2,8 @@ use crate::prelude::*; use crate::core::events::IbcEvent; use crate::core::events::MessageEvent; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics04_channel::channel::Counterparty; use crate::core::ics04_channel::commitment::compute_packet_commitment; use crate::core::ics04_channel::context::SendPacketExecutionContext; diff --git a/crates/ibc/src/core/ics04_channel/handler/timeout.rs b/crates/ibc/src/core/ics04_channel/handler/timeout.rs index 9b492530a..8a142d9fc 100644 --- a/crates/ibc/src/core/ics04_channel/handler/timeout.rs +++ b/crates/ibc/src/core/ics04_channel/handler/timeout.rs @@ -3,6 +3,8 @@ use prost::Message; use crate::core::events::IbcEvent; use crate::core::events::MessageEvent; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{Counterparty, Order}; diff --git a/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs b/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs index d7540375a..5630a1f2a 100644 --- a/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs +++ b/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs @@ -2,6 +2,8 @@ use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use prost::Message; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, Order}; diff --git a/crates/ibc/src/erased.rs b/crates/ibc/src/erased.rs deleted file mode 100644 index 3bde286ea..000000000 --- a/crates/ibc/src/erased.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// If the `serde` feature is enabled then re-export `erased_serde::Serialize` as `ErasedSerialize`, -/// otherwise define an empty `ErasedSerialize` trait and provide a blanket implementation for all -/// types. -#[cfg(feature = "serde")] -pub use erased_serde::Serialize as ErasedSerialize; -#[cfg(not(feature = "serde"))] -pub trait ErasedSerialize {} -#[cfg(not(feature = "serde"))] -impl ErasedSerialize for T {} diff --git a/crates/ibc/src/hosts/tendermint/upgrade_proposal/context.rs b/crates/ibc/src/hosts/tendermint/upgrade_proposal/context.rs index 0f5c4f528..bb6e014b0 100644 --- a/crates/ibc/src/hosts/tendermint/upgrade_proposal/context.rs +++ b/crates/ibc/src/hosts/tendermint/upgrade_proposal/context.rs @@ -5,17 +5,21 @@ //! [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 alloc::boxed::Box; - use super::Plan; use crate::core::ics02_client::client_state::ClientState; use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::UpgradeClientError; +use crate::core::ics02_client::ClientExecutionContext; use crate::core::ics24_host::path::UpgradeClientPath; /// Helper context to validate client upgrades, providing methods to retrieve /// an upgrade plan and related upgraded client and consensus states. pub trait UpgradeValidationContext { + type 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; @@ -23,13 +27,13 @@ pub trait UpgradeValidationContext { fn upgraded_client_state( &self, upgrade_path: &UpgradeClientPath, - ) -> Result, UpgradeClientError>; + ) -> Result; /// Returns the upgraded consensus state at the specified upgrade path. fn upgraded_consensus_state( &self, upgrade_path: &UpgradeClientPath, - ) -> Result, UpgradeClientError>; + ) -> Result; } /// Helper context to execute client upgrades, providing methods to schedule @@ -45,13 +49,13 @@ pub trait UpgradeExecutionContext: UpgradeValidationContext { fn store_upgraded_client_state( &mut self, upgrade_path: UpgradeClientPath, - client_state: Box, + client_state: Self::AnyClientState, ) -> Result<(), UpgradeClientError>; /// Stores the upgraded consensus state at the specified upgrade path. fn store_upgraded_consensus_state( &mut self, upgrade_path: UpgradeClientPath, - consensus_state: Box, + consensus_state: Self::AnyConsensusState, ) -> Result<(), UpgradeClientError>; } diff --git a/crates/ibc/src/hosts/tendermint/upgrade_proposal/handler.rs b/crates/ibc/src/hosts/tendermint/upgrade_proposal/handler.rs index 1cdc9c43b..9b6026f5c 100644 --- a/crates/ibc/src/hosts/tendermint/upgrade_proposal/handler.rs +++ b/crates/ibc/src/hosts/tendermint/upgrade_proposal/handler.rs @@ -1,6 +1,5 @@ use core::convert::Infallible; -use alloc::boxed::Box; use alloc::string::ToString; use tendermint::abci::Event as TmEvent; use tendermint_proto::abci::Event as ProtoEvent; @@ -23,6 +22,7 @@ pub fn upgrade_client_proposal_handler( ) -> Result where Ctx: UpgradeExecutionContext, + Ctx::AnyClientState: From, { let plan = proposal.plan; @@ -43,7 +43,7 @@ where let upgraded_client_state_path = UpgradeClientPath::UpgradedClientState(plan.height); - ctx.store_upgraded_client_state(upgraded_client_state_path, Box::new(client_state))?; + ctx.store_upgraded_client_state(upgraded_client_state_path, client_state.into())?; let event = TmEvent::from(UpgradeClientProposal::new(proposal.title, plan.height)) .try_into() diff --git a/crates/ibc/src/hosts/tendermint/validate_self_client.rs b/crates/ibc/src/hosts/tendermint/validate_self_client.rs index 7417bc74e..b305027d4 100644 --- a/crates/ibc/src/hosts/tendermint/validate_self_client.rs +++ b/crates/ibc/src/hosts/tendermint/validate_self_client.rs @@ -5,7 +5,7 @@ use alloc::string::{String, ToString}; use ibc_proto::google::protobuf::Any; use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState; -use crate::core::ics02_client::client_state::ClientState; +use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics02_client::error::ClientError; use crate::core::ics03_connection::error::ConnectionError; use crate::core::ics23_commitment::specs::ProofSpecs; diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 6fcf814ed..234af7a48 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -58,7 +58,6 @@ pub mod mock; #[cfg(any(test, feature = "mocks"))] pub mod test_utils; // Context mock, the underlying host chain, and client types: for testing all handlers. -mod erased; mod prelude; mod signer; mod utils; @@ -68,3 +67,6 @@ mod serializers; #[cfg(test)] mod test; + +/// Re-export the `Any` type which used across the library. +pub use ibc_proto::google::protobuf::Any; diff --git a/crates/ibc/src/mock/client_state.rs b/crates/ibc/src/mock/client_state.rs index d3bd25357..dfcf91b19 100644 --- a/crates/ibc/src/mock/client_state.rs +++ b/crates/ibc/src/mock/client_state.rs @@ -1,6 +1,5 @@ use crate::prelude::*; -use alloc::collections::btree_map::BTreeMap as HashMap; use core::str::FromStr; use core::time::Duration; @@ -8,10 +7,13 @@ use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::mock::ClientState as RawMockClientState; use ibc_proto::protobuf::Protobuf; -use crate::core::ics02_client::client_state::{ClientState, UpdateKind, UpdatedState}; +use crate::core::ics02_client::client_state::ClientStateCommon; +use crate::core::ics02_client::client_state::ClientStateExecution; +use crate::core::ics02_client::client_state::ClientStateValidation; +use crate::core::ics02_client::client_state::UpdateKind; use crate::core::ics02_client::client_type::ClientType; -use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::{ClientError, UpgradeClientError}; +use crate::core::ics02_client::ClientExecutionContext; use crate::core::ics23_commitment::commitment::{ CommitmentPrefix, CommitmentProofBytes, CommitmentRoot, }; @@ -26,27 +28,13 @@ use crate::mock::misbehaviour::Misbehaviour; use crate::Height; -use crate::core::{ExecutionContext, ValidationContext}; - pub const MOCK_CLIENT_STATE_TYPE_URL: &str = "/ibc.mock.ClientState"; - pub const MOCK_CLIENT_TYPE: &str = "9999-mock"; pub fn client_type() -> ClientType { ClientType::from_str(MOCK_CLIENT_TYPE).expect("never fails because it's valid client type") } -/// A mock of an IBC client record as it is stored in a mock context. -/// For testing ICS02 handlers mostly, cf. `MockClientContext`. -#[derive(Clone, Debug)] -pub struct MockClientRecord { - /// The client state (representing only the latest height at the moment). - pub client_state: Option>, - - /// Mapping of heights to consensus states for this client. - pub consensus_states: HashMap>, -} - /// A mock of a client state. For an example of a real structure that this mocks, you can see /// `ClientState` of ics07_tendermint/client_state.rs. @@ -138,7 +126,13 @@ impl From for Any { } } -impl ClientState for MockClientState { +impl ClientStateCommon for MockClientState { + fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError> { + let _mock_consensus_state = MockConsensusState::try_from(consensus_state)?; + + Ok(()) + } + fn client_type(&self) -> ClientType { mock_client_type() } @@ -170,10 +164,6 @@ impl ClientState for MockClientState { false } - fn initialise(&self, consensus_state: Any) -> Result, ClientError> { - MockConsensusState::try_from(consensus_state).map(MockConsensusState::into_box) - } - fn verify_upgrade_client( &self, upgraded_client_state: Any, @@ -193,19 +183,6 @@ impl ClientState for MockClientState { Ok(()) } - fn update_state_with_upgrade_client( - &self, - upgraded_client_state: Any, - upgraded_consensus_state: Any, - ) -> Result { - let mock_client_state = MockClientState::try_from(upgraded_client_state)?; - let mock_consensus_state = MockConsensusState::try_from(upgraded_consensus_state)?; - Ok(UpdatedState { - client_state: mock_client_state.into_box(), - consensus_state: mock_consensus_state.into_box(), - }) - } - fn verify_membership( &self, _prefix: &CommitmentPrefix, @@ -226,10 +203,12 @@ impl ClientState for MockClientState { ) -> Result<(), ClientError> { Ok(()) } +} +impl ClientStateValidation for MockClientState { fn verify_client_message( &self, - _ctx: &dyn ValidationContext, + _ctx: &ClientValidationContext, _client_id: &ClientId, client_message: Any, update_kind: &UpdateKind, @@ -253,12 +232,9 @@ impl ClientState for MockClientState { Ok(()) } - /// We consider the following to be misbehaviour: - /// + UpdateHeader: no misbehaviour possible - /// + Misbehaviour: headers are at same height and are in the future fn check_for_misbehaviour( &self, - _ctx: &dyn ValidationContext, + _ctx: &ClientValidationContext, _client_id: &ClientId, client_message: Any, update_kind: &UpdateKind, @@ -277,51 +253,86 @@ impl ClientState for MockClientState { } } } +} + +impl ClientStateExecution for MockClientState +where + E: ClientExecutionContext, + ::AnyClientState: From, + ::AnyConsensusState: From, +{ + fn initialise( + &self, + ctx: &mut E, + client_id: &ClientId, + consensus_state: Any, + ) -> Result<(), ClientError> { + let mock_consensus_state = MockConsensusState::try_from(consensus_state)?; + + ctx.store_client_state(ClientStatePath::new(client_id), (*self).into())?; + ctx.store_consensus_state( + ClientConsensusStatePath::new(client_id, &self.latest_height()), + mock_consensus_state.into(), + )?; + + Ok(()) + } fn update_state( &self, - ctx: &mut dyn ExecutionContext, + ctx: &mut E, client_id: &ClientId, header: Any, ) -> Result, ClientError> { let header = MockHeader::try_from(header)?; let header_height = header.height; - let new_client_state = MockClientState::new(header).into_box(); - let new_consensus_state = MockConsensusState::new(header).into_box(); + let new_client_state = MockClientState::new(header); + let new_consensus_state = MockConsensusState::new(header); - ctx.store_update_time( - client_id.clone(), - new_client_state.latest_height(), - ctx.host_timestamp()?, - )?; - ctx.store_update_height( - client_id.clone(), - new_client_state.latest_height(), - ctx.host_height()?, - )?; ctx.store_consensus_state( ClientConsensusStatePath::new(client_id, &new_client_state.latest_height()), - new_consensus_state, + new_consensus_state.into(), )?; - ctx.store_client_state(ClientStatePath::new(client_id), new_client_state)?; + ctx.store_client_state(ClientStatePath::new(client_id), new_client_state.into())?; Ok(vec![header_height]) } fn update_state_on_misbehaviour( &self, - ctx: &mut dyn ExecutionContext, + ctx: &mut E, client_id: &ClientId, _client_message: Any, _update_kind: &UpdateKind, ) -> Result<(), ClientError> { - let frozen_client_state = self.with_frozen_height(Height::min(0)).into_box(); + let frozen_client_state = self.with_frozen_height(Height::min(0)); - ctx.store_client_state(ClientStatePath::new(client_id), frozen_client_state)?; + ctx.store_client_state(ClientStatePath::new(client_id), frozen_client_state.into())?; Ok(()) } + + fn update_state_with_upgrade_client( + &self, + ctx: &mut E, + client_id: &ClientId, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + ) -> Result { + let new_client_state = MockClientState::try_from(upgraded_client_state)?; + let new_consensus_state = MockConsensusState::try_from(upgraded_consensus_state)?; + + let latest_height = new_client_state.latest_height(); + + ctx.store_consensus_state( + ClientConsensusStatePath::new(client_id, &latest_height), + new_consensus_state.into(), + )?; + ctx.store_client_state(ClientStatePath::new(client_id), new_client_state.into())?; + + Ok(latest_height) + } } impl From for MockClientState { diff --git a/crates/ibc/src/mock/consensus_state.rs b/crates/ibc/src/mock/consensus_state.rs index 799da55ef..b82d038be 100644 --- a/crates/ibc/src/mock/consensus_state.rs +++ b/crates/ibc/src/mock/consensus_state.rs @@ -4,6 +4,8 @@ use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::mock::ConsensusState as RawMockConsensusState; use ibc_proto::protobuf::Protobuf; +use tendermint_proto::{Error, Protobuf as TmProtobuf}; + use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; use crate::core::ics23_commitment::commitment::CommitmentRoot; @@ -94,6 +96,8 @@ impl From for Any { } } +impl TmProtobuf for MockConsensusState {} + impl ConsensusState for MockConsensusState { fn root(&self) -> &CommitmentRoot { &self.root @@ -102,4 +106,8 @@ impl ConsensusState for MockConsensusState { fn timestamp(&self) -> Timestamp { self.header.timestamp } + + fn encode_vec(&self) -> Result, Error> { + >::encode_vec(self) + } } diff --git a/crates/ibc/src/mock/context.rs b/crates/ibc/src/mock/context.rs index ee46be824..f2f9ab340 100644 --- a/crates/ibc/src/mock/context.rs +++ b/crates/ibc/src/mock/context.rs @@ -1,14 +1,12 @@ //! Implementation of a global context mock. Used in testing handlers of all IBC modules. -use crate::applications::transfer::context::{ - cosmos_adr028_escrow_address, TokenTransferExecutionContext, TokenTransferValidationContext, -}; -use crate::applications::transfer::error::TokenTransferError; -use crate::applications::transfer::PrefixedCoin; +mod applications; +mod clients; + use crate::clients::ics07_tendermint::TENDERMINT_CLIENT_TYPE; use crate::core::ics24_host::path::{ - AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, CommitmentPath, + ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::prelude::*; @@ -18,15 +16,22 @@ use core::cmp::min; use core::fmt::Debug; use core::ops::{Add, Sub}; use core::time::Duration; +use derive_more::{From, TryInto}; use parking_lot::Mutex; -use subtle_encoding::bech32; +use tendermint_proto::Protobuf; use ibc_proto::google::protobuf::Any; use tracing::debug; use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState; +use crate::clients::ics07_tendermint::client_state::TENDERMINT_CLIENT_STATE_TYPE_URL; +use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use crate::clients::ics07_tendermint::consensus_state::TENDERMINT_CONSENSUS_STATE_TYPE_URL; + +use crate::core::dispatch; use crate::core::events::IbcEvent; use crate::core::ics02_client::client_state::ClientState; +use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics02_client::client_type::ClientType; use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; @@ -41,12 +46,9 @@ use crate::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, Connecti use crate::core::router::Router; use crate::core::router::{Module, ModuleId}; use crate::core::timestamp::Timestamp; -use crate::core::ContextError; -use crate::core::MsgEnvelope; -use crate::core::{dispatch, ExecutionContext, ValidationContext}; -use crate::mock::client_state::{ - client_type as mock_client_type, MockClientRecord, MockClientState, -}; +use crate::core::{ContextError, ValidationContext}; +use crate::core::{ExecutionContext, MsgEnvelope}; +use crate::mock::client_state::{client_type as mock_client_type, MockClientState}; use crate::mock::consensus_state::MockConsensusState; use crate::mock::header::MockHeader; use crate::mock::host::{HostBlock, HostType}; @@ -55,10 +57,147 @@ use crate::mock::ics18_relayer::error::RelayerError; use crate::signer::Signer; use crate::Height; -use super::client_state::MOCK_CLIENT_TYPE; +use super::client_state::{MOCK_CLIENT_STATE_TYPE_URL, MOCK_CLIENT_TYPE}; +use super::consensus_state::MOCK_CONSENSUS_STATE_TYPE_URL; pub const DEFAULT_BLOCK_TIME_SECS: u64 = 3; +#[derive(Debug, Clone, From, PartialEq, ClientState)] +#[generics(ClientValidationContext = MockContext, + ClientExecutionContext = MockContext) +] +#[mock] +pub enum AnyClientState { + Tendermint(TmClientState), + Mock(MockClientState), +} + +impl Protobuf for AnyClientState {} + +impl TryFrom for AnyClientState { + type Error = ClientError; + + fn try_from(raw: Any) -> Result { + if raw.type_url == TENDERMINT_CLIENT_STATE_TYPE_URL { + TmClientState::try_from(raw).map(Into::into) + } else if raw.type_url == MOCK_CLIENT_STATE_TYPE_URL { + MockClientState::try_from(raw).map(Into::into) + } else { + Err(ClientError::Other { + description: "failed to deserialize message".to_string(), + }) + } + } +} + +impl From for Any { + fn from(host_client_state: AnyClientState) -> Self { + match host_client_state { + AnyClientState::Tendermint(cs) => cs.into(), + AnyClientState::Mock(cs) => cs.into(), + } + } +} + +#[derive(Debug, Clone, From, TryInto, PartialEq, ConsensusState)] +pub enum AnyConsensusState { + Tendermint(TmConsensusState), + Mock(MockConsensusState), +} + +impl Protobuf for AnyConsensusState {} + +impl TryFrom for AnyConsensusState { + type Error = ClientError; + + fn try_from(raw: Any) -> Result { + if raw.type_url == TENDERMINT_CONSENSUS_STATE_TYPE_URL { + TmConsensusState::try_from(raw).map(Into::into) + } else if raw.type_url == MOCK_CONSENSUS_STATE_TYPE_URL { + MockConsensusState::try_from(raw).map(Into::into) + } else { + Err(ClientError::Other { + description: "failed to deserialize message".to_string(), + }) + } + } +} + +impl From for Any { + fn from(host_consensus_state: AnyConsensusState) -> Self { + match host_consensus_state { + AnyConsensusState::Tendermint(cs) => cs.into(), + AnyConsensusState::Mock(cs) => cs.into(), + } + } +} + +/// A mock of an IBC client record as it is stored in a mock context. +/// For testing ICS02 handlers mostly, cf. `MockClientContext`. +#[derive(Clone, Debug)] +pub struct MockClientRecord { + /// The client state (representing only the latest height at the moment). + pub client_state: Option, + + /// Mapping of heights to consensus states for this client. + pub consensus_states: BTreeMap, +} + +/// An object that stores all IBC related data. +#[derive(Clone, Debug, Default)] +pub struct MockIbcStore { + /// The set of all clients, indexed by their id. + pub clients: BTreeMap, + + /// Tracks the processed time for clients header updates + pub client_processed_times: BTreeMap<(ClientId, Height), Timestamp>, + + /// Tracks the processed height for the clients + pub client_processed_heights: BTreeMap<(ClientId, Height), Height>, + + /// Counter for the client identifiers, necessary for `increase_client_counter` and the + /// `client_counter` methods. + pub client_ids_counter: u64, + + /// Association between client ids and connection ids. + pub client_connections: BTreeMap, + + /// All the connections in the store. + pub connections: BTreeMap, + + /// Counter for connection identifiers (see `increase_connection_counter`). + pub connection_ids_counter: u64, + + /// Association between connection ids and channel ids. + pub connection_channels: BTreeMap>, + + /// Counter for channel identifiers (see `increase_channel_counter`). + pub channel_ids_counter: u64, + + /// All the channels in the store. TODO Make new key PortId X ChannelId + pub channels: PortChannelIdMap, + + /// Tracks the sequence number for the next packet to be sent. + pub next_sequence_send: PortChannelIdMap, + + /// Tracks the sequence number for the next packet to be received. + pub next_sequence_recv: PortChannelIdMap, + + /// Tracks the sequence number for the next packet to be acknowledged. + pub next_sequence_ack: PortChannelIdMap, + + pub packet_acknowledgement: PortChannelIdMap>, + + /// Maps ports to the the module that owns it + pub port_to_module: BTreeMap, + + /// Constant-size commitments to packets data fields + pub packet_commitment: PortChannelIdMap>, + + // Used by unordered channel + pub packet_receipt: PortChannelIdMap>, +} + /// A context implementing the dependencies necessary for testing any IBC module. #[derive(Debug)] pub struct MockContext { @@ -236,8 +375,8 @@ impl MockContext { let client_type = client_type.unwrap_or_else(mock_client_type); let (client_state, consensus_state) = if client_type.as_str() == MOCK_CLIENT_TYPE { ( - Some(MockClientState::new(MockHeader::new(client_state_height)).into_box()), - MockConsensusState::new(MockHeader::new(cs_height)).into_box(), + Some(MockClientState::new(MockHeader::new(client_state_height)).into()), + MockConsensusState::new(MockHeader::new(cs_height)).into(), ) } else if client_type.as_str() == TENDERMINT_CLIENT_TYPE { let light_block = HostBlock::generate_tm_block( @@ -247,7 +386,7 @@ impl MockContext { ); let client_state = - TmClientState::new_dummy_from_header(light_block.header().clone()).into_box(); + TmClientState::new_dummy_from_header(light_block.header().clone()).into(); // Return the tuple. (Some(client_state), light_block.into()) @@ -301,28 +440,29 @@ impl MockContext { let client_type = client_type.unwrap_or_else(mock_client_type); let now = Timestamp::now(); - let (client_state, consensus_state) = if client_type.as_str() == MOCK_CLIENT_TYPE { - // If it's a mock client, create the corresponding mock states. - ( - Some(MockClientState::new(MockHeader::new(client_state_height)).into_box()), - MockConsensusState::new(MockHeader::new(cs_height)).into_box(), - ) - } else if client_type.as_str() == TENDERMINT_CLIENT_TYPE { - // If it's a Tendermint client, we need TM states. - let light_block = - HostBlock::generate_tm_block(client_chain_id, cs_height.revision_height(), now); + let (client_state, consensus_state): (Option, AnyConsensusState) = + if client_type.as_str() == MOCK_CLIENT_TYPE { + // If it's a mock client, create the corresponding mock states. + ( + Some(MockClientState::new(MockHeader::new(client_state_height)).into()), + MockConsensusState::new(MockHeader::new(cs_height)).into(), + ) + } else if client_type.as_str() == TENDERMINT_CLIENT_TYPE { + // If it's a Tendermint client, we need TM states. + let light_block = + HostBlock::generate_tm_block(client_chain_id, cs_height.revision_height(), now); - let client_state = - TmClientState::new_dummy_from_header(light_block.header().clone()).into_box(); + let client_state = + TmClientState::new_dummy_from_header(light_block.header().clone()).into(); - // Return the tuple. - (Some(client_state), light_block.into()) - } else { - panic!("Unknown client type") - }; + // Return the tuple. + (Some(client_state), light_block.into()) + } else { + panic!("Unknown client type") + }; let prev_consensus_state = if client_type.as_str() == MOCK_CLIENT_TYPE { - MockConsensusState::new(MockHeader::new(prev_cs_height)).into_box() + MockConsensusState::new(MockHeader::new(prev_cs_height)).into() } else if client_type.as_str() == TENDERMINT_CLIENT_TYPE { let light_block = HostBlock::generate_tm_block( self.host_chain_id.clone(), @@ -570,7 +710,7 @@ impl MockContext { .insert(port_id, module_id); } - pub fn latest_client_states(&self, client_id: &ClientId) -> Box { + pub fn latest_client_states(&self, client_id: &ClientId) -> AnyClientState { self.ibc_store.lock().clients[client_id] .client_state .as_ref() @@ -582,14 +722,12 @@ impl MockContext { &self, client_id: &ClientId, height: &Height, - ) -> Box { - dyn_clone::clone_box( - self.ibc_store.lock().clients[client_id] - .consensus_states - .get(height) - .expect("Never fails") - .as_ref(), - ) + ) -> AnyConsensusState { + self.ibc_store.lock().clients[client_id] + .consensus_states + .get(height) + .expect("Never fails") + .clone() } pub fn latest_height(&self) -> Height { @@ -611,69 +749,14 @@ impl MockContext { type PortChannelIdMap = BTreeMap>; -/// An object that stores all IBC related data. -#[derive(Clone, Debug, Default)] -pub struct MockIbcStore { - /// The set of all clients, indexed by their id. - pub clients: BTreeMap, - - /// Tracks the processed time for clients header updates - pub client_processed_times: BTreeMap<(ClientId, Height), Timestamp>, - - /// Tracks the processed height for the clients - pub client_processed_heights: BTreeMap<(ClientId, Height), Height>, - - /// Counter for the client identifiers, necessary for `increase_client_counter` and the - /// `client_counter` methods. - pub client_ids_counter: u64, - - /// Association between client ids and connection ids. - pub client_connections: BTreeMap, - - /// All the connections in the store. - pub connections: BTreeMap, - - /// Counter for connection identifiers (see `increase_connection_counter`). - pub connection_ids_counter: u64, - - /// Association between connection ids and channel ids. - pub connection_channels: BTreeMap>, - - /// Counter for channel identifiers (see `increase_channel_counter`). - pub channel_ids_counter: u64, - - /// All the channels in the store. TODO Make new key PortId X ChannelId - pub channels: PortChannelIdMap, - - /// Tracks the sequence number for the next packet to be sent. - pub next_sequence_send: PortChannelIdMap, - - /// Tracks the sequence number for the next packet to be received. - pub next_sequence_recv: PortChannelIdMap, - - /// Tracks the sequence number for the next packet to be acknowledged. - pub next_sequence_ack: PortChannelIdMap, - - pub packet_acknowledgement: PortChannelIdMap>, - - /// Maps ports to the the module that owns it - pub port_to_module: BTreeMap, - - /// Constant-size commitments to packets data fields - pub packet_commitment: PortChannelIdMap>, - - // Used by unordered channel - pub packet_receipt: PortChannelIdMap>, -} - impl RelayerContext for MockContext { fn query_latest_height(&self) -> Result { - self.host_height() + ValidationContext::host_height(self) } - fn query_client_full_state(&self, client_id: &ClientId) -> Option> { + fn query_client_full_state(&self, client_id: &ClientId) -> Option { // Forward call to Ics2. - ValidationContext::client_state(self, client_id).ok() + self.client_state(client_id).ok() } fn signer(&self) -> Signer { @@ -709,7 +792,12 @@ impl Router for MockContext { } impl ValidationContext for MockContext { - fn client_state(&self, client_id: &ClientId) -> Result, ContextError> { + type ClientValidationContext = 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 @@ -726,12 +814,12 @@ impl ValidationContext for MockContext { .map_err(ContextError::ClientError) } - fn decode_client_state(&self, client_state: Any) -> Result, ContextError> { + fn decode_client_state(&self, client_state: Any) -> Result { if let Ok(client_state) = TmClientState::try_from(client_state.clone()) { client_state.validate().map_err(ClientError::from)?; - Ok(client_state.into_box()) + Ok(client_state.into()) } else if let Ok(client_state) = MockClientState::try_from(client_state.clone()) { - Ok(client_state.into_box()) + Ok(client_state.into()) } else { Err(ClientError::UnknownClientStateType { client_state_type: client_state.type_url, @@ -743,7 +831,7 @@ impl ValidationContext for MockContext { fn consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, - ) -> Result, ContextError> { + ) -> Result { let client_id = &client_cons_state_path.client_id; let height = Height::new(client_cons_state_path.epoch, client_cons_state_path.height)?; match self.ibc_store.lock().clients.get(client_id) { @@ -762,74 +850,6 @@ impl ValidationContext for MockContext { .map_err(ContextError::ClientError) } - fn next_consensus_state( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result>, ContextError> { - let ibc_store = self.ibc_store.lock(); - let client_record = - ibc_store - .clients - .get(client_id) - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - })?; - - // Get the consensus state heights and sort them in ascending order. - let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); - heights.sort(); - - // Search for next state. - for h in heights { - if h > *height { - // unwrap should never happen, as the consensus state for h must exist - return Ok(Some( - client_record - .consensus_states - .get(&h) - .expect("Never fails") - .clone(), - )); - } - } - Ok(None) - } - - fn prev_consensus_state( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result>, ContextError> { - let ibc_store = self.ibc_store.lock(); - let client_record = - ibc_store - .clients - .get(client_id) - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - })?; - - // Get the consensus state heights and sort them in descending order. - let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); - heights.sort_by(|a, b| b.cmp(a)); - - // Search for previous state. - for h in heights { - if h < *height { - // unwrap should never happen, as the consensus state for h must exist - return Ok(Some( - client_record - .consensus_states - .get(&h) - .expect("Never fails") - .clone(), - )); - } - } - Ok(None) - } - fn host_height(&self) -> Result { Ok(self.latest_height()) } @@ -844,10 +864,7 @@ impl ValidationContext for MockContext { .expect("Never fails")) } - fn host_consensus_state( - &self, - height: &Height, - ) -> Result, ContextError> { + 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 }), @@ -1118,51 +1135,15 @@ impl ValidationContext for MockContext { fn validate_message_signer(&self, _signer: &Signer) -> Result<(), ContextError> { Ok(()) } -} - -impl ExecutionContext for MockContext { - fn store_client_state( - &mut self, - client_state_path: ClientStatePath, - client_state: Box, - ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - - let client_id = client_state_path.0; - let client_record = ibc_store - .clients - .entry(client_id) - .or_insert(MockClientRecord { - consensus_states: Default::default(), - client_state: Default::default(), - }); - client_record.client_state = Some(client_state); - - Ok(()) + fn get_client_validation_context(&self) -> &Self::ClientValidationContext { + self } +} - fn store_consensus_state( - &mut self, - consensus_state_path: ClientConsensusStatePath, - consensus_state: Box, - ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - - let client_record = ibc_store - .clients - .entry(consensus_state_path.client_id) - .or_insert(MockClientRecord { - consensus_states: Default::default(), - client_state: Default::default(), - }); - - let height = Height::new(consensus_state_path.epoch, consensus_state_path.height) - .expect("Never fails"); - client_record - .consensus_states - .insert(height, consensus_state); - Ok(()) +impl ExecutionContext for MockContext { + fn get_client_execution_context(&mut self) -> &mut Self::E { + self } fn increase_client_counter(&mut self) { @@ -1387,83 +1368,6 @@ impl ExecutionContext for MockContext { } } -impl TokenTransferValidationContext for MockContext { - type AccountId = Signer; - - fn get_port(&self) -> Result { - Ok(PortId::transfer()) - } - - fn get_escrow_account( - &self, - port_id: &PortId, - channel_id: &ChannelId, - ) -> Result { - let addr = cosmos_adr028_escrow_address(port_id, channel_id); - Ok(bech32::encode("cosmos", addr).into()) - } - - fn can_send_coins(&self) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn can_receive_coins(&self) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn send_coins_validate( - &self, - _from_account: &Self::AccountId, - _to_account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn mint_coins_validate( - &self, - _account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn burn_coins_validate( - &self, - _account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } -} - -impl TokenTransferExecutionContext for MockContext { - fn send_coins_execute( - &mut self, - _from_account: &Self::AccountId, - _to_account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn mint_coins_execute( - &mut self, - _account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } - - fn burn_coins_execute( - &mut self, - _account: &Self::AccountId, - _coin: &PrefixedCoin, - ) -> Result<(), TokenTransferError> { - Ok(()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/ibc/src/mock/context/applications.rs b/crates/ibc/src/mock/context/applications.rs new file mode 100644 index 000000000..219604de0 --- /dev/null +++ b/crates/ibc/src/mock/context/applications.rs @@ -0,0 +1,89 @@ +//! Application context implementations for `MockContext` + +use subtle_encoding::bech32; + +use super::MockContext; +use crate::applications::transfer::context::cosmos_adr028_escrow_address; +use crate::applications::transfer::context::TokenTransferExecutionContext; +use crate::applications::transfer::context::TokenTransferValidationContext; +use crate::applications::transfer::error::TokenTransferError; +use crate::applications::transfer::PrefixedCoin; +use crate::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::Signer; + +impl TokenTransferValidationContext for MockContext { + type AccountId = Signer; + + fn get_port(&self) -> Result { + Ok(PortId::transfer()) + } + + fn get_escrow_account( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let addr = cosmos_adr028_escrow_address(port_id, channel_id); + Ok(bech32::encode("cosmos", addr).into()) + } + + fn can_send_coins(&self) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn can_receive_coins(&self) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn send_coins_validate( + &self, + _from_account: &Self::AccountId, + _to_account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn mint_coins_validate( + &self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn burn_coins_validate( + &self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } +} + +impl TokenTransferExecutionContext for MockContext { + fn send_coins_execute( + &mut self, + _from_account: &Self::AccountId, + _to_account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn mint_coins_execute( + &mut self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn burn_coins_execute( + &mut self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + Ok(()) + } +} diff --git a/crates/ibc/src/mock/context/clients.rs b/crates/ibc/src/mock/context/clients.rs new file mode 100644 index 000000000..f9b13fd25 --- /dev/null +++ b/crates/ibc/src/mock/context/clients.rs @@ -0,0 +1,153 @@ +//! Client context implementations for `MockContext` + +use crate::prelude::*; + +use super::{AnyClientState, AnyConsensusState, MockClientRecord, MockContext}; +use crate::clients::ics07_tendermint::CommonContext as TmCommonContext; +use crate::clients::ics07_tendermint::ValidationContext as TmValidationContext; +use crate::core::ics02_client::error::ClientError; +use crate::core::ics02_client::ClientExecutionContext; +use crate::core::ics24_host::identifier::ClientId; +use crate::core::ics24_host::path::ClientConsensusStatePath; +use crate::core::ics24_host::path::ClientStatePath; +use crate::core::timestamp::Timestamp; +use crate::core::ContextError; +use crate::core::ValidationContext; +use crate::Height; + +impl TmCommonContext for MockContext { + type ConversionError = &'static str; + type AnyConsensusState = AnyConsensusState; + + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result { + ValidationContext::consensus_state(self, client_cons_state_path) + } +} + +impl TmValidationContext for MockContext { + fn host_timestamp(&self) -> Result { + ValidationContext::host_timestamp(self) + } + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError> { + let ibc_store = self.ibc_store.lock(); + let client_record = + ibc_store + .clients + .get(client_id) + .ok_or_else(|| ClientError::ClientStateNotFound { + client_id: client_id.clone(), + })?; + + // Get the consensus state heights and sort them in ascending order. + let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); + heights.sort(); + + // Search for next state. + for h in heights { + if h > *height { + // unwrap should never happen, as the consensus state for h must exist + return Ok(Some( + client_record + .consensus_states + .get(&h) + .expect("Never fails") + .clone(), + )); + } + } + Ok(None) + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError> { + let ibc_store = self.ibc_store.lock(); + let client_record = + ibc_store + .clients + .get(client_id) + .ok_or_else(|| ClientError::ClientStateNotFound { + client_id: client_id.clone(), + })?; + + // Get the consensus state heights and sort them in descending order. + let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); + heights.sort_by(|a, b| b.cmp(a)); + + // Search for previous state. + for h in heights { + if h < *height { + // unwrap should never happen, as the consensus state for h must exist + return Ok(Some( + client_record + .consensus_states + .get(&h) + .expect("Never fails") + .clone(), + )); + } + } + Ok(None) + } +} + +impl ClientExecutionContext for MockContext { + type ClientValidationContext = Self; + type AnyClientState = AnyClientState; + type AnyConsensusState = AnyConsensusState; + + fn store_client_state( + &mut self, + client_state_path: ClientStatePath, + client_state: Self::AnyClientState, + ) -> Result<(), ContextError> { + let mut ibc_store = self.ibc_store.lock(); + + let client_id = client_state_path.0; + let client_record = ibc_store + .clients + .entry(client_id) + .or_insert(MockClientRecord { + consensus_states: Default::default(), + client_state: Default::default(), + }); + + client_record.client_state = Some(client_state); + + Ok(()) + } + + fn store_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + consensus_state: Self::AnyConsensusState, + ) -> Result<(), ContextError> { + let mut ibc_store = self.ibc_store.lock(); + + let client_record = ibc_store + .clients + .entry(consensus_state_path.client_id) + .or_insert(MockClientRecord { + consensus_states: Default::default(), + client_state: Default::default(), + }); + + let height = Height::new(consensus_state_path.epoch, consensus_state_path.height) + .expect("Never fails"); + client_record + .consensus_states + .insert(height, consensus_state); + + Ok(()) + } +} diff --git a/crates/ibc/src/mock/host.rs b/crates/ibc/src/mock/host.rs index 663b10daa..1c52e2700 100644 --- a/crates/ibc/src/mock/host.rs +++ b/crates/ibc/src/mock/host.rs @@ -8,9 +8,8 @@ use tendermint::block::Header as TmHeader; use tendermint_testgen::light_block::TmLightBlock; use tendermint_testgen::{Generator, LightBlock as TestgenLightBlock}; -use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TMConsensusState; +use crate::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::clients::ics07_tendermint::header::TENDERMINT_HEADER_TYPE_URL; -use crate::core::ics02_client::consensus_state::ConsensusState; use crate::core::ics02_client::error::ClientError; use crate::core::ics24_host::identifier::ChainId; use crate::core::timestamp::Timestamp; @@ -19,6 +18,8 @@ use crate::mock::header::MockHeader; use crate::prelude::*; use crate::Height; +use super::context::AnyConsensusState; + /// Defines the different types of host chains that a mock context can emulate. /// The variants are as follows: /// - `Mock` defines that the context history consists of `MockHeader` blocks. @@ -124,19 +125,19 @@ impl HostBlock { } } -impl From for Box { +impl From for AnyConsensusState { fn from(light_block: SyntheticTmBlock) -> Self { - let cs = TMConsensusState::from(light_block.header().clone()); - cs.into_box() + let cs = TmConsensusState::from(light_block.header().clone()); + cs.into() } } -impl From for Box { +impl From for AnyConsensusState { fn from(any_block: HostBlock) -> Self { match any_block { - HostBlock::Mock(mock_header) => MockConsensusState::new(*mock_header).into_box(), + HostBlock::Mock(mock_header) => MockConsensusState::new(*mock_header).into(), HostBlock::SyntheticTendermint(light_block) => { - TMConsensusState::from(light_block.header().clone()).into_box() + TmConsensusState::from(light_block.header().clone()).into() } } } diff --git a/crates/ibc/src/mock/ics18_relayer/context.rs b/crates/ibc/src/mock/ics18_relayer/context.rs index 797d68147..d9ec46f1e 100644 --- a/crates/ibc/src/mock/ics18_relayer/context.rs +++ b/crates/ibc/src/mock/ics18_relayer/context.rs @@ -1,9 +1,8 @@ use crate::prelude::*; -use crate::core::ics02_client::client_state::ClientState; - use crate::core::ics24_host::identifier::ClientId; use crate::core::ContextError; +use crate::mock::context::AnyClientState; use crate::signer::Signer; use crate::Height; @@ -18,7 +17,7 @@ pub trait RelayerContext { /// Returns this client state for the given `client_id` on this chain. /// Wrapper over the `/abci_query?path=..` endpoint. - fn query_client_full_state(&self, client_id: &ClientId) -> Option>; + fn query_client_full_state(&self, client_id: &ClientId) -> Option; /// Temporary solution. Similar to `CosmosSDKChain::key_and_signer()` but simpler. fn signer(&self) -> Signer; @@ -27,6 +26,7 @@ pub trait RelayerContext { #[cfg(test)] mod tests { use crate::clients::ics07_tendermint::client_type as tm_client_type; + use crate::core::ics02_client::client_state::ClientStateCommon; use crate::core::ics02_client::msgs::update_client::MsgUpdateClient; use crate::core::ics02_client::msgs::ClientMsg; use crate::core::ics24_host::identifier::{ChainId, ClientId};