Skip to content

Commit 8b97ea9

Browse files
authored
Merge pull request #101 from datachainlab/fix-tendermint-lc
tendermint-lc: avoid reading states not included in a commitment Signed-off-by: Jun Kimura <jun.kimura@datachain.jp>
2 parents e594d36 + d4f7410 commit 8b97ea9

File tree

6 files changed

+137
-42
lines changed

6 files changed

+137
-42
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enclave/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

modules/tendermint-lc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ibc = { version = "0.29.0", default-features = false, features = ["serde"] }
88
serde = { version = "1.0.184", default-features = false, features = ["alloc"] }
99
log = { version = "0.4.8", default-features = false }
1010
flex-error = { version = "0.4.4", default-features = false }
11+
tendermint-light-client-verifier = { version = "0.29", features = ["rust-crypto"], default-features = false }
1112

1213
light-client = { path = "../light-client", default-features = false, features = ["ibc"] }
1314
lcp-proto = { path = "../../proto", default-features = false }

modules/tendermint-lc/src/client.rs

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::errors::Error;
22
use crate::message::{ClientMessage, Header, Misbehaviour};
33
use crate::prelude::*;
44
use crate::state::{canonicalize_state, gen_state_id, ClientState, ConsensusState};
5+
use crate::verifier::check_header_and_update_state;
56
use core::str::FromStr;
67
use crypto::Keccak256;
78
use ibc::clients::ics07_tendermint::client_state::{
@@ -35,6 +36,7 @@ use light_client::{
3536
LightClientRegistry, UpdateClientResult, VerifyMembershipResult,
3637
};
3738
use light_client::{MisbehaviourData, UpdateStateData, VerifyNonMembershipResult};
39+
#[allow(unused_imports)]
3840
use log::*;
3941

4042
#[derive(Default)]
@@ -238,37 +240,6 @@ impl TendermintLightClient {
238240
.into());
239241
}
240242

241-
// Read consensus state from the host chain store.
242-
let latest_consensus_state: ConsensusState = ctx
243-
.consensus_state(&client_id, &client_state.latest_height().into())
244-
.map_err(|_| {
245-
Error::ics02(ICS02Error::ConsensusStateNotFound {
246-
client_id: client_id.clone().into(),
247-
height: client_state.latest_height(),
248-
})
249-
})?
250-
.try_into()?;
251-
252-
debug!("latest consensus state: {:?}", latest_consensus_state);
253-
254-
let now = ctx.host_timestamp();
255-
let duration = now
256-
.duration_since(latest_consensus_state.timestamp().into_tm_time().unwrap())
257-
.map_err(|_| {
258-
Error::ics02(ICS02Error::InvalidConsensusStateTimestamp {
259-
time1: latest_consensus_state.timestamp(),
260-
time2: now.into(),
261-
})
262-
})?;
263-
264-
if client_state.expired(duration) {
265-
return Err(Error::ics02(ICS02Error::HeaderNotWithinTrustPeriod {
266-
latest_time: latest_consensus_state.timestamp(),
267-
update_time: header.timestamp(),
268-
})
269-
.into());
270-
}
271-
272243
let height = header.height().into();
273244
let header_timestamp: Time = header.timestamp().into();
274245

@@ -288,17 +259,17 @@ impl TendermintLightClient {
288259
let UpdatedState {
289260
client_state: new_client_state,
290261
consensus_state: new_consensus_state,
291-
} = client_state
292-
.check_header_and_update_state(
293-
&IBCContext::<TendermintClientState, TendermintConsensusState>::new(ctx),
294-
client_id.into(),
295-
Any::from(header.clone()).into(),
296-
)
297-
.map_err(|e| {
298-
Error::ics02(ICS02Error::HeaderVerificationFailure {
299-
reason: e.to_string(),
300-
})
301-
})?;
262+
} = check_header_and_update_state(
263+
&client_state,
264+
&IBCContext::<TendermintClientState, TendermintConsensusState>::new(ctx),
265+
client_id.into(),
266+
Any::from(header.clone()).into(),
267+
)
268+
.map_err(|e| {
269+
Error::ics02(ICS02Error::HeaderVerificationFailure {
270+
reason: e.to_string(),
271+
})
272+
})?;
302273

