Skip to content

Commit

Permalink
Merge pull request #101 from datachainlab/fix-tendermint-lc
Browse files Browse the repository at this point in the history
tendermint-lc: avoid reading states not included in a commitment

Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele authored Feb 6, 2024
2 parents e594d36 + d4f7410 commit 8b97ea9
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 42 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions enclave/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions modules/tendermint-lc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ibc = { version = "0.29.0", default-features = false, features = ["serde"] }
serde = { version = "1.0.184", default-features = false, features = ["alloc"] }
log = { version = "0.4.8", default-features = false }
flex-error = { version = "0.4.4", default-features = false }
tendermint-light-client-verifier = { version = "0.29", features = ["rust-crypto"], default-features = false }

light-client = { path = "../light-client", default-features = false, features = ["ibc"] }
lcp-proto = { path = "../../proto", default-features = false }
Expand Down
55 changes: 13 additions & 42 deletions modules/tendermint-lc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::errors::Error;
use crate::message::{ClientMessage, Header, Misbehaviour};
use crate::prelude::*;
use crate::state::{canonicalize_state, gen_state_id, ClientState, ConsensusState};
use crate::verifier::check_header_and_update_state;
use core::str::FromStr;
use crypto::Keccak256;
use ibc::clients::ics07_tendermint::client_state::{
Expand Down Expand Up @@ -35,6 +36,7 @@ use light_client::{
LightClientRegistry, UpdateClientResult, VerifyMembershipResult,
};
use light_client::{MisbehaviourData, UpdateStateData, VerifyNonMembershipResult};
#[allow(unused_imports)]
use log::*;

#[derive(Default)]
Expand Down Expand Up @@ -238,37 +240,6 @@ impl TendermintLightClient {
.into());
}

// Read consensus state from the host chain store.
let latest_consensus_state: ConsensusState = ctx
.consensus_state(&client_id, &client_state.latest_height().into())
.map_err(|_| {
Error::ics02(ICS02Error::ConsensusStateNotFound {
client_id: client_id.clone().into(),
height: client_state.latest_height(),
})
})?
.try_into()?;

debug!("latest consensus state: {:?}", latest_consensus_state);

let now = ctx.host_timestamp();
let duration = now
.duration_since(latest_consensus_state.timestamp().into_tm_time().unwrap())
.map_err(|_| {
Error::ics02(ICS02Error::InvalidConsensusStateTimestamp {
time1: latest_consensus_state.timestamp(),
time2: now.into(),
})
})?;

if client_state.expired(duration) {
return Err(Error::ics02(ICS02Error::HeaderNotWithinTrustPeriod {
latest_time: latest_consensus_state.timestamp(),
update_time: header.timestamp(),
})
.into());
}

let height = header.height().into();
let header_timestamp: Time = header.timestamp().into();

Expand All @@ -288,17 +259,17 @@ impl TendermintLightClient {
let UpdatedState {
client_state: new_client_state,
consensus_state: new_consensus_state,
} = client_state
.check_header_and_update_state(
&IBCContext::<TendermintClientState, TendermintConsensusState>::new(ctx),
client_id.into(),
Any::from(header.clone()).into(),
)
.map_err(|e| {
Error::ics02(ICS02Error::HeaderVerificationFailure {
reason: e.to_string(),
})
})?;
} = check_header_and_update_state(
&client_state,
&IBCContext::<TendermintClientState, TendermintConsensusState>::new(ctx),
client_id.into(),
Any::from(header.clone()).into(),
)
.map_err(|e| {
Error::ics02(ICS02Error::HeaderVerificationFailure {
reason: e.to_string(),
})
})?;

let new_client_state = ClientState(
downcast_client_state::<TendermintClientState>(new_client_state.as_ref())
Expand Down
1 change: 1 addition & 0 deletions modules/tendermint-lc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ pub mod client;
pub mod errors;
pub mod message;
pub mod state;
mod verifier;
120 changes: 120 additions & 0 deletions modules/tendermint-lc/src/verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::prelude::*;
use ibc::{
clients::ics07_tendermint::{
client_state::ClientState, client_type,
consensus_state::ConsensusState as TmConsensusState, error::Error,
header::Header as TmHeader,
},
core::{
ics02_client::{
client_state::{ClientState as Ics2ClientState, UpdatedState},
consensus_state::ConsensusState,
error::ClientError,
},
ics24_host::{identifier::ClientId, path::ClientConsensusStatePath},
ContextError, ValidationContext,
},
};
use lcp_proto::google::protobuf::Any;
use tendermint_light_client_verifier::{
types::{TrustedBlockState, UntrustedBlockState},
ProdVerifier, Verdict, Verifier,
};

/// Fork of the `check_header_and_update_state` function from ibc-rs v0.29.0
/// https://github.com/cosmos/ibc-rs/blob/10b47c077065a07ded9ac7f03fdb6c0980592d81/crates/ibc/src/clients/ics07_tendermint/client_state.rs#L457
pub(crate) fn check_header_and_update_state(
client_state: &ClientState,
ctx: &dyn ValidationContext,
client_id: ClientId,
header: Any,
) -> Result<UpdatedState, ClientError> {
let client_state = downcast_tm_client_state(client_state)?.clone();
let header = TmHeader::try_from(header)?;

if header.height().revision_number() != client_state.chain_id().version() {
return Err(ClientError::ClientSpecific {
description: Error::MismatchedRevisions {
current_revision: client_state.chain_id().version(),
update_revision: header.height().revision_number(),
}
.to_string(),
});
}

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)
.map_err(|e| match e {
ContextError::ClientError(e) => e,
_ => ClientError::Other {
description: e.to_string(),
},
})?
.as_ref(),
)?;

let trusted_state = TrustedBlockState {
chain_id: &client_state.chain_id.clone().into(),
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_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.as_light_client_options()?;
let now = ctx
.host_timestamp()
.map_err(|e| ClientError::Other {
description: e.to_string(),
})?
.into_tm_time()
.unwrap();

match ProdVerifier::default().verify(untrusted_state, trusted_state, &options, now) {
Verdict::Success => Ok(()),
Verdict::NotEnoughTrust(reason) => Err(Error::NotEnoughTrustedValsSigned { reason }),
Verdict::Invalid(detail) => Err(Error::VerificationError { detail }),
}?;

Ok(UpdatedState {
client_state: client_state.with_header(header.clone())?.into_box(),
consensus_state: TmConsensusState::from(header).into_box(),
})
}

fn downcast_tm_client_state(cs: &dyn Ics2ClientState) -> Result<&ClientState, ClientError> {
cs.as_any()
.downcast_ref::<ClientState>()
.ok_or_else(|| ClientError::ClientArgsTypeMismatch {
client_type: client_type(),
})
}

fn downcast_tm_consensus_state(cs: &dyn ConsensusState) -> Result<TmConsensusState, ClientError> {
cs.as_any()
.downcast_ref::<TmConsensusState>()
.ok_or_else(|| ClientError::ClientArgsTypeMismatch {
client_type: client_type(),
})
.map(Clone::clone)
}

0 comments on commit 8b97ea9

Please sign in to comment.