ibc_client_tendermint/client_state/
execution.rs

1use ibc_client_tendermint_types::{
2    ClientState as ClientStateType, ConsensusState as ConsensusStateType, Header as TmHeader,
3};
4use ibc_core_client::context::prelude::*;
5use ibc_core_client::types::error::ClientError;
6use ibc_core_client::types::Height;
7use ibc_core_host::types::identifiers::ClientId;
8use ibc_core_host::types::path::{ClientConsensusStatePath, ClientStatePath};
9use ibc_primitives::prelude::*;
10use ibc_primitives::proto::Any;
11use ibc_primitives::{IntoHostTime, TimestampError};
12
13use super::ClientState;
14
15impl<E> ClientStateExecution<E> for ClientState
16where
17    E: ExtClientExecutionContext,
18    E::ClientStateRef: From<ClientStateType>,
19    ConsensusStateType: Convertible<E::ConsensusStateRef>,
20    <ConsensusStateType as TryFrom<E::ConsensusStateRef>>::Error: Into<ClientError>,
21{
22    fn initialise(
23        &self,
24        ctx: &mut E,
25        client_id: &ClientId,
26        consensus_state: Any,
27    ) -> Result<(), ClientError> {
28        initialise(self.inner(), ctx, client_id, consensus_state)
29    }
30
31    fn update_state(
32        &self,
33        ctx: &mut E,
34        client_id: &ClientId,
35        header: Any,
36    ) -> Result<Vec<Height>, ClientError> {
37        update_state(self.inner(), ctx, client_id, header)
38    }
39
40    fn update_state_on_misbehaviour(
41        &self,
42        ctx: &mut E,
43        client_id: &ClientId,
44        client_message: Any,
45    ) -> Result<(), ClientError> {
46        update_on_misbehaviour(self.inner(), ctx, client_id, client_message)
47    }
48
49    fn update_state_on_upgrade(
50        &self,
51        ctx: &mut E,
52        client_id: &ClientId,
53        upgraded_client_state: Any,
54        upgraded_consensus_state: Any,
55    ) -> Result<Height, ClientError> {
56        update_on_upgrade(
57            self.inner(),
58            ctx,
59            client_id,
60            upgraded_client_state,
61            upgraded_consensus_state,
62        )
63    }
64
65    fn update_on_recovery(
66        &self,
67        ctx: &mut E,
68        subject_client_id: &ClientId,
69        substitute_client_state: Any,
70        substitute_consensus_state: Any,
71    ) -> Result<(), ClientError> {
72        let subject_client_state = self.inner().clone();
73
74        update_on_recovery(
75            subject_client_state,
76            ctx,
77            subject_client_id,
78            substitute_client_state,
79            substitute_consensus_state,
80        )
81    }
82}
83
84/// Seed the host store with initial client and consensus states.
85///
86/// Note that this function is typically implemented as part of the
87/// [`ClientStateExecution`] trait, but has been made a standalone function
88/// in order to make the ClientState APIs more flexible.
89pub fn initialise<E>(
90    client_state: &ClientStateType,
91    ctx: &mut E,
92    client_id: &ClientId,
93    consensus_state: Any,
94) -> Result<(), ClientError>
95where
96    E: ExtClientExecutionContext,
97    E::ClientStateRef: From<ClientStateType>,
98    ConsensusStateType: Convertible<E::ConsensusStateRef>,
99{
100    let host_timestamp = ExtClientValidationContext::host_timestamp(ctx)?;
101    let host_height = ExtClientValidationContext::host_height(ctx)?;
102
103    let tm_consensus_state: ConsensusStateType = consensus_state.try_into()?;
104
105    ctx.store_client_state(
106        ClientStatePath::new(client_id.clone()),
107        client_state.clone().into(),
108    )?;
109    ctx.store_consensus_state(
110        ClientConsensusStatePath::new(
111            client_id.clone(),
112            client_state.latest_height.revision_number(),
113            client_state.latest_height.revision_height(),
114        ),
115        tm_consensus_state.into(),
116    )?;
117
118    ctx.store_update_meta(
119        client_id.clone(),
120        client_state.latest_height,
121        host_timestamp,
122        host_height,
123    )?;
124
125    Ok(())
126}
127
128/// Update the host store with a new client state, pruning old states from the
129/// store if need be.
130///
131/// Note that this function is typically implemented as part of the
132/// [`ClientStateExecution`] trait, but has been made a standalone function
133/// in order to make the ClientState APIs more flexible.
134pub fn update_state<E>(
135    client_state: &ClientStateType,
136    ctx: &mut E,
137    client_id: &ClientId,
138    header: Any,
139) -> Result<Vec<Height>, ClientError>
140where
141    E: ExtClientExecutionContext,
142    E::ClientStateRef: From<ClientStateType>,
143    ConsensusStateType: Convertible<E::ConsensusStateRef>,
144    <ConsensusStateType as TryFrom<E::ConsensusStateRef>>::Error: Into<ClientError>,
145{
146    let header = TmHeader::try_from(header)?;
147    let header_height = header.height();
148
149    prune_oldest_consensus_state(client_state, ctx, client_id)?;
150
151    let maybe_existing_consensus_state = {
152        let path_at_header_height = ClientConsensusStatePath::new(
153            client_id.clone(),
154            header_height.revision_number(),
155            header_height.revision_height(),
156        );
157
158        ctx.consensus_state(&path_at_header_height).ok()
159    };
160
161    if maybe_existing_consensus_state.is_some() {
162        // if we already had the header installed by a previous relayer
163        // then this is a no-op.
164        //
165        // Do nothing.
166    } else {
167        let host_timestamp = ExtClientValidationContext::host_timestamp(ctx)?;
168        let host_height = ExtClientValidationContext::host_height(ctx)?;
169
170        let new_consensus_state = ConsensusStateType::from(header.clone());
171        let new_client_state = client_state.clone().with_header(header)?;
172
173        ctx.store_consensus_state(
174            ClientConsensusStatePath::new(
175                client_id.clone(),
176                header_height.revision_number(),
177                header_height.revision_height(),
178            ),
179            new_consensus_state.into(),
180        )?;
181        ctx.store_client_state(
182            ClientStatePath::new(client_id.clone()),
183            new_client_state.into(),
184        )?;
185        ctx.store_update_meta(
186            client_id.clone(),
187            header_height,
188            host_timestamp,
189            host_height,
190        )?;
191    }
192
193    Ok(vec![header_height])
194}
195
196/// Commit a frozen client state, which was frozen as a result of having exhibited
197/// misbehaviour, to the store.
198///
199/// Note that this function is typically implemented as part of the
200/// [`ClientStateExecution`] trait, but has been made a standalone function
201/// in order to make the ClientState APIs more flexible.
202pub fn update_on_misbehaviour<E>(
203    client_state: &ClientStateType,
204    ctx: &mut E,
205    client_id: &ClientId,
206    _client_message: Any,
207) -> Result<(), ClientError>
208where
209    E: ExtClientExecutionContext,
210    E::ClientStateRef: From<ClientStateType>,
211{
212    // NOTE: frozen height is  set to `Height {revision_height: 0,
213    // revision_number: 1}` and it is the same for all misbehaviour. This
214    // aligns with the
215    // [`ibc-go`](https://github.com/cosmos/ibc-go/blob/0e3f428e66d6fc0fc6b10d2f3c658aaa5000daf7/modules/light-clients/07-tendermint/misbehaviour.go#L18-L19)
216    // implementation.
217    let frozen_client_state = client_state.clone().with_frozen_height(Height::min(0));
218
219    ctx.store_client_state(
220        ClientStatePath::new(client_id.clone()),
221        frozen_client_state.into(),
222    )?;
223
224    Ok(())
225}
226
227/// Commit the new client state and consensus state to the store upon a
228/// successful client upgrade.
229///
230/// Note that this function is typically implemented as part of the
231/// [`ClientStateExecution`] trait, but has been made a standalone function
232/// in order to make the ClientState APIs more flexible.
233pub fn update_on_upgrade<E>(
234    client_state: &ClientStateType,
235    ctx: &mut E,
236    client_id: &ClientId,
237    upgraded_client_state: Any,
238    upgraded_consensus_state: Any,
239) -> Result<Height, ClientError>
240where
241    E: ExtClientExecutionContext,
242    E::ClientStateRef: From<ClientStateType>,
243    ConsensusStateType: Convertible<E::ConsensusStateRef>,
244{
245    let mut upgraded_tm_client_state = ClientState::try_from(upgraded_client_state)?;
246    let upgraded_tm_cons_state: ConsensusStateType = upgraded_consensus_state.try_into()?;
247
248    upgraded_tm_client_state.0.zero_custom_fields();
249
250    // Construct new client state and consensus state relayer chosen client
251    // parameters are ignored. All chain-chosen parameters come from
252    // committed client, all client-chosen parameters come from current
253    // client.
254    let new_client_state = ClientStateType::new(
255        upgraded_tm_client_state.0.chain_id,
256        client_state.trust_level,
257        client_state.trusting_period,
258        upgraded_tm_client_state.0.unbonding_period,
259        client_state.max_clock_drift,
260        upgraded_tm_client_state.0.latest_height,
261        upgraded_tm_client_state.0.proof_specs,
262        upgraded_tm_client_state.0.upgrade_path,
263        client_state.allow_update,
264    )?;
265
266    // The new consensus state is merely used as a trusted kernel against
267    // which headers on the new chain can be verified. The root is just a
268    // stand-in sentinel value as it cannot be known in advance, thus no
269    // proof verification will pass. The timestamp and the
270    // NextValidatorsHash of the consensus state is the blocktime and
271    // NextValidatorsHash of the last block committed by the old chain. This
272    // will allow the first block of the new chain to be verified against
273    // the last validators of the old chain so long as it is submitted
274    // within the TrustingPeriod of this client.
275    // NOTE: We do not set processed time for this consensus state since
276    // this consensus state should not be used for packet verification as
277    // the root is empty. The next consensus state submitted using update
278    // will be usable for packet-verification.
279    let sentinel_root = b"sentinel_root".to_vec();
280    let new_consensus_state = ConsensusStateType::new(
281        sentinel_root.into(),
282        upgraded_tm_cons_state.timestamp(),
283        upgraded_tm_cons_state.next_validators_hash,
284    );
285
286    let latest_height = new_client_state.latest_height;
287    let host_timestamp = ExtClientValidationContext::host_timestamp(ctx)?;
288    let host_height = ExtClientValidationContext::host_height(ctx)?;
289
290    ctx.store_client_state(
291        ClientStatePath::new(client_id.clone()),
292        new_client_state.into(),
293    )?;
294    ctx.store_consensus_state(
295        ClientConsensusStatePath::new(
296            client_id.clone(),
297            latest_height.revision_number(),
298            latest_height.revision_height(),
299        ),
300        new_consensus_state.into(),
301    )?;
302    ctx.store_update_meta(
303        client_id.clone(),
304        latest_height,
305        host_timestamp,
306        host_height,
307    )?;
308
309    Ok(latest_height)
310}
311
312/// Removes consensus states from the client store whose timestamps
313/// are less than or equal to the host timestamp. This ensures that
314/// the client store does not amass a buildup of stale consensus states.
315pub fn prune_oldest_consensus_state<E>(
316    client_state: &ClientStateType,
317    ctx: &mut E,
318    client_id: &ClientId,
319) -> Result<(), ClientError>
320where
321    E: ClientExecutionContext + ExtClientValidationContext,
322    E::ClientStateRef: From<ClientStateType>,
323    ConsensusStateType: Convertible<E::ConsensusStateRef>,
324    <ConsensusStateType as TryFrom<E::ConsensusStateRef>>::Error: Into<ClientError>,
325{
326    let mut heights = ctx.consensus_state_heights(client_id)?;
327
328    heights.sort();
329
330    for height in heights {
331        let client_consensus_state_path = ClientConsensusStatePath::new(
332            client_id.clone(),
333            height.revision_number(),
334            height.revision_height(),
335        );
336        let consensus_state = ctx.consensus_state(&client_consensus_state_path)?;
337        let tm_consensus_state: ConsensusStateType =
338            consensus_state.try_into().map_err(Into::into)?;
339
340        let host_timestamp = ctx.host_timestamp()?.into_host_time()?;
341
342        let tm_consensus_state_timestamp = tm_consensus_state.timestamp();
343        let tm_consensus_state_expiry = (tm_consensus_state_timestamp
344            + client_state.trusting_period)
345            .map_err(|_| TimestampError::OverflowedTimestamp)?;
346
347        if tm_consensus_state_expiry > host_timestamp {
348            break;
349        }
350
351        ctx.delete_consensus_state(client_consensus_state_path)?;
352        ctx.delete_update_meta(client_id.clone(), height)?;
353    }
354
355    Ok(())
356}
357
358/// Update the `client_state`'s ID, trusting period, latest height, processed height,
359/// and processed time metadata values to those values provided by a verified substitute
360/// client state in response to a successful client recovery.
361///
362/// Note that unlike the `update_on_upgrade` function, `update_on_recovery` assumes
363/// that the client being updated has already been re-initialised such that its original
364/// client and consensus states have been overwritten to their new states.
365///
366/// This function is typically implemented as part of the [`ClientStateExecution`]
367/// trait, but has been made standalone in order to enable greater flexibility
368/// of the ClientState APIs.
369pub fn update_on_recovery<E>(
370    subject_client_state: ClientStateType,
371    ctx: &mut E,
372    subject_client_id: &ClientId,
373    substitute_client_state: Any,
374    substitute_consensus_state: Any,
375) -> Result<(), ClientError>
376where
377    E: ExtClientExecutionContext,
378    E::ClientStateRef: From<ClientStateType>,
379    ConsensusStateType: Convertible<E::ConsensusStateRef>,
380{
381    let substitute_client_state = ClientState::try_from(substitute_client_state)?
382        .inner()
383        .clone();
384
385    let chain_id = substitute_client_state.chain_id;
386    let trusting_period = substitute_client_state.trusting_period;
387    let latest_height = substitute_client_state.latest_height;
388
389    let new_client_state = ClientStateType {
390        chain_id,
391        trusting_period,
392        latest_height,
393        frozen_height: None,
394        ..subject_client_state
395    };
396
397    let host_timestamp = E::host_timestamp(ctx)?;
398    let host_height = E::host_height(ctx)?;
399
400    let tm_consensus_state: ConsensusStateType = substitute_consensus_state.try_into()?;
401
402    ctx.store_consensus_state(
403        ClientConsensusStatePath::new(
404            subject_client_id.clone(),
405            new_client_state.latest_height.revision_number(),
406            new_client_state.latest_height.revision_height(),
407        ),
408        tm_consensus_state.into(),
409    )?;
410
411    ctx.store_client_state(
412        ClientStatePath::new(subject_client_id.clone()),
413        new_client_state.into(),
414    )?;
415
416    ctx.store_update_meta(
417        subject_client_id.clone(),
418        latest_height,
419        host_timestamp,
420        host_height,
421    )?;
422
423    Ok(())
424}