1use std::{cmp::min, time::Duration};
2
3use crate::{
4 cosmos_modules, env::DaemonEnvVars, error::DaemonError, senders::query::QuerySender,
5 tx_resp::CosmTxResponse, DaemonBase,
6};
7
8use cosmrs::{
9 proto::cosmos::{
10 base::query::v1beta1::PageRequest,
11 tx::v1beta1::{OrderBy, SimulateResponse},
12 },
13 tendermint::{Block, Time},
14};
15use cosmwasm_std::BlockInfo;
16use cw_orch_core::{
17 environment::{NodeQuerier, Querier, QuerierGetter},
18 log::query_target,
19};
20use tokio::runtime::Handle;
21use tonic::transport::Channel;
22
23pub struct Node {
27 pub channel: Channel,
28 pub rt_handle: Option<Handle>,
29}
30
31impl Node {
32 pub fn new<Sender: QuerySender>(daemon: &DaemonBase<Sender>) -> Self {
33 Self {
34 channel: daemon.channel(),
35 rt_handle: Some(daemon.rt_handle.clone()),
36 }
37 }
38 pub fn new_async(channel: Channel) -> Self {
39 Self {
40 channel,
41 rt_handle: None,
42 }
43 }
44}
45
46impl<Sender: QuerySender> QuerierGetter<Node> for DaemonBase<Sender> {
47 fn querier(&self) -> Node {
48 Node::new(self)
49 }
50}
51
52impl Querier for Node {
53 type Error = DaemonError;
54}
55
56impl Node {
57 pub async fn _info(
59 &self,
60 ) -> Result<cosmos_modules::tendermint::GetNodeInfoResponse, DaemonError> {
61 let mut client =
62 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
63
64 let resp = client
65 .get_node_info(cosmos_modules::tendermint::GetNodeInfoRequest {})
66 .await?
67 .into_inner();
68
69 Ok(resp)
70 }
71
72 pub async fn _syncing(&self) -> Result<bool, DaemonError> {
74 let mut client =
75 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
76
77 let resp = client
78 .get_syncing(cosmos_modules::tendermint::GetSyncingRequest {})
79 .await?
80 .into_inner();
81
82 Ok(resp.syncing)
83 }
84
85 pub async fn _latest_block(&self) -> Result<Block, DaemonError> {
87 let mut client =
88 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
89
90 let resp = client
91 .get_latest_block(cosmos_modules::tendermint::GetLatestBlockRequest {})
92 .await?
93 .into_inner();
94
95 Ok(Block::try_from(resp.block.unwrap())?)
96 }
97
98 pub async fn _block_by_height(&self, height: u64) -> Result<Block, DaemonError> {
100 let mut client =
101 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
102
103 let resp = client
104 .get_block_by_height(cosmos_modules::tendermint::GetBlockByHeightRequest {
105 height: height as i64,
106 })
107 .await?
108 .into_inner();
109
110 Ok(Block::try_from(resp.block.unwrap())?)
111 }
112
113 pub async fn _average_block_speed(
116 &self,
117 multiplier: Option<f32>,
118 ) -> Result<Duration, DaemonError> {
119 let mut latest_block = self._latest_block().await?;
121 let latest_block_time = latest_block.header.time;
122 let mut latest_block_height = latest_block.header.height.value();
123
124 while latest_block_height <= 1 {
125 tokio::time::sleep(Duration::from_secs(1)).await;
127 latest_block = self._latest_block().await?;
128 latest_block_height = latest_block.header.height.value();
129 }
130
131 let avg_period = min(latest_block_height - 1, 50);
133
134 let block_avg_period_ago = self
136 ._block_by_height(latest_block_height - avg_period)
137 .await?;
138 let block_avg_period_ago_time = block_avg_period_ago.header.time;
139
140 let average_block_time = latest_block_time.duration_since(block_avg_period_ago_time)?;
142 let average_block_time = average_block_time.div_f64(avg_period as f64);
143
144 let average_block_time = match multiplier {
146 Some(multiplier) => average_block_time.mul_f32(multiplier),
147 None => average_block_time,
148 };
149
150 Ok(average_block_time)
151 }
152
153 pub async fn _latest_validator_set(
155 &self,
156 pagination: Option<PageRequest>,
157 ) -> Result<cosmos_modules::tendermint::GetLatestValidatorSetResponse, DaemonError> {
158 let mut client =
159 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
160
161 let resp = client
162 .get_latest_validator_set(cosmos_modules::tendermint::GetLatestValidatorSetRequest {
163 pagination,
164 })
165 .await?
166 .into_inner();
167
168 Ok(resp)
169 }
170
171 pub async fn _validator_set_by_height(
173 &self,
174 height: i64,
175 pagination: Option<PageRequest>,
176 ) -> Result<cosmos_modules::tendermint::GetValidatorSetByHeightResponse, DaemonError> {
177 let mut client =
178 cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone());
179
180 let resp = client
181 .get_validator_set_by_height(
182 cosmos_modules::tendermint::GetValidatorSetByHeightRequest { height, pagination },
183 )
184 .await?
185 .into_inner();
186
187 Ok(resp)
188 }
189
190 pub async fn _block_height(&self) -> Result<u64, DaemonError> {
192 let block = self._latest_block().await?;
193 Ok(block.header.height.value())
194 }
195
196 pub async fn _block_time(&self) -> Result<u128, DaemonError> {
198 let block = self._latest_block().await?;
199 Ok(block
200 .header
201 .time
202 .duration_since(Time::unix_epoch())?
203 .as_nanos())
204 }
205
206 pub async fn _simulate_tx(&self, tx_bytes: Vec<u8>) -> Result<u64, DaemonError> {
208 let mut client =
209 cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone());
210 #[allow(deprecated)]
211 let resp: SimulateResponse = client
212 .simulate(cosmos_modules::tx::SimulateRequest { tx: None, tx_bytes })
213 .await?
214 .into_inner();
215 let gas_used = resp.gas_info.unwrap().gas_used;
216 Ok(gas_used)
217 }
218
219 pub async fn _block_info(&self) -> Result<cosmwasm_std::BlockInfo, DaemonError> {
221 let block = self._latest_block().await?;
222
223 block_to_block_info(block)
224 }
225
226 pub async fn _find_tx(&self, hash: String) -> Result<CosmTxResponse, DaemonError> {
228 self._find_tx_with_retries(hash, DaemonEnvVars::max_tx_query_retries())
229 .await
230 }
231
232 pub async fn _find_tx_with_retries(
234 &self,
235 hash: String,
236 retries: usize,
237 ) -> Result<CosmTxResponse, DaemonError> {
238 let mut client =
239 cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone());
240
241 let request = cosmos_modules::tx::GetTxRequest { hash: hash.clone() };
242 let mut block_speed = self._average_block_speed(Some(0.7)).await?;
243 let max_block_time = DaemonEnvVars::max_block_time();
244 if let Some(max_time) = max_block_time {
245 block_speed = block_speed.min(max_time);
246 } else {
247 let min_block_time = DaemonEnvVars::min_block_time();
248 block_speed = block_speed.max(min_block_time);
249 }
250
251 for _ in 0..retries {
252 match client.get_tx(request.clone()).await {
253 Ok(tx) => {
254 let resp = tx.into_inner().tx_response.unwrap().into();
255 log::debug!(target: &query_target(), "TX found: {:?}", resp);
256 return Ok(resp);
257 }
258 Err(err) => {
259 block_speed = block_speed.mul_f64(1.6);
261 if let Some(max_time) = max_block_time {
262 block_speed = block_speed.min(max_time)
263 }
264 log::debug!(target: &query_target(), "TX not found with error: {:?}", err);
265 log::debug!(target: &query_target(), "Waiting {} milli-seconds", block_speed.as_millis());
266 tokio::time::sleep(block_speed).await;
267 }
268 }
269 }
270
271 Err(DaemonError::TXNotFound(hash, retries))
273 }
274
275 pub async fn _find_tx_by_events(
277 &self,
278 events: Vec<String>,
279 page: Option<u64>,
280 order_by: Option<OrderBy>,
281 ) -> Result<Vec<CosmTxResponse>, DaemonError> {
282 self._find_tx_by_events_with_retries(
283 events,
284 page,
285 order_by,
286 false,
287 DaemonEnvVars::max_tx_query_retries(),
288 )
289 .await
290 }
291
292 pub async fn _find_some_tx_by_events(
296 &self,
297 events: Vec<String>,
298 page: Option<u64>,
299 order_by: Option<OrderBy>,
300 ) -> Result<Vec<CosmTxResponse>, DaemonError> {
301 self._find_tx_by_events_with_retries(
302 events,
303 page,
304 order_by,
305 true,
306 DaemonEnvVars::max_tx_query_retries(),
307 )
308 .await
309 }
310
311 pub async fn _find_tx_by_events_with_retries(
315 &self,
316 events: Vec<String>,
317 page: Option<u64>,
318 order_by: Option<OrderBy>,
319 retry_on_empty: bool,
320 retries: usize,
321 ) -> Result<Vec<CosmTxResponse>, DaemonError> {
322 let mut client = cosmrs::proto::cosmos::tx::v1beta1::service_client::ServiceClient::new(
323 self.channel.clone(),
324 );
325
326 #[allow(deprecated)]
327 let request = cosmrs::proto::cosmos::tx::v1beta1::GetTxsEventRequest {
328 events: events.clone(),
329 pagination: None,
330 order_by: order_by.unwrap_or(OrderBy::Desc).into(),
331 page: page.unwrap_or(0),
332 limit: 100,
333 query: events.join(" AND "),
334 };
335
336 for _ in 0..retries {
337 match client.get_txs_event(request.clone()).await {
338 Ok(tx) => {
339 let resp = tx.into_inner().tx_responses;
340 if retry_on_empty && resp.is_empty() {
341 log::debug!(target: &query_target(), "No TX found with events {:?}", events);
342 log::debug!(target: &query_target(), "Waiting 10s");
343 tokio::time::sleep(Duration::from_secs(10)).await;
344 } else {
345 log::debug!(
346 target: &query_target(),
347 "TX found by events: {:?}",
348 resp.iter().map(|t| t.txhash.clone())
349 );
350 return Ok(resp.iter().map(|r| r.clone().into()).collect());
351 }
352 }
353 Err(err) => {
354 log::debug!(target: &query_target(), "TX not found with error: {:?}", err);
355 log::debug!(target: &query_target(), "Waiting 10s");
356 tokio::time::sleep(Duration::from_secs(10)).await;
357 }
358 }
359 }
360 Err(DaemonError::TXNotFound(
362 format!("with events {:?}", events),
363 DaemonEnvVars::max_tx_query_retries(),
364 ))
365 }
366}
367
368impl NodeQuerier for Node {
371 type Response = CosmTxResponse;
372
373 fn latest_block(&self) -> Result<cosmwasm_std::BlockInfo, Self::Error> {
374 self.rt_handle
375 .as_ref()
376 .ok_or(DaemonError::QuerierNeedRuntime)?
377 .block_on(self._block_info())
378 }
379
380 fn block_by_height(&self, height: u64) -> Result<cosmwasm_std::BlockInfo, Self::Error> {
381 let block = self
382 .rt_handle
383 .as_ref()
384 .ok_or(DaemonError::QuerierNeedRuntime)?
385 .block_on(self._block_by_height(height))?;
386
387 block_to_block_info(block)
388 }
389
390 fn block_height(&self) -> Result<u64, Self::Error> {
391 self.rt_handle
392 .as_ref()
393 .ok_or(DaemonError::QuerierNeedRuntime)?
394 .block_on(self._block_height())
395 }
396
397 fn block_time(&self) -> Result<u128, Self::Error> {
398 self.rt_handle
399 .as_ref()
400 .ok_or(DaemonError::QuerierNeedRuntime)?
401 .block_on(self._block_time())
402 }
403
404 fn simulate_tx(&self, tx_bytes: Vec<u8>) -> Result<u64, Self::Error> {
405 self.rt_handle
406 .as_ref()
407 .ok_or(DaemonError::QuerierNeedRuntime)?
408 .block_on(self._simulate_tx(tx_bytes))
409 }
410
411 fn find_tx(&self, hash: String) -> Result<Self::Response, Self::Error> {
412 self.rt_handle
413 .as_ref()
414 .ok_or(DaemonError::QuerierNeedRuntime)?
415 .block_on(self._find_tx(hash))
416 }
417}
418
419fn block_to_block_info(block: Block) -> Result<BlockInfo, DaemonError> {
420 let since_epoch = block.header.time.duration_since(Time::unix_epoch())?;
421 let time = cosmwasm_std::Timestamp::from_nanos(since_epoch.as_nanos() as u64);
422 Ok(cosmwasm_std::BlockInfo {
423 height: block.header.height.value(),
424 time,
425 chain_id: block.header.chain_id.to_string(),
426 })
427}