nym_validator_client/nyxd/
error.rs1use crate::nyxd::cosmwasm_client::types::ContractCodeId;
5use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
6use cosmrs::tendermint::Hash;
7use cosmrs::{
8 tendermint::{abci::Code as AbciCode, block},
9 AccountId,
10};
11use std::{io, time::Duration};
12use tendermint_rpc::endpoint::abci_query::AbciQuery;
13use thiserror::Error;
14
15pub use cosmrs::tendermint::error::Error as TendermintError;
16pub use tendermint_rpc::{
17 error::{Error as TendermintRpcError, ErrorDetail as TendermintRpcErrorDetail},
18 response_error::{Code, ResponseError},
19};
20
21#[derive(Debug, Error)]
22pub enum NyxdError {
23 #[error("No contract address is available to perform the call: {0}")]
24 NoContractAddressAvailable(String),
25
26 #[error(transparent)]
27 WalletError(#[from] DirectSecp256k1HdWalletError),
28
29 #[error("There was an issue on the cosmrs side: {0}")]
30 CosmrsError(#[from] cosmrs::Error),
31
32 #[error("There was an issue on the cosmrs side: {0}")]
33 CosmrsErrorReport(#[from] cosmrs::ErrorReport),
34
35 #[error("cosmwasm event not found")]
36 ComswasmEventNotFound,
37
38 #[error("cosmwasm attribute not found")]
39 ComswasmAttributeNotFound,
40
41 #[error("Failed to derive account address")]
42 AccountDerivationError,
43
44 #[error("Address {0} was not found in the wallet")]
45 SigningAccountNotFound(AccountId),
46
47 #[error("Failed to sign raw transaction")]
48 SigningFailure,
49
50 #[error("{0} is not a valid tx hash")]
51 InvalidTxHash(String),
52
53 #[error("Tendermint RPC request failed - {0}")]
54 TendermintErrorRpc(#[from] TendermintRpcError),
55
56 #[error("tendermint library failure: {0}")]
57 TendermintError(#[from] TendermintError),
58
59 #[error("Failed when attempting to serialize data ({0})")]
60 SerializationError(String),
61
62 #[error("Failed when attempting to deserialize data ({0})")]
63 DeserializationError(String),
64
65 #[error("Failed when attempting to encode our protobuf data - {0}")]
66 ProtobufEncodingError(#[from] prost::EncodeError),
67
68 #[error("Failed to decode our protobuf data - {0}")]
69 ProtobufDecodingError(#[from] prost::DecodeError),
70
71 #[error("Account {0} does not exist on the chain")]
72 NonExistentAccountError(AccountId),
73
74 #[error("Failed on json serialization/deserialization - {0}")]
75 SerdeJsonError(#[from] serde_json::Error),
76
77 #[error("Account {0} is not a valid account address")]
78 MalformedAccountAddress(String),
79
80 #[error("Account {0} has an invalid associated public key")]
81 InvalidPublicKey(AccountId),
82
83 #[error("Queried contract (code_id: {0}) did not have any code information attached")]
84 NoCodeInformation(ContractCodeId),
85
86 #[error("Queried contract (address: {0}) did not have any contract information attached")]
87 NoContractInformation(AccountId),
88
89 #[error("Contract contains invalid operations in its history")]
90 InvalidContractHistoryOperation,
91
92 #[error("Block has an invalid height (either negative or larger than i64::MAX")]
93 InvalidHeight,
94
95 #[error("Failed to compress provided wasm code - {0}")]
96 WasmCompressionError(io::Error),
97
98 #[error("Logs returned from the validator were malformed")]
99 MalformedLogString,
100
101 #[error(
102 "Error when broadcasting tx {hash} at height {height:?}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
103 )]
104 BroadcastTxErrorCheckTx {
105 hash: Hash,
106 height: Option<block::Height>,
107 code: u32,
108 raw_log: String,
109 },
110
111 #[error(
112 "Error when broadcasting tx {hash} at height {height:?}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
113 )]
114 BroadcastTxErrorDeliverTx {
115 hash: Hash,
116 height: Option<block::Height>,
117 code: u32,
118 raw_log: String,
119 },
120
121 #[error("The provided gas price is malformed")]
122 MalformedGasPrice,
123
124 #[error("Failed to estimate gas price for the transaction")]
125 GasEstimationFailure,
126
127 #[error("Abci query failed with code {code} - {log}")]
128 AbciError {
129 code: u32,
130 log: String,
131 pretty_log: Option<String>,
132 },
133
134 #[error("Unsupported account type: {type_url}")]
135 UnsupportedAccountType { type_url: String },
136
137 #[error("{coin_representation} is not a valid Cosmos Coin")]
138 MalformedCoin { coin_representation: String },
139
140 #[error("This account does not have BaseAccount information available to it")]
141 NoBaseAccountInformationAvailable,
142
143 #[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())]
144 BroadcastTimeout { hash: Hash, timeout: Duration },
145
146 #[error("Cosmwasm std error: {0}")]
147 CosmwasmStdError(#[from] cosmwasm_std::StdError),
148
149 #[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
150 UnexpectedBech32Prefix { got: String, expected: String },
151
152 #[error("the transaction returned unexpected, {got}, number of MsgResponse. Expected to receive a single one")]
153 UnexpectedNumberOfMsgResponses { got: usize },
154
155 #[error("the response data has invalid size. got {got} bytes, but expected {expected} bytes instead")]
156 MalformedResponseData { got: usize, expected: usize },
157
158 #[error(
159 "one of the extension query for {contract} failed with the following message: {message}"
160 )]
161 ExtensionQueryFailure { contract: String, message: String },
162}
163
164impl NyxdError {
165 pub fn extension_query_failure(
166 contract: impl Into<String>,
167 message: impl Into<String>,
168 ) -> Self {
169 NyxdError::ExtensionQueryFailure {
170 contract: contract.into(),
171 message: message.into(),
172 }
173 }
174}
175
176pub fn parse_abci_query_result(query_result: AbciQuery) -> Result<AbciQuery, NyxdError> {
179 match query_result.code {
180 AbciCode::Ok => Ok(query_result),
181 AbciCode::Err(code) => Err(NyxdError::AbciError {
182 code: code.into(),
183 log: query_result.log.clone(),
184 pretty_log: try_parse_abci_log(&query_result.log),
185 }),
186 }
187}
188
189fn try_parse_abci_log(log: &str) -> Option<String> {
192 if log.contains("Maximum amount of locked coins has already been pledged") {
193 Some("Maximum amount of locked tokens has already been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
194 } else {
195 None
196 }
197}
198
199impl NyxdError {
200 pub fn is_tendermint_response_timeout(&self) -> bool {
201 match &self {
202 NyxdError::TendermintErrorRpc(TendermintRpcError(
203 TendermintRpcErrorDetail::Response(err),
204 _,
205 )) => {
206 let response = &err.source;
207 if response.code() == Code::InternalError {
208 if let Some(data) = response.data() {
215 data.contains("timed out") || data.contains("timeout")
216 } else {
217 false
218 }
219 } else {
220 false
221 }
222 }
223 _ => false,
224 }
225 }
226
227 pub fn is_tendermint_response_duplicate(&self) -> bool {
228 match &self {
229 NyxdError::TendermintErrorRpc(TendermintRpcError(
230 TendermintRpcErrorDetail::Response(err),
231 _,
232 )) => {
233 let response = &err.source;
234 if response.code() == Code::InternalError {
235 if let Some(data) = response.data() {
239 data.contains("tx already exists in cache")
240 } else {
241 false
242 }
243 } else {
244 false
245 }
246 }
247 _ => false,
248 }
249 }
250
251 pub fn is_block_pruned(&self) -> bool {
254 match &self {
255 NyxdError::TendermintErrorRpc(TendermintRpcError(
256 TendermintRpcErrorDetail::Response(err),
257 _,
258 )) => {
259 let response = &err.source;
260 if response.code() == Code::InternalError {
261 if let Some(data) = response.data() {
263 data.contains("is not available") && data.contains("lowest height")
264 } else {
265 false
266 }
267 } else {
268 false
269 }
270 }
271 _ => false,
272 }
273 }
274
275 pub fn unavailable_contract_address<S: Into<String>>(contract_type: S) -> Self {
276 NyxdError::NoContractAddressAvailable(contract_type.into())
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 fn rpc_internal_error(data: &str) -> NyxdError {
285 let response = ResponseError::new(Code::InternalError, Some(data.to_string()));
286 NyxdError::TendermintErrorRpc(TendermintRpcError::response(response))
287 }
288
289 #[test]
290 fn detects_pruned_block_error() {
291 let err = rpc_internal_error("height 14862522 is not available, lowest height is 16853136");
292 assert!(err.is_block_pruned());
293 }
294
295 #[test]
296 fn ignores_other_internal_errors() {
297 assert!(
298 !rpc_internal_error("timed out waiting for tx to be included in a block")
299 .is_block_pruned()
300 );
301 }
302
303 #[test]
304 fn ignores_non_rpc_errors() {
305 assert!(!NyxdError::NoContractAddressAvailable("mixnet".to_string()).is_block_pruned());
306 }
307}