iota_json_rpc/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::BTreeMap;
6
7use fastcrypto::error::FastCryptoError;
8use hyper::header::InvalidHeaderValue;
9use iota_json_rpc_api::{
10    TRANSACTION_EXECUTION_CLIENT_ERROR_CODE, TRANSIENT_ERROR_CODE, error_object_from_rpc,
11};
12use iota_names::error::IotaNamesError;
13use iota_types::{
14    error::{IotaError, IotaObjectResponseError, UserInputError},
15    quorum_driver_types::QuorumDriverError,
16};
17use jsonrpsee::{
18    core::{ClientError as RpcError, RegisterMethodError},
19    types::{
20        ErrorObject, ErrorObjectOwned,
21        error::{CALL_EXECUTION_FAILED_CODE, ErrorCode, INTERNAL_ERROR_CODE},
22    },
23};
24use thiserror::Error;
25use tokio::task::JoinError;
26
27use crate::authority_state::StateReadError;
28
29pub type RpcInterimResult<T = ()> = Result<T, Error>;
30
31#[derive(Debug, Error)]
32#[non_exhaustive]
33pub enum Error {
34    #[error(transparent)]
35    Iota(IotaError),
36
37    #[error(transparent)]
38    Internal(#[from] anyhow::Error),
39
40    #[error("Deserialization error: {0}")]
41    Bcs(#[from] bcs::Error),
42    #[error("Unexpected error: {0}")]
43    Unexpected(String),
44
45    #[error(transparent)]
46    RPCServer(#[from] RpcError),
47    #[error(transparent)]
48    RPCRegisterMethod(#[from] RegisterMethodError),
49
50    #[error(transparent)]
51    InvalidHeaderValue(#[from] InvalidHeaderValue),
52
53    #[error(transparent)]
54    UserInput(#[from] UserInputError),
55
56    #[error(transparent)]
57    Encoding(#[from] eyre::Report),
58
59    #[error(transparent)]
60    TokioJoin(#[from] JoinError),
61
62    #[error(transparent)]
63    QuorumDriver(#[from] QuorumDriverError),
64
65    #[error(transparent)]
66    FastCrypto(#[from] FastCryptoError),
67
68    #[error(transparent)]
69    IotaObjectResponse(#[from] IotaObjectResponseError),
70
71    #[error(transparent)]
72    IotaRpcInput(#[from] IotaRpcInputError),
73
74    // TODO(wlmyng): convert StateReadError::Internal message to generic internal error message.
75    #[error(transparent)]
76    StateRead(#[from] StateReadError),
77
78    #[error("Unsupported Feature: {0}")]
79    UnsupportedFeature(String),
80
81    #[error(transparent)]
82    IotaNames(#[from] IotaNamesError),
83}
84
85impl From<IotaError> for Error {
86    fn from(e: IotaError) -> Self {
87        match e {
88            IotaError::UserInput { error } => Self::UserInput(error),
89            IotaError::IotaObjectResponse { error } => Self::IotaObjectResponse(error),
90            IotaError::UnsupportedFeature { error } => Self::UnsupportedFeature(error),
91            IotaError::IndexStoreNotAvailable => Self::UnsupportedFeature(
92                "Required indexes are not available on this node".to_string(),
93            ),
94            other => Self::Iota(other),
95        }
96    }
97}
98
99impl From<Error> for RpcError {
100    /// `InvalidParams`/`INVALID_PARAMS_CODE` for client errors.
101    fn from(e: Error) -> RpcError {
102        match e {
103            Error::UserInput(_) | Error::UnsupportedFeature(_) => RpcError::Call(
104                ErrorObject::owned::<()>(ErrorCode::InvalidRequest.code(), e.to_string(), None),
105            ),
106            Error::IotaObjectResponse(err) => match err {
107                IotaObjectResponseError::NotExists { .. }
108                | IotaObjectResponseError::DynamicFieldNotFound { .. }
109                | IotaObjectResponseError::Deleted { .. }
110                | IotaObjectResponseError::Display { .. } => {
111                    RpcError::Call(ErrorObject::owned::<()>(
112                        ErrorCode::InvalidParams.code(),
113                        err.to_string(),
114                        None,
115                    ))
116                }
117                _ => RpcError::Call(ErrorObject::owned::<()>(
118                    CALL_EXECUTION_FAILED_CODE,
119                    err.to_string(),
120                    None,
121                )),
122            },
123            Error::IotaRpcInput(err) => RpcError::Call(ErrorObject::owned::<()>(
124                ErrorCode::InvalidParams.code(),
125                err.to_string(),
126                None,
127            )),
128            Error::Iota(iota_error) => match iota_error {
129                IotaError::TransactionNotFound { .. }
130                | IotaError::TransactionsNotFound { .. }
131                | IotaError::TransactionEventsNotFound { .. } => {
132                    RpcError::Call(ErrorObject::owned::<()>(
133                        ErrorCode::InvalidParams.code(),
134                        iota_error.to_string(),
135                        None,
136                    ))
137                }
138                _ => RpcError::Call(ErrorObject::owned::<()>(
139                    CALL_EXECUTION_FAILED_CODE,
140                    iota_error.to_string(),
141                    None,
142                )),
143            },
144            Error::StateRead(err) => match err {
145                StateReadError::Client(_) => RpcError::Call(ErrorObject::owned::<()>(
146                    ErrorCode::InvalidParams.code(),
147                    err.to_string(),
148                    None,
149                )),
150                _ => {
151                    let error_object = ErrorObject::owned::<()>(
152                        jsonrpsee::types::error::INTERNAL_ERROR_CODE,
153                        err.to_string(),
154                        None,
155                    );
156                    RpcError::Call(error_object)
157                }
158            },
159            Error::QuorumDriver(err) => {
160                let error_msg = err.to_error_message();
161
162                match err {
163                    QuorumDriverError::InvalidUserSignature { .. }
164                    | QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures
165                    | QuorumDriverError::NonRecoverableTransactionError { .. } => {
166                        let error_object = ErrorObject::owned::<()>(
167                            TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
168                            error_msg,
169                            None,
170                        );
171                        RpcError::Call(error_object)
172                    }
173                    QuorumDriverError::ObjectsDoubleUsed { conflicting_txes } => {
174                        let new_map = conflicting_txes
175                            .into_iter()
176                            .map(|(digest, (pairs, _))| {
177                                (
178                                    digest,
179                                    pairs.into_iter().map(|(_, obj_ref)| obj_ref).collect(),
180                                )
181                            })
182                            .collect::<BTreeMap<_, Vec<_>>>();
183
184                        let error_object = ErrorObject::owned(
185                            TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
186                            error_msg,
187                            Some(new_map),
188                        );
189                        RpcError::Call(error_object)
190                    }
191                    QuorumDriverError::TimeoutBeforeFinality
192                    | QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. }
193                    | QuorumDriverError::SystemOverload { .. }
194                    | QuorumDriverError::SystemOverloadRetryAfter { .. } => {
195                        let error_object =
196                            ErrorObject::owned::<()>(TRANSIENT_ERROR_CODE, error_msg, None);
197                        RpcError::Call(error_object)
198                    }
199                    QuorumDriverError::QuorumDriverInternal(_) => {
200                        let error_object =
201                            ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, error_msg, None);
202                        RpcError::Call(error_object)
203                    }
204                }
205            }
206            _ => RpcError::Call(ErrorObject::owned::<()>(
207                CALL_EXECUTION_FAILED_CODE,
208                e.to_string(),
209                None,
210            )),
211        }
212    }
213}
214
215impl From<Error> for ErrorObjectOwned {
216    fn from(value: Error) -> Self {
217        error_object_from_rpc(value.into())
218    }
219}
220
221#[derive(Debug, Error)]
222pub enum IotaRpcInputError {
223    #[error("Input contains duplicates")]
224    ContainsDuplicates,
225
226    #[error("Input exceeds limit of {0}")]
227    SizeLimitExceeded(String),
228
229    #[error("{0}")]
230    GenericNotFound(String),
231
232    #[error("{0}")]
233    GenericInvalid(String),
234
235    #[error(
236        "request_type` must set to `None` or `WaitForLocalExecution` if effects is required in the response"
237    )]
238    InvalidExecuteTransactionRequestType,
239
240    #[error("Unsupported protocol version requested. Min supported: {0}, max supported: {1}")]
241    ProtocolVersionUnsupported(u64, u64),
242
243    #[error("{0}")]
244    CannotParseIotaStructTag(String),
245
246    #[error(transparent)]
247    Base64(#[from] eyre::Report),
248
249    #[error("Deserialization error: {0}")]
250    Bcs(#[from] bcs::Error),
251
252    #[error(transparent)]
253    FastCrypto(#[from] FastCryptoError),
254
255    #[error(transparent)]
256    Anyhow(#[from] anyhow::Error),
257
258    #[error(transparent)]
259    UserInput(#[from] UserInputError),
260}
261
262impl From<IotaRpcInputError> for RpcError {
263    fn from(e: IotaRpcInputError) -> Self {
264        RpcError::Call(ErrorObject::owned::<()>(
265            ErrorCode::InvalidParams.code(),
266            e.to_string(),
267            None,
268        ))
269    }
270}
271
272impl From<IotaRpcInputError> for ErrorObjectOwned {
273    fn from(value: IotaRpcInputError) -> Self {
274        error_object_from_rpc(value.into())
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use expect_test::expect;
281    use iota_types::{
282        base_types::{AuthorityName, ObjectID, ObjectRef, SequenceNumber},
283        committee::StakeUnit,
284        crypto::{AuthorityPublicKey, AuthorityPublicKeyBytes},
285        digests::{ObjectDigest, TransactionDigest},
286    };
287
288    use super::*;
289
290    fn test_object_ref() -> ObjectRef {
291        (
292            ObjectID::ZERO,
293            SequenceNumber::from_u64(0),
294            ObjectDigest::new([0; 32]),
295        )
296    }
297
298    mod match_quorum_driver_error_tests {
299        use super::*;
300
301        #[test]
302        fn test_invalid_user_signature() {
303            let quorum_driver_error =
304                QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature {
305                    error: "Test inner invalid signature".to_string(),
306                });
307
308            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
309
310            let error_object = error_object_from_rpc(rpc_error);
311            let expected_code = expect!["-32002"];
312            expected_code.assert_eq(&error_object.code().to_string());
313            let expected_message = expect![
314                "Invalid user signature: Signature is not valid: Test inner invalid signature"
315            ];
316            expected_message.assert_eq(error_object.message());
317        }
318
319        #[test]
320        fn test_timeout_before_finality() {
321            let quorum_driver_error = QuorumDriverError::TimeoutBeforeFinality;
322
323            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
324
325            let error_object = error_object_from_rpc(rpc_error);
326            let expected_code = expect!["-32050"];
327            expected_code.assert_eq(&error_object.code().to_string());
328            let expected_message = expect!["Transaction timed out before reaching finality"];
329            expected_message.assert_eq(error_object.message());
330        }
331
332        #[test]
333        fn test_failed_with_transient_error_after_maximum_attempts() {
334            let quorum_driver_error =
335                QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
336                    total_attempts: 10,
337                };
338
339            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
340
341            let error_object = error_object_from_rpc(rpc_error);
342            let expected_code = expect!["-32050"];
343            expected_code.assert_eq(&error_object.code().to_string());
344            let expected_message = expect![
345                "Transaction failed to reach finality with transient error after 10 attempts."
346            ];
347            expected_message.assert_eq(error_object.message());
348        }
349
350        #[test]
351        fn test_objects_double_used() {
352            use iota_types::crypto::VerifyingKey;
353            let mut conflicting_txes: BTreeMap<
354                TransactionDigest,
355                (Vec<(AuthorityName, ObjectRef)>, StakeUnit),
356            > = BTreeMap::new();
357            let tx_digest = TransactionDigest::from([1; 32]);
358            let object_ref = test_object_ref();
359            // 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi has enough stake to escape
360            // equivocation
361            let stake_unit: StakeUnit = 8000;
362            let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
363            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
364
365            // 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR stake below quorum threshold
366            let tx_digest = TransactionDigest::from([2; 32]);
367            let stake_unit: StakeUnit = 500;
368            let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
369            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
370
371            let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
372
373            let error_object: ErrorObjectOwned = Error::QuorumDriver(quorum_driver_error).into();
374
375            let expected_code = expect!["-32002"];
376            expected_code.assert_eq(&error_object.code().to_string());
377            println!("error_object.message() {}", error_object.message());
378            let expected_message = expect![[r#"
379                Failed to sign transaction by a quorum of validators because one or more of its objects is reserved for another transaction. Other transactions locking these objects:
380                - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 80.0)
381                - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 5.0)"#]];
382            expected_message.assert_eq(error_object.message());
383            let expected_data = expect![[
384                r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]]}"#
385            ]];
386            let actual_data = error_object.data().unwrap().to_string();
387            expected_data.assert_eq(&actual_data);
388        }
389
390        #[test]
391        fn test_objects_double_used_equivocated() {
392            use iota_types::crypto::VerifyingKey;
393            let mut conflicting_txes: BTreeMap<
394                TransactionDigest,
395                (Vec<(AuthorityName, ObjectRef)>, StakeUnit),
396            > = BTreeMap::new();
397            let tx_digest = TransactionDigest::from([1; 32]);
398            let object_ref = test_object_ref();
399
400            // 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi has lower stake at 10
401            let stake_unit: StakeUnit = 4000;
402            let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
403            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
404
405            // 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR is a higher stake and should be
406            // first in the list
407            let tx_digest = TransactionDigest::from([2; 32]);
408            let stake_unit: StakeUnit = 5000;
409            let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
410            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
411
412            let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
413
414            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
415
416            let error_object = error_object_from_rpc(rpc_error);
417            let expected_code = expect!["-32002"];
418            expected_code.assert_eq(&error_object.code().to_string());
419            let expected_message = expect![[r#"
420                Failed to sign transaction by a quorum of validators because one or more of its objects is equivocated until the next epoch. Other transactions locking these objects:
421                - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 50.0)
422                - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 40.0)"#]];
423            expected_message.assert_eq(error_object.message());
424            let expected_data = expect![[
425                r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]]}"#
426            ]];
427            let actual_data = error_object.data().unwrap().to_string();
428            expected_data.assert_eq(&actual_data);
429        }
430
431        #[test]
432        fn test_non_recoverable_transaction_error() {
433            let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
434                errors: vec![
435                    (
436                        IotaError::UserInput {
437                            error: UserInputError::GasBalanceTooLow {
438                                gas_balance: 10,
439                                needed_gas_amount: 100,
440                            },
441                        },
442                        0,
443                        vec![],
444                    ),
445                    (
446                        IotaError::UserInput {
447                            error: UserInputError::ObjectVersionUnavailableForConsumption {
448                                provided_obj_ref: test_object_ref(),
449                                current_version: 10.into(),
450                            },
451                        },
452                        0,
453                        vec![],
454                    ),
455                ],
456            };
457
458            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
459
460            let error_object = error_object_from_rpc(rpc_error);
461            let expected_code = expect!["-32002"];
462            expected_code.assert_eq(&error_object.code().to_string());
463            let expected_message = expect![
464                "Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n- Balance of gas object 10 is lower than the needed amount: 100\n- Object ID 0x0000000000000000000000000000000000000000000000000000000000000000 Version 0x0 Digest 11111111111111111111111111111111 is not available for consumption, current version: 0xa"
465            ];
466            expected_message.assert_eq(error_object.message());
467        }
468
469        #[test]
470        fn test_non_recoverable_transaction_error_with_transient_errors() {
471            let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
472                errors: vec![
473                    (
474                        IotaError::UserInput {
475                            error: UserInputError::ObjectNotFound {
476                                object_id: test_object_ref().0,
477                                version: None,
478                            },
479                        },
480                        0,
481                        vec![],
482                    ),
483                    (
484                        IotaError::Rpc("Hello".to_string(), "Testing".to_string()),
485                        0,
486                        vec![],
487                    ),
488                ],
489            };
490
491            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
492
493            let error_object = error_object_from_rpc(rpc_error);
494            let expected_code = expect!["-32002"];
495            expected_code.assert_eq(&error_object.code().to_string());
496            let expected_message = expect![
497                "Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n- Could not find the referenced object 0x0000000000000000000000000000000000000000000000000000000000000000 at version None"
498            ];
499            expected_message.assert_eq(error_object.message());
500        }
501
502        #[test]
503        fn test_quorum_driver_internal_error() {
504            let quorum_driver_error =
505                QuorumDriverError::QuorumDriverInternal(IotaError::UnexpectedMessage);
506
507            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
508
509            let error_object = error_object_from_rpc(rpc_error);
510            let expected_code = expect!["-32603"];
511            expected_code.assert_eq(&error_object.code().to_string());
512            let expected_message = expect!["Internal error occurred while executing transaction."];
513            expected_message.assert_eq(error_object.message());
514        }
515
516        #[test]
517        fn test_system_overload() {
518            let quorum_driver_error = QuorumDriverError::SystemOverload {
519                overloaded_stake: 10,
520                errors: vec![(IotaError::UnexpectedMessage, 0, vec![])],
521            };
522
523            let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
524
525            let error_object = error_object_from_rpc(rpc_error);
526            let expected_code = expect!["-32050"];
527            expected_code.assert_eq(&error_object.code().to_string());
528            let expected_message = expect![
529                "Transaction is not processed because 10 of validators by stake are overloaded with certificates pending execution."
530            ];
531            expected_message.assert_eq(error_object.message());
532        }
533    }
534}