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("The response did not contain the requested IAVL inclusion proof")]
96 MissingProof,
97
98 #[error("Failed to compress provided wasm code - {0}")]
99 WasmCompressionError(io::Error),
100
101 #[error("Logs returned from the validator were malformed")]
102 MalformedLogString,
103
104 #[error(
105 "Error when broadcasting tx {hash} at height {height:?}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
106 )]
107 BroadcastTxErrorCheckTx {
108 hash: Hash,
109 height: Option<block::Height>,
110 code: u32,
111 raw_log: String,
112 },
113
114 #[error(
115 "Error when broadcasting tx {hash} at height {height:?}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
116 )]
117 BroadcastTxErrorDeliverTx {
118 hash: Hash,
119 height: Option<block::Height>,
120 code: u32,
121 raw_log: String,
122 },
123
124 #[error("The provided gas price is malformed")]
125 MalformedGasPrice,
126
127 #[error("Failed to estimate gas price for the transaction")]
128 GasEstimationFailure,
129
130 #[error("Abci query failed with code {code} - {log}")]
131 AbciError {
132 code: u32,
133 log: String,
134 pretty_log: Option<String>,
135 },
136
137 #[error("Unsupported account type: {type_url}")]
138 UnsupportedAccountType { type_url: String },
139
140 #[error("{coin_representation} is not a valid Cosmos Coin")]
141 MalformedCoin { coin_representation: String },
142
143 #[error("This account does not have BaseAccount information available to it")]
144 NoBaseAccountInformationAvailable,
145
146 #[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())]
147 BroadcastTimeout { hash: Hash, timeout: Duration },
148
149 #[error("Cosmwasm std error: {0}")]
150 CosmwasmStdError(#[from] cosmwasm_std::StdError),
151
152 #[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
153 UnexpectedBech32Prefix { got: String, expected: String },
154
155 #[error("the transaction returned unexpected, {got}, number of MsgResponse. Expected to receive a single one")]
156 UnexpectedNumberOfMsgResponses { got: usize },
157
158 #[error("the response data has invalid size. got {got} bytes, but expected {expected} bytes instead")]
159 MalformedResponseData { got: usize, expected: usize },
160
161 #[error(
162 "one of the extension query for {contract} failed with the following message: {message}"
163 )]
164 ExtensionQueryFailure { contract: String, message: String },
165}
166
167impl NyxdError {
168 pub fn extension_query_failure(
169 contract: impl Into<String>,
170 message: impl Into<String>,
171 ) -> Self {
172 NyxdError::ExtensionQueryFailure {
173 contract: contract.into(),
174 message: message.into(),
175 }
176 }
177}
178
179pub fn parse_abci_query_result(query_result: AbciQuery) -> Result<AbciQuery, NyxdError> {
182 match query_result.code {
183 AbciCode::Ok => Ok(query_result),
184 AbciCode::Err(code) => Err(NyxdError::AbciError {
185 code: code.into(),
186 log: query_result.log.clone(),
187 pretty_log: try_parse_abci_log(&query_result.log),
188 }),
189 }
190}
191
192fn try_parse_abci_log(log: &str) -> Option<String> {
195 if log.contains("Maximum amount of locked coins has already been pledged") {
196 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())
197 } else {
198 None
199 }
200}
201
202impl NyxdError {
203 pub fn is_tendermint_response_timeout(&self) -> bool {
204 match &self {
205 NyxdError::TendermintErrorRpc(TendermintRpcError(
206 TendermintRpcErrorDetail::Response(err),
207 _,
208 )) => {
209 let response = &err.source;
210 if response.code() == Code::InternalError {
211 if let Some(data) = response.data() {
218 data.contains("timed out") || data.contains("timeout")
219 } else {
220 false
221 }
222 } else {
223 false
224 }
225 }
226 _ => false,
227 }
228 }
229
230 pub fn is_tendermint_response_duplicate(&self) -> bool {
231 match &self {
232 NyxdError::TendermintErrorRpc(TendermintRpcError(
233 TendermintRpcErrorDetail::Response(err),
234 _,
235 )) => {
236 let response = &err.source;
237 if response.code() == Code::InternalError {
238 if let Some(data) = response.data() {
242 data.contains("tx already exists in cache")
243 } else {
244 false
245 }
246 } else {
247 false
248 }
249 }
250 _ => false,
251 }
252 }
253
254 pub fn is_block_pruned(&self) -> bool {
257 match &self {
258 NyxdError::TendermintErrorRpc(TendermintRpcError(
259 TendermintRpcErrorDetail::Response(err),
260 _,
261 )) => {
262 let response = &err.source;
263 if response.code() == Code::InternalError {
264 if let Some(data) = response.data() {
266 data.contains("is not available") && data.contains("lowest height")
267 } else {
268 false
269 }
270 } else {
271 false
272 }
273 }
274 _ => false,
275 }
276 }
277
278 pub fn unavailable_contract_address<S: Into<String>>(contract_type: S) -> Self {
279 NyxdError::NoContractAddressAvailable(contract_type.into())
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 fn rpc_internal_error(data: &str) -> NyxdError {
288 let response = ResponseError::new(Code::InternalError, Some(data.to_string()));
289 NyxdError::TendermintErrorRpc(TendermintRpcError::response(response))
290 }
291
292 #[test]
293 fn detects_pruned_block_error() {
294 let err = rpc_internal_error("height 14862522 is not available, lowest height is 16853136");
295 assert!(err.is_block_pruned());
296 }
297
298 #[test]
299 fn ignores_other_internal_errors() {
300 assert!(
301 !rpc_internal_error("timed out waiting for tx to be included in a block")
302 .is_block_pruned()
303 );
304 }
305
306 #[test]
307 fn ignores_non_rpc_errors() {
308 assert!(!NyxdError::NoContractAddressAvailable("mixnet".to_string()).is_block_pruned());
309 }
310}