Skip to content

Commit

Permalink
Static ClientState API (#683)
Browse files Browse the repository at this point in the history
* 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 282424f.

* 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 <farhad.shabani@gmail.com>
  • Loading branch information
plafer and Farhad-Shabani authored Jul 5, 2023
1 parent 5ceaef3 commit cd3a266
Show file tree
Hide file tree
Showing 58 changed files with 2,052 additions and 1,022 deletions.
Original file line number Diff line number Diff line change
@@ -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))
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ resolver = "2"

members = [
"crates/ibc",
"crates/ibc-derive",
]

exclude = [
"ci/no-std-check",
]
]
13 changes: 13 additions & 0 deletions crates/ibc-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
78 changes: 78 additions & 0 deletions crates/ibc-derive/src/client_state.rs
Original file line number Diff line number Diff line change
@@ -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 = <your ClientValidationContext>, ClientExecutionContext: <your 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
}
5 changes: 5 additions & 0 deletions crates/ibc-derive/src/client_state/traits.rs
Original file line number Diff line number Diff line change
@@ -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;
197 changes: 197 additions & 0 deletions crates/ibc-derive/src/client_state/traits/client_state_common.rs
Original file line number Diff line number Diff line change
@@ -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<Variant, Comma>,
) -> 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<u8>,
) -> 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) => <TmClientState as ClientStateCommon>::client_type(cs),
/// // 2nd TokenStream returned
/// HostClientState::Mock(cs) => <MockClientState as ClientStateCommon>::client_type(cs),
///
/// // END code generated
/// }
/// }
/// }
/// ```
///
fn delegate_call_in_match(
enum_name: &Ident,
enum_variants: Iter<'_, Variant>,
fn_call: TokenStream,
) -> Vec<TokenStream> {
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()
}
Loading

0 comments on commit cd3a266

Please sign in to comment.