1use core::str::FromStr;
2use core::time::Duration;
3
4use ibc::clients::tendermint::client_state::consensus_state_status;
5use ibc::core::client::context::prelude::*;
6use ibc::core::client::types::error::{ClientError, UpgradeClientError};
7use ibc::core::client::types::{Height, Status};
8use ibc::core::commitment_types::commitment::{
9 CommitmentPrefix, CommitmentProofBytes, CommitmentRoot,
10};
11use ibc::core::host::types::error::{DecodingError, HostError};
12use ibc::core::host::types::identifiers::{ClientId, ClientType};
13use ibc::core::host::types::path::{ClientConsensusStatePath, ClientStatePath, Path, PathBytes};
14use ibc::core::primitives::prelude::*;
15use ibc::core::primitives::Timestamp;
16use ibc::primitives::proto::{Any, Protobuf};
17
18use crate::testapp::ibc::clients::mock::client_state::client_type as mock_client_type;
19use crate::testapp::ibc::clients::mock::consensus_state::MockConsensusState;
20use crate::testapp::ibc::clients::mock::header::{MockHeader, MOCK_HEADER_TYPE_URL};
21use crate::testapp::ibc::clients::mock::misbehaviour::{Misbehaviour, MOCK_MISBEHAVIOUR_TYPE_URL};
22use crate::testapp::ibc::clients::mock::proto::ClientState as RawMockClientState;
23
24pub const MOCK_CLIENT_STATE_TYPE_URL: &str = "/ibc.mock.ClientState";
25pub const MOCK_CLIENT_TYPE: &str = "9999-mock";
26
27pub fn client_type() -> ClientType {
28 ClientType::from_str(MOCK_CLIENT_TYPE).expect("never fails because it's valid client type")
29}
30
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub struct MockClientState {
36 pub header: MockHeader,
37 pub trusting_period: Duration,
38 pub frozen: bool,
39}
40
41impl MockClientState {
42 pub fn new(header: MockHeader) -> Self {
46 Self {
47 header,
48 trusting_period: Duration::from_secs(64000),
49 frozen: false,
50 }
51 }
52
53 pub fn latest_height(&self) -> Height {
54 self.header.height()
55 }
56
57 pub fn refresh_time(&self) -> Option<Duration> {
58 None
59 }
60
61 pub fn with_trusting_period(self, trusting_period: Duration) -> Self {
62 Self {
63 trusting_period,
64 ..self
65 }
66 }
67
68 pub fn frozen(self) -> Self {
69 Self {
70 frozen: true,
71 ..self
72 }
73 }
74
75 pub fn unfrozen(self) -> Self {
76 Self {
77 frozen: false,
78 ..self
79 }
80 }
81
82 pub fn is_frozen(&self) -> bool {
83 self.frozen
84 }
85
86 fn expired(&self, elapsed: Duration) -> bool {
87 elapsed > self.trusting_period
88 }
89}
90
91impl Protobuf<RawMockClientState> for MockClientState {}
92
93impl TryFrom<RawMockClientState> for MockClientState {
94 type Error = DecodingError;
95
96 fn try_from(raw: RawMockClientState) -> Result<Self, Self::Error> {
97 Ok(Self {
98 header: raw
99 .header
100 .ok_or(DecodingError::missing_raw_data("mock client state header"))?
101 .try_into()?,
102 trusting_period: Duration::from_nanos(raw.trusting_period),
103 frozen: raw.frozen,
104 })
105 }
106}
107
108impl From<MockClientState> for RawMockClientState {
109 fn from(value: MockClientState) -> Self {
110 Self {
111 header: Some(value.header.into()),
112 trusting_period: value
113 .trusting_period
114 .as_nanos()
115 .try_into()
116 .expect("no overflow"),
117 frozen: value.frozen,
118 }
119 }
120}
121
122impl Protobuf<Any> for MockClientState {}
123
124impl TryFrom<Any> for MockClientState {
125 type Error = DecodingError;
126
127 fn try_from(raw: Any) -> Result<Self, Self::Error> {
128 if let MOCK_CLIENT_STATE_TYPE_URL = raw.type_url.as_str() {
129 Protobuf::<RawMockClientState>::decode(raw.value.as_ref()).map_err(Into::into)
130 } else {
131 Err(DecodingError::MismatchedResourceName {
132 expected: MOCK_CLIENT_STATE_TYPE_URL.to_string(),
133 actual: raw.type_url,
134 })
135 }
136 }
137}
138
139impl From<MockClientState> for Any {
140 fn from(client_state: MockClientState) -> Self {
141 Self {
142 type_url: MOCK_CLIENT_STATE_TYPE_URL.to_string(),
143 value: Protobuf::<RawMockClientState>::encode_vec(client_state),
144 }
145 }
146}
147
148pub trait MockClientContext {
149 fn host_timestamp(&self) -> Result<Timestamp, HostError>;
151
152 fn host_height(&self) -> Result<Height, HostError>;
154}
155
156impl ClientStateCommon for MockClientState {
157 fn verify_consensus_state(
158 &self,
159 consensus_state: Any,
160 host_timestamp: &Timestamp,
161 ) -> Result<(), ClientError> {
162 let mock_consensus_state = MockConsensusState::try_from(consensus_state)?;
163
164 if consensus_state_status(&mock_consensus_state, host_timestamp, self.trusting_period)?
165 .is_expired()
166 {
167 return Err(ClientError::InvalidStatus(Status::Expired));
168 }
169
170 Ok(())
171 }
172
173 fn client_type(&self) -> ClientType {
174 mock_client_type()
175 }
176
177 fn latest_height(&self) -> Height {
178 self.header.height()
179 }
180
181 fn validate_proof_height(&self, proof_height: Height) -> Result<(), ClientError> {
182 if self.latest_height() < proof_height {
183 return Err(ClientError::InsufficientProofHeight {
184 actual: self.latest_height(),
185 expected: proof_height,
186 });
187 }
188 Ok(())
189 }
190
191 fn serialize_path(&self, path: Path) -> Result<PathBytes, ClientError> {
192 Ok(path.to_string().into_bytes().into())
193 }
194
195 fn verify_upgrade_client(
196 &self,
197 upgraded_client_state: Any,
198 upgraded_consensus_state: Any,
199 _proof_upgrade_client: CommitmentProofBytes,
200 _proof_upgrade_consensus_state: CommitmentProofBytes,
201 _root: &CommitmentRoot,
202 ) -> Result<(), ClientError> {
203 let upgraded_mock_client_state = Self::try_from(upgraded_client_state)?;
204 MockConsensusState::try_from(upgraded_consensus_state)?;
205 if self.latest_height() >= upgraded_mock_client_state.latest_height() {
206 return Err(UpgradeClientError::InsufficientUpgradeHeight {
207 upgraded_height: self.latest_height(),
208 client_height: upgraded_mock_client_state.latest_height(),
209 })?;
210 }
211 Ok(())
212 }
213
214 fn verify_membership_raw(
215 &self,
216 _prefix: &CommitmentPrefix,
217 _proof: &CommitmentProofBytes,
218 _root: &CommitmentRoot,
219 _path: PathBytes,
220 _value: Vec<u8>,
221 ) -> Result<(), ClientError> {
222 Ok(())
223 }
224
225 fn verify_non_membership_raw(
226 &self,
227 _prefix: &CommitmentPrefix,
228 _proof: &CommitmentProofBytes,
229 _root: &CommitmentRoot,
230 _path: PathBytes,
231 ) -> Result<(), ClientError> {
232 Ok(())
233 }
234}
235
236impl<V> ClientStateValidation<V> for MockClientState
237where
238 V: ClientValidationContext + MockClientContext,
239 MockConsensusState: Convertible<V::ConsensusStateRef>,
240 <MockConsensusState as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
241{
242 fn verify_client_message(
243 &self,
244 _ctx: &V,
245 _client_id: &ClientId,
246 client_message: Any,
247 ) -> Result<(), ClientError> {
248 match client_message.type_url.as_str() {
249 MOCK_HEADER_TYPE_URL => {
250 let _header = MockHeader::try_from(client_message)?;
251 }
252 MOCK_MISBEHAVIOUR_TYPE_URL => {
253 let _misbehaviour = Misbehaviour::try_from(client_message)?;
254 }
255 _ => {}
256 }
257
258 Ok(())
259 }
260
261 fn check_for_misbehaviour(
262 &self,
263 _ctx: &V,
264 _client_id: &ClientId,
265 client_message: Any,
266 ) -> Result<bool, ClientError> {
267 match client_message.type_url.as_str() {
268 MOCK_HEADER_TYPE_URL => Ok(false),
269 MOCK_MISBEHAVIOUR_TYPE_URL => {
270 let misbehaviour = Misbehaviour::try_from(client_message)?;
271 let header_1 = misbehaviour.header1;
272 let header_2 = misbehaviour.header2;
273
274 let header_heights_equal = header_1.height() == header_2.height();
275 let headers_are_in_future = self.latest_height() < header_1.height();
276
277 Ok(header_heights_equal && headers_are_in_future)
278 }
279 header_type => Err(ClientError::InvalidHeaderType(header_type.to_owned())),
280 }
281 }
282
283 fn status(&self, ctx: &V, client_id: &ClientId) -> Result<Status, ClientError> {
284 if self.is_frozen() {
285 return Ok(Status::Frozen);
286 }
287
288 let latest_consensus_state: MockConsensusState = {
289 match ctx.consensus_state(&ClientConsensusStatePath::new(
290 client_id.clone(),
291 self.latest_height().revision_number(),
292 self.latest_height().revision_height(),
293 )) {
294 Ok(cs) => cs.try_into().map_err(Into::into)?,
295 Err(_) => return Ok(Status::Expired),
298 }
299 };
300
301 let now = ctx.host_timestamp()?;
302 let elapsed_since_latest_consensus_state = now
303 .duration_since(&latest_consensus_state.timestamp())
304 .ok_or(ClientError::InvalidConsensusStateTimestamp(
305 latest_consensus_state.timestamp(),
306 ))?;
307
308 if self.expired(elapsed_since_latest_consensus_state) {
309 return Ok(Status::Expired);
310 }
311
312 Ok(Status::Active)
313 }
314
315 fn check_substitute(&self, _ctx: &V, _substitute_client_state: Any) -> Result<(), ClientError> {
316 Ok(())
317 }
318}
319
320impl<E> ClientStateExecution<E> for MockClientState
321where
322 E: ClientExecutionContext + MockClientContext,
323 E::ClientStateRef: From<Self>,
324 MockConsensusState: Convertible<E::ConsensusStateRef>,
325 <MockConsensusState as TryFrom<E::ConsensusStateRef>>::Error: Into<ClientError>,
326{
327 fn initialise(
328 &self,
329 ctx: &mut E,
330 client_id: &ClientId,
331 consensus_state: Any,
332 ) -> Result<(), ClientError> {
333 let mock_consensus_state: MockConsensusState = consensus_state.try_into()?;
334
335 ctx.store_client_state(ClientStatePath::new(client_id.clone()), (*self).into())?;
336 ctx.store_consensus_state(
337 ClientConsensusStatePath::new(
338 client_id.clone(),
339 self.latest_height().revision_number(),
340 self.latest_height().revision_height(),
341 ),
342 mock_consensus_state.into(),
343 )?;
344 ctx.store_update_meta(
345 client_id.clone(),
346 self.latest_height(),
347 ctx.host_timestamp()?,
348 ctx.host_height()?,
349 )?;
350
351 Ok(())
352 }
353
354 fn update_state(
355 &self,
356 ctx: &mut E,
357 client_id: &ClientId,
358 header: Any,
359 ) -> Result<Vec<Height>, ClientError> {
360 let header = MockHeader::try_from(header)?;
361 let header_height = header.height;
362
363 let new_client_state = Self::new(header);
364 let new_consensus_state = MockConsensusState::new(header);
365
366 ctx.store_consensus_state(
367 ClientConsensusStatePath::new(
368 client_id.clone(),
369 new_client_state.latest_height().revision_number(),
370 new_client_state.latest_height().revision_height(),
371 ),
372 new_consensus_state.into(),
373 )?;
374 ctx.store_client_state(
375 ClientStatePath::new(client_id.clone()),
376 new_client_state.into(),
377 )?;
378 ctx.store_update_meta(
379 client_id.clone(),
380 header_height,
381 ctx.host_timestamp()?,
382 ctx.host_height()?,
383 )?;
384
385 Ok(vec![header_height])
386 }
387
388 fn update_state_on_misbehaviour(
389 &self,
390 ctx: &mut E,
391 client_id: &ClientId,
392 _client_message: Any,
393 ) -> Result<(), ClientError> {
394 let frozen_client_state = self.frozen();
395
396 ctx.store_client_state(
397 ClientStatePath::new(client_id.clone()),
398 frozen_client_state.into(),
399 )?;
400
401 Ok(())
402 }
403
404 fn update_state_on_upgrade(
405 &self,
406 ctx: &mut E,
407 client_id: &ClientId,
408 upgraded_client_state: Any,
409 upgraded_consensus_state: Any,
410 ) -> Result<Height, ClientError> {
411 let new_client_state = Self::try_from(upgraded_client_state)?;
412 let new_consensus_state: MockConsensusState = upgraded_consensus_state.try_into()?;
413
414 let latest_height = new_client_state.latest_height();
415
416 ctx.store_consensus_state(
417 ClientConsensusStatePath::new(
418 client_id.clone(),
419 latest_height.revision_number(),
420 latest_height.revision_height(),
421 ),
422 new_consensus_state.into(),
423 )?;
424 ctx.store_client_state(
425 ClientStatePath::new(client_id.clone()),
426 new_client_state.into(),
427 )?;
428
429 let host_timestamp = ctx.host_timestamp()?;
430 let host_height = ctx.host_height()?;
431
432 ctx.store_update_meta(
433 client_id.clone(),
434 latest_height,
435 host_timestamp,
436 host_height,
437 )?;
438
439 Ok(latest_height)
440 }
441
442 fn update_on_recovery(
443 &self,
444 ctx: &mut E,
445 subject_client_id: &ClientId,
446 substitute_client_state: Any,
447 substitute_consensus_state: Any,
448 ) -> Result<(), ClientError> {
449 let substitute_client_state = MockClientState::try_from(substitute_client_state)?;
450
451 let latest_height = substitute_client_state.latest_height();
452
453 let new_mock_client_state = MockClientState {
454 frozen: false,
455 ..substitute_client_state
456 };
457
458 let host_timestamp = ctx.host_timestamp()?;
459 let host_height = ctx.host_height()?;
460
461 let mock_consensus_state: MockConsensusState = substitute_consensus_state.try_into()?;
462
463 ctx.store_consensus_state(
464 ClientConsensusStatePath::new(
465 subject_client_id.clone(),
466 new_mock_client_state.latest_height().revision_number(),
467 new_mock_client_state.latest_height().revision_height(),
468 ),
469 mock_consensus_state.into(),
470 )?;
471
472 ctx.store_client_state(
473 ClientStatePath::new(subject_client_id.clone()),
474 new_mock_client_state.into(),
475 )?;
476
477 ctx.store_update_meta(
478 subject_client_id.clone(),
479 latest_height,
480 host_timestamp,
481 host_height,
482 )?;
483
484 Ok(())
485 }
486}
487
488impl From<MockConsensusState> for MockClientState {
489 fn from(cs: MockConsensusState) -> Self {
490 Self::new(cs.header)
491 }
492}
493
494#[cfg(test)]
495mod test {
496 #[cfg(feature = "serde")]
497 #[test]
498 fn test_any_client_state_to_json() {
499 use ibc::primitives::proto::Any;
500
501 use super::{MockClientState, MockHeader};
502
503 let client_state = MockClientState::new(MockHeader::default());
504 let expected =
505 r#"{"typeUrl":"/ibc.mock.ClientState","value":"Cg4KAhABEICAiJ69yIGbFxCAgJDK0sYO"}"#;
506 let json = serde_json::to_string(&Any::from(client_state)).unwrap();
507 assert_eq!(json, expected);
508
509 let proto_any = serde_json::from_str::<Any>(expected).unwrap();
510 assert_eq!(proto_any, Any::from(client_state));
511 }
512}