303274
let new_client_state = ClientState(
304275
downcast_client_state::<TendermintClientState>(new_client_state.as_ref())

modules/tendermint-lc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ pub mod client;
2626
pub mod errors;
2727
pub mod message;
2828
pub mod state;
29+
mod verifier;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::prelude::*;
2+
use ibc::{
3+
clients::ics07_tendermint::{
4+
client_state::ClientState, client_type,
5+
consensus_state::ConsensusState as TmConsensusState, error::Error,
6+
header::Header as TmHeader,
7+
},
8+
core::{
9+
ics02_client::{
10+
client_state::{ClientState as Ics2ClientState, UpdatedState},
11+
consensus_state::ConsensusState,
12+
error::ClientError,
13+
},
14+
ics24_host::{identifier::ClientId, path::ClientConsensusStatePath},
15+
ContextError, ValidationContext,
16+
},
17+
};
18+
use lcp_proto::google::protobuf::Any;
19+
use tendermint_light_client_verifier::{
20+
types::{TrustedBlockState, UntrustedBlockState},
21+
ProdVerifier, Verdict, Verifier,
22+
};
23+
24+
/// Fork of the `check_header_and_update_state` function from ibc-rs v0.29.0
25+
/// https://github.com/cosmos/ibc-rs/blob/10b47c077065a07ded9ac7f03fdb6c0980592d81/crates/ibc/src/clients/ics07_tendermint/client_state.rs#L457
26+
pub(crate) fn check_header_and_update_state(
27+
client_state: &ClientState,
28+
ctx: &dyn ValidationContext,
29+
client_id: ClientId,
30+
header: Any,
31+
) -> Result<UpdatedState, ClientError> {
32+
let client_state = downcast_tm_client_state(client_state)?.clone();
33+
let header = TmHeader::try_from(header)?;
34+
35+
if header.height().revision_number() != client_state.chain_id().version() {
36+
return Err(ClientError::ClientSpecific {
37+
description: Error::MismatchedRevisions {
38+
current_revision: client_state.chain_id().version(),
39+
update_revision: header.height().revision_number(),
40+
}
41+
.to_string(),
42+
});
43+
}
44+
45+
let trusted_client_cons_state_path =
46+
ClientConsensusStatePath::new(&client_id, &header.trusted_height);
47+
let trusted_consensus_state = downcast_tm_consensus_state(
48+
ctx.consensus_state(&trusted_client_cons_state_path)
49+
.map_err(|e| match e {
50+
ContextError::ClientError(e) => e,
51+
_ => ClientError::Other {
52+
description: e.to_string(),
53+
},
54+
})?
55+
.as_ref(),
56+
)?;
57+
58+
let trusted_state = TrustedBlockState {
59+
chain_id: &client_state.chain_id.clone().into(),
60+
header_time: trusted_consensus_state.timestamp,
61+
height: header
62+
.trusted_height
63+
.revision_height()
64+
.try_into()
65+
.map_err(|_| ClientError::ClientSpecific {
66+
description: Error::InvalidHeaderHeight {
67+
height: header.trusted_height.revision_height(),
68+
}
69+
.to_string(),
70+
})?,
71+
next_validators: &header.trusted_validator_set,
72+
next_validators_hash: trusted_consensus_state.next_validators_hash,
73+
};
74+
75+
let untrusted_state = UntrustedBlockState {
76+
signed_header: &header.signed_header,
77+
validators: &header.validator_set,
78+
// NB: This will skip the
79+
// VerificationPredicates::next_validators_match check for the
80+
// untrusted state.
81+
next_validators: None,
82+
};
83+
84+
let options = client_state.as_light_client_options()?;
85+
let now = ctx
86+
.host_timestamp()
87+
.map_err(|e| ClientError::Other {
88+
description: e.to_string(),
89+
})?
90+
.into_tm_time()
91+
.unwrap();
92+
93+
match ProdVerifier::default().verify(untrusted_state, trusted_state, &options, now) {
94+
Verdict::Success => Ok(()),
95+
Verdict::NotEnoughTrust(reason) => Err(Error::NotEnoughTrustedValsSigned { reason }),
96+
Verdict::Invalid(detail) => Err(Error::VerificationError { detail }),
97+
}?;
98+
99+
Ok(UpdatedState {
100+
client_state: client_state.with_header(header.clone())?.into_box(),
101+
consensus_state: TmConsensusState::from(header).into_box(),
102+
})
103+
}
104+
105+
fn downcast_tm_client_state(cs: &dyn Ics2ClientState) -> Result<&ClientState, ClientError> {
106+
cs.as_any()
107+
.downcast_ref::<ClientState>()
108+
.ok_or_else(|| ClientError::ClientArgsTypeMismatch {
109+
client_type: client_type(),
110+
})
111+
}
112+
113+
fn downcast_tm_consensus_state(cs: &dyn ConsensusState) -> Result<TmConsensusState, ClientError> {
114+
cs.as_any()
115+
.downcast_ref::<TmConsensusState>()
116+
.ok_or_else(|| ClientError::ClientArgsTypeMismatch {
117+
client_type: client_type(),
118+
})
119+
.map(Clone::clone)
120+
}

0 commit comments

Comments
 (0)