Skip to content

Commit cd3a266

Browse files
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 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 <[email protected]>
1 parent 5ceaef3 commit cd3a266

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2052
-1022
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Implement ADR 7, where `ClientState` objects are now statically dispatched instead
2+
of dynamically dispatched.
3+
([#296](https://github.com/cosmos/ibc-rs/issues/296))

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ resolver = "2"
44

55
members = [
66
"crates/ibc",
7+
"crates/ibc-derive",
78
]
89

910
exclude = [
1011
"ci/no-std-check",
11-
]
12+
]

crates/ibc-derive/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "ibc-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = "2"
11+
proc-macro2 = "1"
12+
quote = "1"
13+
darling = "0.20"

crates/ibc-derive/src/client_state.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
mod traits;
2+
3+
use darling::FromDeriveInput;
4+
use proc_macro2::TokenStream;
5+
use quote::quote;
6+
use syn::DeriveInput;
7+
8+
use traits::{
9+
client_state_common::impl_ClientStateCommon, client_state_execution::impl_ClientStateExecution,
10+
client_state_validation::impl_ClientStateValidation,
11+
};
12+
13+
#[derive(FromDeriveInput)]
14+
#[darling(attributes(generics))]
15+
pub(crate) struct Opts {
16+
#[darling(rename = "ClientValidationContext")]
17+
client_validation_context: syn::ExprPath,
18+
#[darling(rename = "ClientExecutionContext")]
19+
client_execution_context: syn::ExprPath,
20+
}
21+
22+
pub fn client_state_derive_impl(ast: DeriveInput) -> TokenStream {
23+
let opts = match Opts::from_derive_input(&ast) {
24+
Ok(opts) => opts,
25+
Err(e) => panic!(
26+
"{} must be annotated with #[generics(ClientValidationContext = <your ClientValidationContext>, ClientExecutionContext: <your ClientExecutionContext>)]: {e}",
27+
ast.ident
28+
),
29+
};
30+
31+
let enum_name = &ast.ident;
32+
let enum_variants = match ast.data {
33+
syn::Data::Enum(ref enum_data) => &enum_data.variants,
34+
_ => panic!("ClientState only supports enums"),
35+
};
36+
37+
let ClientStateCommon_impl_block = impl_ClientStateCommon(enum_name, enum_variants);
38+
let ClientStateValidation_impl_block =
39+
impl_ClientStateValidation(enum_name, enum_variants, &opts);
40+
let ClientStateExecution_impl_block =
41+
impl_ClientStateExecution(enum_name, enum_variants, &opts);
42+
43+
let maybe_extern_crate_stmt = if is_mock(&ast) {
44+
// Note: we must add this statement when in "mock mode"
45+
// (i.e. in ibc-rs itself) because we don't have `ibc` as a dependency,
46+
// so we need to define the `ibc` symbol to mean "the `self` crate".
47+
quote! {extern crate self as ibc;}
48+
} else {
49+
quote! {}
50+
};
51+
52+
quote! {
53+
#maybe_extern_crate_stmt
54+
55+
#ClientStateCommon_impl_block
56+
#ClientStateValidation_impl_block
57+
#ClientStateExecution_impl_block
58+
}
59+
}
60+
61+
/// We are in "mock mode" (i.e. within ibc-rs crate itself) if the user added
62+
/// a #[mock] attribute
63+
fn is_mock(ast: &DeriveInput) -> bool {
64+
for attr in &ast.attrs {
65+
let path = match attr.meta {
66+
syn::Meta::Path(ref path) => path,
67+
_ => continue,
68+
};
69+
70+
for path_segment in path.segments.iter() {
71+
if path_segment.ident == "mock" {
72+
return true;
73+
}
74+
}
75+
}
76+
77+
false
78+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//! Hosts the code generation of the `impl`s for the `ClientState` traits
2+
3+
pub mod client_state_common;
4+
pub mod client_state_execution;
5+
pub mod client_state_validation;
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use proc_macro2::{Ident, TokenStream};
2+
use quote::quote;
3+
use syn::{
4+
punctuated::{Iter, Punctuated},
5+
token::Comma,
6+
Variant,
7+
};
8+
9+
use crate::utils::{get_enum_variant_type_path, Imports};
10+
11+
pub(crate) fn impl_ClientStateCommon(
12+
client_state_enum_name: &Ident,
13+
enum_variants: &Punctuated<Variant, Comma>,
14+
) -> TokenStream {
15+
let verify_consensus_state_impl = delegate_call_in_match(
16+
client_state_enum_name,
17+
enum_variants.iter(),
18+
quote! { verify_consensus_state(cs, consensus_state) },
19+
);
20+
let client_type_impl = delegate_call_in_match(
21+
client_state_enum_name,
22+
enum_variants.iter(),
23+
quote! {client_type(cs)},
24+
);
25+
let latest_height_impl = delegate_call_in_match(
26+
client_state_enum_name,
27+
enum_variants.iter(),
28+
quote! {latest_height(cs)},
29+
);
30+
let validate_proof_height_impl = delegate_call_in_match(
31+
client_state_enum_name,
32+
enum_variants.iter(),
33+
quote! {validate_proof_height(cs, proof_height)},
34+
);
35+
let confirm_not_frozen_impl = delegate_call_in_match(
36+
client_state_enum_name,
37+
enum_variants.iter(),
38+
quote! {confirm_not_frozen(cs)},
39+
);
40+
let expired_impl = delegate_call_in_match(
41+
client_state_enum_name,
42+
enum_variants.iter(),
43+
quote! {expired(cs, elapsed)},
44+
);
45+
let verify_upgrade_client_impl = delegate_call_in_match(
46+
client_state_enum_name,
47+
enum_variants.iter(),
48+
quote! {verify_upgrade_client(cs, upgraded_client_state, upgraded_consensus_state, proof_upgrade_client, proof_upgrade_consensus_state, root)},
49+
);
50+
let verify_membership_impl = delegate_call_in_match(
51+
client_state_enum_name,
52+
enum_variants.iter(),
53+
quote! {verify_membership(cs, prefix, proof, root, path, value)},
54+
);
55+
let verify_non_membership_impl = delegate_call_in_match(
56+
client_state_enum_name,
57+
enum_variants.iter(),
58+
quote! {verify_non_membership(cs, prefix, proof, root, path)},
59+
);
60+
61+
let HostClientState = client_state_enum_name;
62+
63+
let Any = Imports::Any();
64+
let CommitmentRoot = Imports::CommitmentRoot();
65+
let CommitmentPrefix = Imports::CommitmentPrefix();
66+
let CommitmentProofBytes = Imports::CommitmentProofBytes();
67+
let ClientStateCommon = Imports::ClientStateCommon();
68+
let ClientType = Imports::ClientType();
69+
let ClientError = Imports::ClientError();
70+
let Height = Imports::Height();
71+
let MerkleProof = Imports::MerkleProof();
72+
let Path = Imports::Path();
73+
74+
quote! {
75+
impl #ClientStateCommon for #HostClientState {
76+
fn verify_consensus_state(&self, consensus_state: #Any) -> Result<(), #ClientError> {
77+
match self {
78+
#(#verify_consensus_state_impl),*
79+
}
80+
}
81+
fn client_type(&self) -> #ClientType {
82+
match self {
83+
#(#client_type_impl),*
84+
}
85+
}
86+
87+
fn latest_height(&self) -> #Height {
88+
match self {
89+
#(#latest_height_impl),*
90+
}
91+
}
92+
93+
fn validate_proof_height(&self, proof_height: #Height) -> core::result::Result<(), #ClientError> {
94+
match self {
95+
#(#validate_proof_height_impl),*
96+
}
97+
}
98+
99+
fn confirm_not_frozen(&self) -> core::result::Result<(), #ClientError> {
100+
match self {
101+
#(#confirm_not_frozen_impl),*
102+
}
103+
}
104+
105+
fn expired(&self, elapsed: core::time::Duration) -> bool {
106+
match self {
107+
#(#expired_impl),*
108+
}
109+
}
110+
111+
fn verify_upgrade_client(
112+
&self,
113+
upgraded_client_state: #Any,
114+
upgraded_consensus_state: #Any,
115+
proof_upgrade_client: #MerkleProof,
116+
proof_upgrade_consensus_state: #MerkleProof,
117+
root: &#CommitmentRoot,
118+
) -> core::result::Result<(), #ClientError> {
119+
match self {
120+
#(#verify_upgrade_client_impl),*
121+
}
122+
}
123+
124+
fn verify_membership(
125+
&self,
126+
prefix: &#CommitmentPrefix,
127+
proof: &#CommitmentProofBytes,
128+
root: &#CommitmentRoot,
129+
path: #Path,
130+
value: Vec<u8>,
131+
) -> core::result::Result<(), #ClientError> {
132+
match self {
133+
#(#verify_membership_impl),*
134+
}
135+
}
136+
137+
fn verify_non_membership(
138+
&self,
139+
prefix: &#CommitmentPrefix,
140+
proof: &#CommitmentProofBytes,
141+
root: &#CommitmentRoot,
142+
path: #Path,
143+
) -> core::result::Result<(), #ClientError> {
144+
match self {
145+
#(#verify_non_membership_impl),*
146+
}
147+
}
148+
}
149+
150+
}
151+
}
152+
153+
///
154+
/// Generates the per-enum variant function call delegation token streams.
155+
///
156+
/// enum_name: The user's enum identifier (e.g. `HostClientState`)
157+
/// enum_variants: An iterator of all enum variants (e.g. `[HostClientState::Tendermint, HostClientState::Mock]`)
158+
/// fn_call: The tokens for the function call. Fully-qualified syntax is assumed, where the name for `self`
159+
/// is `cs` (e.g. `client_type(cs)`).
160+
///
161+
/// For example,
162+
///
163+
/// ```ignore
164+
/// impl ClientStateCommon for HostClientState {
165+
/// fn client_type(&self) -> ClientType {
166+
/// match self {
167+
/// // BEGIN code generated
168+
///
169+
/// // 1st TokenStream returned
170+
/// HostClientState::Tendermint(cs) => <TmClientState as ClientStateCommon>::client_type(cs),
171+
/// // 2nd TokenStream returned
172+
/// HostClientState::Mock(cs) => <MockClientState as ClientStateCommon>::client_type(cs),
173+
///
174+
/// // END code generated
175+
/// }
176+
/// }
177+
/// }
178+
/// ```
179+
///
180+
fn delegate_call_in_match(
181+
enum_name: &Ident,
182+
enum_variants: Iter<'_, Variant>,
183+
fn_call: TokenStream,
184+
) -> Vec<TokenStream> {
185+
let ClientStateCommon = Imports::ClientStateCommon();
186+
187+
enum_variants
188+
.map(|variant| {
189+
let variant_name = &variant.ident;
190+
let variant_type_name = get_enum_variant_type_path(variant);
191+
192+
quote! {
193+
#enum_name::#variant_name(cs) => <#variant_type_name as #ClientStateCommon>::#fn_call
194+
}
195+
})
196+
.collect()
197+
}

0 commit comments

Comments
 (0)