hermes_cli_components/impls/commands/queries/
client_status.rs1use core::marker::PhantomData;
2
3use cgp::prelude::*;
4use hermes_error::traits::wrap::CanWrapError;
5use hermes_logging_components::traits::has_logger::HasLogger;
6use hermes_logging_components::traits::logger::CanLog;
7use hermes_logging_components::types::level::LevelInfo;
8use hermes_relayer_components::build::traits::builders::chain_builder::CanBuildChain;
9use hermes_relayer_components::chain::traits::queries::chain_status::CanQueryChainStatus;
10use hermes_relayer_components::chain::traits::queries::client_state::{
11 CanQueryClientState, CanQueryClientStateWithLatestHeight,
12};
13use hermes_relayer_components::chain::traits::queries::consensus_state::{
14 CanQueryConsensusState, CanQueryConsensusStateWithLatestHeight,
15};
16use hermes_relayer_components::chain::traits::types::client_state::{
17 HasClientStateFields, HasClientStateType,
18};
19use hermes_relayer_components::chain::traits::types::consensus_state::{
20 HasConsensusStateFields, HasConsensusStateType,
21};
22use hermes_relayer_components::chain::traits::types::ibc::HasIbcChainTypes;
23use hermes_relayer_components::multi::traits::chain_at::HasChainTypeAt;
24use hermes_relayer_components::multi::types::index::Index;
25use serde::Serialize;
26
27use crate::traits::build::CanLoadBuilder;
28use crate::traits::command::CommandRunner;
29use crate::traits::output::CanProduceOutput;
30use crate::traits::parse::CanParseArg;
31
32pub struct RunQueryClientStatusCommand;
33
34#[derive(Debug, clap::Parser, HasField)]
35pub struct QueryClientStatusArgs {
36 #[clap(
38 long = "chain",
39 required = true,
40 value_name = "CHAIN_ID",
41 help_heading = "REQUIRED"
42 )]
43 chain_id: String,
44
45 #[clap(
47 long = "client",
48 required = true,
49 value_name = "CLIENT_ID",
50 help_heading = "REQUIRED"
51 )]
52 client_id: String,
53}
54
55#[derive(Debug, Serialize)]
56pub enum ClientStatus {
57 Frozen,
58 Expired,
59 Active,
60}
61
62impl<App, Args, Build, Chain, Counterparty> CommandRunner<App, Args> for RunQueryClientStatusCommand
63where
64 App: CanLoadBuilder<Builder = Build>
65 + CanProduceOutput<ClientStatus>
66 + HasLogger
67 + CanParseArg<Args, symbol!("chain_id"), Parsed = Chain::ChainId>
70 + CanParseArg<Args, symbol!("client_id"), Parsed = Chain::ClientId>
71 + CanRaiseError<Build::Error>
72 + CanRaiseError<Chain::Error>,
73 Build: CanBuildChain<0, Chain = Chain> + HasChainTypeAt<1, Chain = Counterparty>,
74 Chain: HasIbcChainTypes<Counterparty> + CanQueryClientStatus<Counterparty>,
75 Counterparty: HasIbcChainTypes<Chain>
76 + HasClientStateType<Chain>
77 + HasClientStateFields<Chain>
78 + HasConsensusStateType<Chain>
79 + HasConsensusStateFields<Chain>,
80 App::Logger: CanLog<LevelInfo>,
81 Args: Async,
82{
83 async fn run_command(app: &App, args: &Args) -> Result<App::Output, App::Error> {
84 let chain_id = app.parse_arg(args, PhantomData::<symbol!("chain_id")>)?;
85 let client_id = app.parse_arg(args, PhantomData::<symbol!("client_id")>)?;
86
87 let logger = app.logger();
88 let builder = app.load_builder().await?;
89
90 let chain = builder
91 .build_chain(Index::<0>, &chain_id)
92 .await
93 .map_err(App::raise_error)?;
94
95 let client_status = chain
96 .query_client_status(&client_id)
97 .await
98 .map_err(App::raise_error)?;
99
100 match client_status {
101 ClientStatus::Frozen => {
102 logger
103 .log(&format!("Client `{}` is frozen", client_id), &LevelInfo)
104 .await;
105 }
106 ClientStatus::Expired => {
107 logger
108 .log(&format!("Client `{}` has expired", client_id), &LevelInfo)
109 .await;
110 }
111 ClientStatus::Active => {
112 logger
113 .log(&format!("Client `{}` is active", client_id), &LevelInfo)
114 .await;
115 }
116 }
117
118 Ok(app.produce_output(client_status))
119 }
120}
121
122#[async_trait]
123pub trait CanQueryClientStatus<Counterparty>:
124 HasIbcChainTypes<Counterparty>
125 + HasErrorType
126 + CanQueryClientState<Counterparty>
127 + CanQueryClientStateWithLatestHeight<Counterparty>
128 + CanQueryChainStatus
129 + CanQueryConsensusState<Counterparty>
130where
131 Counterparty: HasIbcChainTypes<Self>
132 + HasClientStateType<Self>
133 + HasClientStateFields<Self>
134 + HasConsensusStateType<Self>
135 + HasConsensusStateFields<Self>,
136{
137 async fn query_client_status(
138 &self,
139 client_id: &Self::ClientId,
140 ) -> Result<ClientStatus, Self::Error>;
141}
142
143impl<Chain, Counterparty> CanQueryClientStatus<Counterparty> for Chain
144where
145 Chain: HasIbcChainTypes<Counterparty>
146 + HasErrorType
147 + CanQueryClientState<Counterparty>
148 + CanQueryClientStateWithLatestHeight<Counterparty>
149 + CanQueryChainStatus
150 + CanQueryConsensusState<Counterparty>
151 + CanWrapError<String>,
152 Counterparty: HasIbcChainTypes<Chain>
153 + HasClientStateType<Chain>
154 + HasClientStateFields<Chain>
155 + HasConsensusStateType<Chain>
156 + HasConsensusStateFields<Chain>,
157{
158 async fn query_client_status(
159 &self,
160 client_id: &Self::ClientId,
161 ) -> Result<ClientStatus, Self::Error> {
162 let client_state = self
163 .query_client_state_with_latest_height(client_id)
164 .await
165 .map_err(|e| {
166 Chain::wrap_error(
167 format!("Failed to query client state for client `{client_id}`"),
168 e,
169 )
170 })?;
171
172 if Counterparty::client_state_is_frozen(&client_state) {
173 return Ok(ClientStatus::Frozen);
174 }
175
176 let client_latest_height = Counterparty::client_state_latest_height(&client_state);
177
178 let latest_consensus_state = self
179 .query_consensus_state_with_latest_height(client_id, &client_latest_height)
180 .await
181 .map_err(|e| {
182 Chain::wrap_error(
183 format!("Failed to query consensus state at height {client_latest_height}"),
184 e,
185 )
186 })?;
187
188 let latest_consensus_state_timestamp =
189 Counterparty::consensus_state_timestamp(&latest_consensus_state);
190
191 let chain_status = self
192 .query_chain_status()
193 .await
194 .map_err(|e| Chain::wrap_error("Failed to query chain status".to_owned(), e))?;
195
196 let current_network_time = Self::chain_status_timestamp(&chain_status);
197
198 let elapsed =
199 Self::timestamp_duration_since(&latest_consensus_state_timestamp, current_network_time);
200
201 let has_expired = elapsed.map_or(false, |elapsed| {
202 Counterparty::client_state_has_expired(&client_state, elapsed)
203 });
204
205 if has_expired {
206 Ok(ClientStatus::Expired)
207 } else {
208 Ok(ClientStatus::Active)
209 }
210 }
211}