Skip to main content

cosm_tome/chain/
response.rs

1use cosmrs::proto::{
2    cosmos::base::abci::v1beta1::TxResponse as CosmosResponse,
3    tendermint::abci::{Event as ProtoEvent, EventAttribute},
4};
5use cosmrs::rpc::abci::{
6    tag::{Key, Tag as TendermintProtoTag, Value},
7    Code as TendermintCode, Event as TendermintEvent,
8};
9use cosmrs::rpc::endpoint::{
10    abci_query::AbciQuery,
11    broadcast::tx_async::Response as AsyncTendermintResponse,
12    broadcast::tx_commit::{Response as BlockingTendermintResponse, TxResult},
13    broadcast::tx_sync::Response as SyncTendermintResponse,
14};
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use std::str::FromStr;
18
19use super::error::{ChainError, DeserializeError};
20
21#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Default)]
22pub struct ChainResponse {
23    pub code: Code,
24    pub data: Option<Vec<u8>>,
25    pub log: String,
26}
27
28impl ChainResponse {
29    pub fn data<'a, T: Deserialize<'a>>(&'a self) -> Result<T, DeserializeError> {
30        let r: T = serde_json::from_slice(
31            self.data
32                .as_ref()
33                .ok_or(DeserializeError::EmptyResponse)?
34                .as_slice(),
35        )?;
36        Ok(r)
37    }
38}
39
40impl From<AbciQuery> for ChainResponse {
41    fn from(res: AbciQuery) -> ChainResponse {
42        ChainResponse {
43            code: res.code.into(),
44            data: Some(res.value),
45            log: res.log.to_string(),
46        }
47    }
48}
49
50impl From<TxResult> for ChainResponse {
51    fn from(res: TxResult) -> ChainResponse {
52        ChainResponse {
53            code: res.code.into(),
54            data: res.data.map(|d| d.into()),
55            log: res.log.to_string(),
56        }
57    }
58}
59
60impl From<tonic::Status> for ChainResponse {
61    fn from(res: tonic::Status) -> ChainResponse {
62        ChainResponse {
63            code: res.code().into(),
64            data: Some(res.details().into()),
65            log: res.message().into(),
66        }
67    }
68}
69
70/// AsyncChainTxResponse is returned from the async `tx_broadcast()` api.
71#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Default)]
72pub struct AsyncChainTxResponse {
73    pub res: ChainResponse,
74    pub tx_hash: String,
75}
76
77impl AsRef<ChainResponse> for AsyncChainTxResponse {
78    fn as_ref(&self) -> &ChainResponse {
79        &self.res
80    }
81}
82
83impl From<CosmosResponse> for AsyncChainTxResponse {
84    fn from(res: CosmosResponse) -> Self {
85        Self {
86            res: ChainResponse {
87                code: res.code.into(),
88                data: Some(res.data.into()), // TODO
89                log: res.raw_log,
90            },
91            tx_hash: res.txhash,
92        }
93    }
94}
95
96impl From<AsyncTendermintResponse> for AsyncChainTxResponse {
97    fn from(res: AsyncTendermintResponse) -> Self {
98        Self {
99            res: ChainResponse {
100                code: res.code.into(),
101                data: Some(res.data.into()),
102                log: res.log.to_string(),
103            },
104            tx_hash: res.hash.to_string(),
105        }
106    }
107}
108
109impl From<SyncTendermintResponse> for AsyncChainTxResponse {
110    fn from(res: SyncTendermintResponse) -> Self {
111        Self {
112            res: ChainResponse {
113                code: res.code.into(),
114                data: Some(res.data.into()),
115                log: res.log.to_string(),
116            },
117            tx_hash: res.hash.to_string(),
118        }
119    }
120}
121
122/// ChainTxResponse is returned from the blocking `tx_broadcast_block()` api.
123/// Since we wait for the tx to be commited in the next block, we get the full tx data.
124#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Default)]
125pub struct ChainTxResponse {
126    pub res: ChainResponse,
127    pub events: Vec<Event>,
128    pub gas_wanted: u64,
129    pub gas_used: u64,
130    pub tx_hash: String,
131    pub height: u64,
132}
133
134impl ChainTxResponse {
135    pub fn find_event_tags(&self, event_type: String, key_name: String) -> Vec<&Tag> {
136        let mut events = vec![];
137        for event in &self.events {
138            if event.type_str == event_type {
139                for attr in &event.attributes {
140                    if attr.key == key_name {
141                        events.push(attr);
142                    }
143                }
144            }
145        }
146        events
147    }
148}
149
150impl AsRef<ChainResponse> for ChainTxResponse {
151    fn as_ref(&self) -> &ChainResponse {
152        &self.res
153    }
154}
155
156impl From<BlockingTendermintResponse> for ChainTxResponse {
157    fn from(res: BlockingTendermintResponse) -> Self {
158        ChainTxResponse {
159            res: ChainResponse {
160                code: res.deliver_tx.code.into(),
161                data: res.deliver_tx.data.map(|d| d.into()),
162                log: res.deliver_tx.log.to_string(),
163            },
164            events: res.deliver_tx.events.into_iter().map(Into::into).collect(),
165            gas_used: res.deliver_tx.gas_used.into(),
166            gas_wanted: res.deliver_tx.gas_wanted.into(),
167            tx_hash: res.hash.to_string(),
168            height: res.height.into(),
169        }
170    }
171}
172
173impl TryFrom<CosmosResponse> for ChainTxResponse {
174    type Error = ChainError;
175
176    fn try_from(res: CosmosResponse) -> Result<Self, Self::Error> {
177        Ok(ChainTxResponse {
178            res: ChainResponse {
179                code: res.code.into(),
180                data: Some(res.data.into()), // TODO
181                log: res.raw_log,
182            },
183            events: res
184                .events
185                .into_iter()
186                .map(TryInto::try_into)
187                .collect::<Result<Vec<_>, _>>()?,
188            gas_wanted: res.gas_wanted as u64,
189            gas_used: res.gas_used as u64,
190            tx_hash: res.txhash,
191            height: res.height as u64,
192        })
193    }
194}
195
196#[derive(
197    Copy,
198    Clone,
199    Debug,
200    Default,
201    Eq,
202    Hash,
203    PartialEq,
204    PartialOrd,
205    Ord,
206    Serialize,
207    Deserialize,
208    JsonSchema,
209)]
210pub enum Code {
211    #[default]
212    Ok,
213    Err(u32),
214}
215
216impl Code {
217    pub fn is_ok(self) -> bool {
218        match self {
219            Code::Ok => true,
220            Code::Err(_) => false,
221        }
222    }
223
224    pub fn is_err(self) -> bool {
225        !self.is_ok()
226    }
227
228    pub fn value(self) -> u32 {
229        u32::from(self)
230    }
231}
232
233impl From<u32> for Code {
234    fn from(value: u32) -> Code {
235        match value {
236            0 => Code::Ok,
237            err => Code::Err(err),
238        }
239    }
240}
241
242impl From<Code> for u32 {
243    fn from(code: Code) -> u32 {
244        match code {
245            Code::Ok => 0,
246            Code::Err(err) => err,
247        }
248    }
249}
250
251impl From<u16> for Code {
252    fn from(value: u16) -> Code {
253        match value {
254            0 => Code::Ok,
255            err => Code::Err(err.into()),
256        }
257    }
258}
259
260impl From<u8> for Code {
261    fn from(value: u8) -> Code {
262        match value {
263            0 => Code::Ok,
264            err => Code::Err(err.into()),
265        }
266    }
267}
268
269impl From<TendermintCode> for Code {
270    fn from(value: TendermintCode) -> Code {
271        match value {
272            TendermintCode::Ok => Code::Ok,
273            TendermintCode::Err(err) => Code::Err(err.into()),
274        }
275    }
276}
277
278impl From<tonic::Code> for Code {
279    fn from(value: tonic::Code) -> Code {
280        // NOTE: `value` is an isize, so we are just manually
281        // matching them to avoid any casting errors in the future
282        match value {
283            tonic::Code::Ok => Code::Ok,
284            tonic::Code::Cancelled => Code::Err(1),
285            tonic::Code::Unknown => Code::Err(2),
286            tonic::Code::InvalidArgument => Code::Err(3),
287            tonic::Code::DeadlineExceeded => Code::Err(4),
288            tonic::Code::NotFound => Code::Err(5),
289            tonic::Code::AlreadyExists => Code::Err(6),
290            tonic::Code::PermissionDenied => Code::Err(7),
291            tonic::Code::ResourceExhausted => Code::Err(8),
292            tonic::Code::FailedPrecondition => Code::Err(9),
293            tonic::Code::Aborted => Code::Err(10),
294            tonic::Code::OutOfRange => Code::Err(11),
295            tonic::Code::Unimplemented => Code::Err(12),
296            tonic::Code::Internal => Code::Err(13),
297            tonic::Code::Unavailable => Code::Err(14),
298            tonic::Code::DataLoss => Code::Err(15),
299            tonic::Code::Unauthenticated => Code::Err(16),
300        }
301    }
302}
303
304#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Hash)]
305pub struct Event {
306    pub type_str: String,
307    pub attributes: Vec<Tag>,
308}
309
310impl From<TendermintEvent> for Event {
311    fn from(e: TendermintEvent) -> Self {
312        Self {
313            type_str: e.type_str,
314            attributes: e.attributes.into_iter().map(Into::into).collect(),
315        }
316    }
317}
318
319impl TryFrom<Event> for TendermintEvent {
320    type Error = ChainError;
321
322    fn try_from(e: Event) -> Result<Self, Self::Error> {
323        Ok(Self {
324            type_str: e.type_str,
325            attributes: e
326                .attributes
327                .into_iter()
328                .map(TryInto::try_into)
329                .collect::<Result<Vec<_>, _>>()?,
330        })
331    }
332}
333
334impl TryFrom<ProtoEvent> for Event {
335    type Error = ChainError;
336
337    fn try_from(e: ProtoEvent) -> Result<Self, Self::Error> {
338        Ok(Self {
339            type_str: e.r#type,
340            attributes: e
341                .attributes
342                .into_iter()
343                .map(TryInto::try_into)
344                .collect::<Result<Vec<_>, _>>()?,
345        })
346    }
347}
348
349impl From<Event> for ProtoEvent {
350    fn from(e: Event) -> Self {
351        Self {
352            r#type: e.type_str,
353            attributes: e.attributes.into_iter().map(Into::into).collect(),
354        }
355    }
356}
357
358#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Hash)]
359pub struct Tag {
360    pub key: String,
361    pub value: String,
362}
363
364impl From<TendermintProtoTag> for Tag {
365    fn from(tag: TendermintProtoTag) -> Self {
366        Self {
367            key: tag.key.to_string(),
368            value: tag.value.to_string(),
369        }
370    }
371}
372
373impl TryFrom<Tag> for TendermintProtoTag {
374    type Error = ChainError;
375
376    fn try_from(tag: Tag) -> Result<Self, Self::Error> {
377        Ok(Self {
378            key: Key::from_str(&tag.key)?,
379            value: Value::from_str(&tag.value)?,
380        })
381    }
382}
383
384impl From<Tag> for EventAttribute {
385    fn from(tag: Tag) -> Self {
386        Self {
387            key: tag.key.into_bytes().into(),
388            value: tag.value.into_bytes().into(),
389            index: true,
390        }
391    }
392}
393
394impl TryFrom<EventAttribute> for Tag {
395    type Error = ChainError;
396
397    fn try_from(attr: EventAttribute) -> Result<Self, Self::Error> {
398        Ok(Self {
399            key: String::from_utf8(attr.key.into()).map_err(|e| ChainError::ProtoDecoding {
400                message: e.to_string(),
401            })?,
402            value: String::from_utf8(attr.value.into()).map_err(|e| ChainError::ProtoDecoding {
403                message: e.to_string(),
404            })?,
405        })
406    }
407}