Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor TendermintClientState::verify_header into CGP component #937

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Initial refactoring of TendermintClientState::verify_header into CGP …
…component
soareschen committed Oct 26, 2023
commit 06fab7d539bad247fd95de4d0d32fe87164f67ad
213 changes: 137 additions & 76 deletions crates/ibc-cosmos-components/src/client/verify.rs
Original file line number Diff line number Diff line change
@@ -1,106 +1,167 @@
use alloc::format;
use core::fmt::Display;

use cgp_core::prelude::*;
use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState;
use ibc::clients::ics07_tendermint::error::{Error, IntoResult};
use ibc::clients::ics07_tendermint::header::Header as TmHeader;
use ibc::clients::ics07_tendermint::ValidationContext as TmValidationContext;
use ibc::core::ics02_client::error::ClientError;
use ibc::core::ics24_host::identifier::{ChainId, ClientId};
use ibc::core::ics24_host::path::ClientConsensusStatePath;
use ibc::core::timestamp::Timestamp;
use ibc::core::ContextError;
use ibc::prelude::ToString;
use tendermint_light_client_verifier::options::Options;
use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState};
use tendermint_light_client_verifier::{ProdVerifier, Verifier};

pub trait HasClientStateMethods {
pub trait HasChainId {
fn chain_id(&self) -> &ChainId;
}

pub trait HasLightClientOptions {
fn light_client_options(&self) -> Options;
}

pub trait HasVerifier {
fn verifier(&self) -> &ProdVerifier;
}

pub fn verify_header<ClientValidationContext, ClientState>(
client_state: &ClientState,
ctx: &ClientValidationContext,
client_id: &ClientId,
header: TmHeader,
) -> Result<(), ClientError>
pub trait HasHostTimestamp {
fn host_timestamp(&self) -> Result<Timestamp, ContextError>;
}

pub trait HasConsensusState: HasAnyConsensusStateType {
fn consensus_state(
&self,
client_cons_state_path: &ClientConsensusStatePath,
) -> Result<Self::AnyConsensusState, ContextError>;
}

pub trait HasAnyConsensusStateType {
type AnyConsensusState;
}

pub trait HasClientStateType<ClientType> {
type ClientState;
}

pub trait HasModule {
type ModuleId;

type Module;
}

pub trait CanGetRoute: HasModule {
fn get_route(&self, module_id: &Self::ModuleId) -> &Self::Module;
}

pub struct TendermintClientType;

#[derive_component(HeaderVerifierComponent, HeaderVerifier<Context>)]
pub trait CanVerifyHeader<ClientType>: HasClientStateType<ClientType> + HasErrorType {
fn verify_header(
&self,
client_state: &Self::ClientState,
client_id: &ClientId,
header: TmHeader,
) -> Result<(), Self::Error>;
}

pub struct VerifyTendermintHeader;

impl<Context> HeaderVerifier<Context, TendermintClientType> for VerifyTendermintHeader
where
ClientState: HasClientStateMethods,
ClientValidationContext: TmValidationContext,
Context: HasConsensusState
+ HasHostTimestamp
+ HasClientStateType<TendermintClientType>
+ HasErrorType<Error = ClientError>,
Context::AnyConsensusState: TryInto<TmConsensusState>,
<Context::AnyConsensusState as TryInto<TmConsensusState>>::Error: Display,
Context::ClientState: HasChainId + HasLightClientOptions + HasVerifier,
{
// Checks that the header fields are valid.
header.validate_basic()?;
fn verify_header(
ctx: &Context,
client_state: &Context::ClientState,
client_id: &ClientId,
header: TmHeader,
) -> Result<(), ClientError>
where
Context: HasConsensusState + HasHostTimestamp + HasClientStateType<TendermintClientType>,
Context::AnyConsensusState: TryInto<TmConsensusState>,
<Context::AnyConsensusState as TryInto<TmConsensusState>>::Error: Display,
Context::ClientState: HasChainId + HasLightClientOptions + HasVerifier,
{
// Checks that the header fields are valid.
header.validate_basic()?;

// The tendermint-light-client crate though works on heights that are assumed
// to have the same revision number. We ensure this here.
header.verify_chain_id_version_matches_height(client_state.chain_id())?;
// The tendermint-light-client crate though works on heights that are assumed
// to have the same revision number. We ensure this here.
header.verify_chain_id_version_matches_height(client_state.chain_id())?;

// Delegate to tendermint-light-client, which contains the required checks
// of the new header against the trusted consensus state.
{
let trusted_state = {
let trusted_client_cons_state_path =
ClientConsensusStatePath::new(client_id, &header.trusted_height);
let trusted_consensus_state: TmConsensusState = ctx
.consensus_state(&trusted_client_cons_state_path)?
.try_into()
.map_err(|err| ClientError::Other {
description: err.to_string(),
})?;

check_header_trusted_next_validator_set(&header, &trusted_consensus_state)?;

TrustedBlockState {
chain_id: &client_state
.chain_id()
.to_string()
.try_into()
.map_err(|e| ClientError::Other {
description: format!("failed to parse chain id: {}", e),
})?,
header_time: trusted_consensus_state.timestamp,
height: header
.trusted_height
.revision_height()
.try_into()
.map_err(|_| ClientError::ClientSpecific {
description: Error::InvalidHeaderHeight {
height: header.trusted_height.revision_height(),
}
.to_string(),
})?,
next_validators: &header.trusted_next_validator_set,
next_validators_hash: trusted_consensus_state.next_validators_hash,
}
};

let untrusted_state = UntrustedBlockState {
signed_header: &header.signed_header,
validators: &header.validator_set,
// NB: This will skip the
// VerificationPredicates::next_validators_match check for the
// untrusted state.
next_validators: None,
};

let options = client_state.light_client_options();
let now =
ctx.host_timestamp()?
.into_tm_time()
.ok_or_else(|| ClientError::ClientSpecific {
// Delegate to tendermint-light-client, which contains the required checks
// of the new header against the trusted consensus state.
{
let trusted_state =
{
let trusted_client_cons_state_path =
ClientConsensusStatePath::new(client_id, &header.trusted_height);
let trusted_consensus_state: TmConsensusState = ctx
.consensus_state(&trusted_client_cons_state_path)?
.try_into()
.map_err(|err| ClientError::Other {
description: err.to_string(),
})?;

check_header_trusted_next_validator_set(&header, &trusted_consensus_state)?;

TrustedBlockState {
chain_id: &client_state
.chain_id()
.to_string()
.try_into()
.map_err(|e| ClientError::Other {
description: format!("failed to parse chain id: {}", e),
})?,
header_time: trusted_consensus_state.timestamp,
height: header.trusted_height.revision_height().try_into().map_err(
|_| ClientError::ClientSpecific {
description: Error::InvalidHeaderHeight {
height: header.trusted_height.revision_height(),
}
.to_string(),
},
)?,
next_validators: &header.trusted_next_validator_set,
next_validators_hash: trusted_consensus_state.next_validators_hash,
}
};

let untrusted_state = UntrustedBlockState {
signed_header: &header.signed_header,
validators: &header.validator_set,
// NB: This will skip the
// VerificationPredicates::next_validators_match check for the
// untrusted state.
next_validators: None,
};

let options = client_state.light_client_options();
let now = ctx.host_timestamp()?.into_tm_time().ok_or_else(|| {
ClientError::ClientSpecific {
description: "host timestamp is not a valid TM timestamp".to_string(),
})?;
}
})?;

// main header verification, delegated to the tendermint-light-client crate.
client_state
.verifier()
.verify_update_header(untrusted_state, trusted_state, &options, now)
.into_result()?;
}
// main header verification, delegated to the tendermint-light-client crate.
client_state
.verifier()
.verify_update_header(untrusted_state, trusted_state, &options, now)
.into_result()?;
}

Ok(())
Ok(())
}
}

fn check_header_trusted_next_validator_set(
1 change: 1 addition & 0 deletions crates/ibc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -66,6 +66,7 @@ num-traits = { version = "0.2.15", default-features = false }
derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display", "try_into"] }
uint = { version = "0.9", default-features = false }
primitive-types = { version = "0.12.0", default-features = false, features = ["serde_no_std"] }
cgp-core = { version = "0.1.0" }

## for codec encode or decode
parity-scale-codec = { version = "3.0.0", default-features = false, features = ["full"], optional = true }
Original file line number Diff line number Diff line change
@@ -23,6 +23,8 @@ impl ClientState {
where
ClientValidationContext: TmValidationContext,
{
// VerifyTendermintHeader::verify_header(ctx, self, client_id, header)

// Checks that the header fields are valid.
header.validate_basic()?;