sui_json_rpc/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::authority_state::StateReadError;
5use fastcrypto::error::FastCryptoError;
6use hyper::header::InvalidHeaderValue;
7use itertools::Itertools;
8use jsonrpsee::core::ClientError as RpcError;
9use jsonrpsee::types::error::INTERNAL_ERROR_CODE;
10use jsonrpsee::types::{ErrorObject, ErrorObjectOwned};
11use std::collections::BTreeMap;
12use sui_json_rpc_api::{TRANSACTION_EXECUTION_CLIENT_ERROR_CODE, TRANSIENT_ERROR_CODE};
13use sui_name_service::NameServiceError;
14use sui_types::committee::{QUORUM_THRESHOLD, TOTAL_VOTING_POWER};
15use sui_types::error::{
16    ErrorCategory, SuiError, SuiErrorKind, SuiObjectResponseError, UserInputError,
17};
18use sui_types::quorum_driver_types::QuorumDriverError;
19use thiserror::Error;
20use tokio::task::JoinError;
21
22pub type RpcInterimResult<T = ()> = Result<T, Error>;
23
24#[derive(Debug, Error)]
25pub enum Error {
26    #[error(transparent)]
27    SuiError(SuiError),
28
29    #[error(transparent)]
30    InternalError(#[from] anyhow::Error),
31
32    #[error("Deserialization error: {0}")]
33    BcsError(#[from] bcs::Error),
34    #[error("Unexpected error: {0}")]
35    UnexpectedError(String),
36
37    #[error(transparent)]
38    RPCServerError(#[from] jsonrpsee::core::ClientError),
39
40    #[error(transparent)]
41    RPCError(#[from] jsonrpsee::types::ErrorObjectOwned),
42
43    #[error(transparent)]
44    RegisterMethodError(#[from] jsonrpsee::server::RegisterMethodError),
45
46    #[error(transparent)]
47    InvalidHeaderValue(#[from] InvalidHeaderValue),
48
49    #[error(transparent)]
50    UserInputError(#[from] UserInputError),
51
52    #[error(transparent)]
53    EncodingError(#[from] eyre::Report),
54
55    #[error(transparent)]
56    TokioJoinError(#[from] JoinError),
57
58    #[error(transparent)]
59    QuorumDriverError(#[from] QuorumDriverError),
60
61    #[error(transparent)]
62    FastCryptoError(#[from] FastCryptoError),
63
64    #[error(transparent)]
65    SuiObjectResponseError(#[from] SuiObjectResponseError),
66
67    #[error(transparent)]
68    SuiRpcInputError(#[from] SuiRpcInputError),
69
70    // TODO(wlmyng): convert StateReadError::Internal message to generic internal error message.
71    #[error(transparent)]
72    StateReadError(#[from] StateReadError),
73
74    #[error("Unsupported Feature: {0}")]
75    UnsupportedFeature(String),
76
77    #[error("transparent")]
78    NameServiceError(#[from] NameServiceError),
79}
80
81impl From<SuiErrorKind> for Error {
82    fn from(e: SuiErrorKind) -> Self {
83        match e {
84            SuiErrorKind::UserInputError { error } => Self::UserInputError(error),
85            SuiErrorKind::SuiObjectResponseError { error } => Self::SuiObjectResponseError(error),
86            SuiErrorKind::UnsupportedFeatureError { error } => Self::UnsupportedFeature(error),
87            SuiErrorKind::IndexStoreNotAvailable => Self::UnsupportedFeature(
88                "Required indexes are not available on this node".to_string(),
89            ),
90            other => Self::SuiError(SuiError(Box::new(other))),
91        }
92    }
93}
94
95impl From<SuiError> for Error {
96    fn from(e: SuiError) -> Self {
97        e.into_inner().into()
98    }
99}
100
101fn invalid_params<E: std::fmt::Display>(e: E) -> ErrorObjectOwned {
102    ErrorObject::owned(
103        jsonrpsee::types::error::ErrorCode::InvalidParams.code(),
104        e.to_string(),
105        None::<()>,
106    )
107}
108
109fn failed<E: std::fmt::Display>(e: E) -> ErrorObjectOwned {
110    ErrorObject::owned(
111        jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE,
112        e.to_string(),
113        None::<()>,
114    )
115}
116
117impl From<Error> for ErrorObjectOwned {
118    /// `InvalidParams`/`INVALID_PARAMS_CODE` for client errors.
119    fn from(e: Error) -> ErrorObjectOwned {
120        match e {
121            Error::UserInputError(_) => invalid_params(e),
122            Error::UnsupportedFeature(_) => invalid_params(e),
123            Error::SuiObjectResponseError(err) => match err {
124                SuiObjectResponseError::NotExists { .. }
125                | SuiObjectResponseError::DynamicFieldNotFound { .. }
126                | SuiObjectResponseError::Deleted { .. }
127                | SuiObjectResponseError::DisplayError { .. } => invalid_params(err),
128                _ => failed(err),
129            },
130            Error::NameServiceError(err) => match err {
131                NameServiceError::ExceedsMaxLength { .. }
132                | NameServiceError::InvalidHyphens
133                | NameServiceError::InvalidLength { .. }
134                | NameServiceError::InvalidUnderscore
135                | NameServiceError::LabelsEmpty
136                | NameServiceError::InvalidSeparator => invalid_params(err),
137                _ => failed(err),
138            },
139            Error::SuiRpcInputError(err) => invalid_params(err),
140            Error::SuiError(sui_error) => match sui_error.as_inner() {
141                SuiErrorKind::TransactionNotFound { .. }
142                | SuiErrorKind::TransactionsNotFound { .. }
143                | SuiErrorKind::TransactionEventsNotFound { .. } => invalid_params(sui_error),
144                _ => failed(sui_error),
145            },
146            Error::StateReadError(err) => match err {
147                StateReadError::Client(_) => invalid_params(err),
148                _ => ErrorObject::owned(
149                    jsonrpsee::types::error::INTERNAL_ERROR_CODE,
150                    err.to_string(),
151                    None::<()>,
152                ),
153            },
154            Error::QuorumDriverError(err) => {
155                match err {
156                    QuorumDriverError::InvalidUserSignature(err) => ErrorObject::owned(
157                        TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
158                        format!("Invalid user signature: {err}"),
159                        None::<()>,
160                    ),
161                    QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures => {
162                        ErrorObject::owned(
163                            TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
164                            "The transaction is already finalized but with different user signatures",
165                            None::<()>,
166                        )
167                    }
168                    QuorumDriverError::TimeoutBeforeFinality
169                    | QuorumDriverError::TimeoutBeforeFinalityWithErrors { .. }
170                    | QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. } => {
171                        ErrorObject::owned(TRANSIENT_ERROR_CODE, err.to_string(), None::<()>)
172                    }
173                    QuorumDriverError::ObjectsDoubleUsed { conflicting_txes } => {
174                        let weights: Vec<u64> =
175                            conflicting_txes.values().map(|(_, stake)| *stake).collect();
176                        let remaining: u64 = TOTAL_VOTING_POWER - weights.iter().sum::<u64>();
177
178                        // better version of above
179                        let reason = if weights.iter().all(|w| remaining + w < QUORUM_THRESHOLD) {
180                            "equivocated until the next epoch"
181                        } else {
182                            "reserved for another transaction"
183                        };
184
185                        let error_message = format!(
186                            "Failed to sign transaction by a quorum of validators because one or more of its objects is {reason}. Other transactions locking these objects:\n{}",
187                            conflicting_txes
188                                .iter()
189                                .sorted_by(|(_, (_, a)), (_, (_, b))| b.cmp(a))
190                                .map(|(digest, (o, stake))| {
191                                    let objects = o
192                                        .iter()
193                                        .map(|(_, obj_ref)| format!("    - {}", obj_ref.0))
194                                        .join("\n");
195
196                                    format!(
197                                        "- {} (stake {}.{})\n{}",
198                                        digest,
199                                        stake / 100,
200                                        stake % 100,
201                                        objects,
202                                    )
203                                })
204                                .join("\n"),
205                        );
206
207                        let new_map = conflicting_txes
208                            .into_iter()
209                            .map(|(digest, (pairs, _))| {
210                                (
211                                    digest,
212                                    pairs.into_iter().map(|(_, obj_ref)| obj_ref).collect(),
213                                )
214                            })
215                            .collect::<BTreeMap<_, Vec<_>>>();
216
217                        ErrorObject::owned(
218                            TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
219                            error_message,
220                            Some(new_map),
221                        )
222                    }
223                    QuorumDriverError::NonRecoverableTransactionError { errors } => {
224                        let new_errors: Vec<String> = errors
225                            .into_iter()
226                            // sort by total stake, descending, so users see the most prominent one first
227                            .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
228                            .filter_map(|(err, _, _)| {
229                                match err.as_inner() {
230                                    // Special handling of UserInputError:
231                                    // ObjectNotFound and DependentPackageNotFound are considered
232                                    // retryable errors but they have different treatment
233                                    // in AuthorityAggregator.
234                                    // The optimal fix would be to examine if the total stake
235                                    // of ObjectNotFound/DependentPackageNotFound exceeds the
236                                    // quorum threshold, but it takes a Committee here.
237                                    // So, we take an easier route and consider them non-retryable
238                                    // at all. Combining this with the sorting above, clients will
239                                    // see the dominant error first.
240                                    SuiErrorKind::UserInputError { error } => {
241                                        Some(error.to_string())
242                                    }
243                                    _ => {
244                                        if err.is_retryable().0 {
245                                            None
246                                        } else {
247                                            Some(err.to_string())
248                                        }
249                                    }
250                                }
251                            })
252                            .collect();
253
254                        assert!(
255                            !new_errors.is_empty(),
256                            "NonRecoverableTransactionError should have at least one non-retryable error"
257                        );
258
259                        let mut error_list = vec![];
260
261                        for err in new_errors.iter() {
262                            error_list.push(format!("- {}", err));
263                        }
264
265                        let error_msg = format!(
266                            "Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n{}",
267                            error_list.join("\n")
268                        );
269
270                        ErrorObject::owned(
271                            TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
272                            error_msg,
273                            None::<()>,
274                        )
275                    }
276                    QuorumDriverError::QuorumDriverInternalError(_) => ErrorObject::owned(
277                        INTERNAL_ERROR_CODE,
278                        "Internal error occurred while executing transaction.",
279                        None::<()>,
280                    ),
281                    QuorumDriverError::SystemOverload { .. }
282                    | QuorumDriverError::SystemOverloadRetryAfter { .. } => {
283                        ErrorObject::owned(TRANSIENT_ERROR_CODE, err.to_string(), None::<()>)
284                    }
285                    QuorumDriverError::TransactionFailed { category, details } => {
286                        let code = match category {
287                            ErrorCategory::Internal => INTERNAL_ERROR_CODE,
288                            ErrorCategory::Aborted => TRANSIENT_ERROR_CODE,
289                            ErrorCategory::InvalidTransaction => {
290                                TRANSACTION_EXECUTION_CLIENT_ERROR_CODE
291                            }
292                            ErrorCategory::LockConflict => TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
293                            ErrorCategory::ValidatorOverloaded => TRANSIENT_ERROR_CODE,
294                            ErrorCategory::Unavailable => INTERNAL_ERROR_CODE,
295                        };
296                        ErrorObject::owned(code, details, None::<()>)
297                    }
298                    QuorumDriverError::PendingExecutionInTransactionOrchestrator => {
299                        // TODO(fastpath): Remove once traffic is 100% TD
300                        ErrorObject::owned(
301                            TRANSIENT_ERROR_CODE,
302                            "[MFP experimental]: Transaction already being processed in transaction orchestrator (most likely by quorum driver), wait for results",
303                            None::<()>,
304                        )
305                    }
306                }
307            }
308            _ => failed(e),
309        }
310    }
311}
312
313#[derive(Debug, Error)]
314pub enum SuiRpcInputError {
315    #[error("Input contains duplicates")]
316    ContainsDuplicates,
317
318    #[error("Input exceeds limit of {0}")]
319    SizeLimitExceeded(String),
320
321    #[error("{0}")]
322    GenericNotFound(String),
323
324    #[error("{0}")]
325    GenericInvalid(String),
326
327    #[error(
328        "request_type` must set to `None` or `WaitForLocalExecution` if effects is required in the response"
329    )]
330    InvalidExecuteTransactionRequestType,
331
332    #[error("Unsupported protocol version requested. Min supported: {0}, max supported: {1}")]
333    ProtocolVersionUnsupported(u64, u64),
334
335    #[error("{0}")]
336    CannotParseSuiStructTag(String),
337
338    #[error(transparent)]
339    Base64(#[from] eyre::Report),
340
341    #[error("Deserialization error: {0}")]
342    Bcs(#[from] bcs::Error),
343
344    #[error(transparent)]
345    FastCryptoError(#[from] FastCryptoError),
346
347    #[error(transparent)]
348    Anyhow(#[from] anyhow::Error),
349
350    #[error(transparent)]
351    UserInputError(#[from] UserInputError),
352}
353
354impl From<SuiRpcInputError> for RpcError {
355    fn from(e: SuiRpcInputError) -> Self {
356        RpcError::Call(invalid_params(e))
357    }
358}
359
360impl From<SuiRpcInputError> for ErrorObjectOwned {
361    fn from(e: SuiRpcInputError) -> Self {
362        invalid_params(e)
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use expect_test::expect;
370    use jsonrpsee::types::ErrorObjectOwned;
371    use sui_types::base_types::AuthorityName;
372    use sui_types::base_types::ObjectID;
373    use sui_types::base_types::ObjectRef;
374    use sui_types::base_types::SequenceNumber;
375    use sui_types::committee::StakeUnit;
376    use sui_types::crypto::AuthorityPublicKey;
377    use sui_types::crypto::AuthorityPublicKeyBytes;
378    use sui_types::digests::ObjectDigest;
379    use sui_types::digests::TransactionDigest;
380
381    fn test_object_ref(id: u8) -> ObjectRef {
382        (
383            ObjectID::from_single_byte(id),
384            SequenceNumber::from_u64(0),
385            ObjectDigest::new([id; 32]),
386        )
387    }
388
389    mod match_quorum_driver_error_tests {
390        use sui_types::error::SuiErrorKind;
391
392        use super::*;
393
394        #[test]
395        fn test_invalid_user_signature() {
396            let quorum_driver_error = QuorumDriverError::InvalidUserSignature(
397                SuiErrorKind::InvalidSignature {
398                    error: "Test inner invalid signature".to_string(),
399                }
400                .into(),
401            );
402
403            let error_object: ErrorObjectOwned =
404                Error::QuorumDriverError(quorum_driver_error).into();
405            let expected_code = expect!["-32002"];
406            expected_code.assert_eq(&error_object.code().to_string());
407            let expected_message = expect![
408                "Invalid user signature: Signature is not valid: Test inner invalid signature"
409            ];
410            expected_message.assert_eq(error_object.message());
411        }
412
413        #[test]
414        fn test_timeout_before_finality() {
415            let quorum_driver_error = QuorumDriverError::TimeoutBeforeFinality;
416
417            let error_object: ErrorObjectOwned =
418                Error::QuorumDriverError(quorum_driver_error).into();
419            let expected_code = expect!["-32050"];
420            expected_code.assert_eq(&error_object.code().to_string());
421            let expected_message = expect!["Transaction timed out before reaching finality"];
422            expected_message.assert_eq(error_object.message());
423        }
424
425        #[test]
426        fn test_failed_with_transient_error_after_maximum_attempts() {
427            let quorum_driver_error =
428                QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
429                    total_attempts: 10,
430                };
431
432            let error_object: ErrorObjectOwned =
433                Error::QuorumDriverError(quorum_driver_error).into();
434            let expected_code = expect!["-32050"];
435            expected_code.assert_eq(&error_object.code().to_string());
436            let expected_message = expect![
437                "Transaction failed to reach finality with transient error after 10 attempts."
438            ];
439            expected_message.assert_eq(error_object.message());
440        }
441
442        #[test]
443        fn test_objects_double_used() {
444            use sui_types::crypto::VerifyingKey;
445            let mut conflicting_txes: BTreeMap<
446                TransactionDigest,
447                (Vec<(AuthorityName, ObjectRef)>, StakeUnit),
448            > = BTreeMap::new();
449            let tx_digest = TransactionDigest::from([1; 32]);
450            let object_ref = test_object_ref(0);
451
452            // 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi has enough stake to escape equivocation
453            let stake_unit: StakeUnit = 8000;
454            let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
455            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
456
457            // 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR stake below quorum threshold
458            let tx_digest = TransactionDigest::from([2; 32]);
459            let stake_unit: StakeUnit = 500;
460            let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
461            conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
462
463            let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
464
465            let error_object: ErrorObjectOwned =
466                Error::QuorumDriverError(quorum_driver_error).into();
467            let expected_code = expect!["-32002"];
468            expected_code.assert_eq(&error_object.code().to_string());
469            println!("error_object.message() {}", error_object.message());
470            let expected_message = expect![[r#"
471                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:
472                - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 80.0)
473                    - 0x0000000000000000000000000000000000000000000000000000000000000000
474                - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 5.0)
475                    - 0x0000000000000000000000000000000000000000000000000000000000000000"#]];
476            expected_message.assert_eq(error_object.message());
477            let expected_data = expect![[
478                r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"]]}"#
479            ]];
480            let actual_data = error_object.data().unwrap().to_string();
481            expected_data.assert_eq(&actual_data);
482        }
483
484        #[test]
485        fn test_objects_double_used_equivocated() {
486            use sui_types::crypto::VerifyingKey;
487            let mut conflicting_txes: BTreeMap<
488                TransactionDigest,
489                (Vec<(AuthorityName, ObjectRef)>, StakeUnit),
490            > = BTreeMap::new();
491            let tx_digest = TransactionDigest::from([1; 32]);
492            let object_ref_1 = test_object_ref(0);
493            let object_ref_2 = test_object_ref(1);
494
495            // 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi has lower stake at 10
496            let stake_unit: StakeUnit = 4000;
497            let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
498            conflicting_txes.insert(
499                tx_digest,
500                (
501                    vec![
502                        (authority_name, object_ref_1),
503                        (authority_name, object_ref_2),
504                    ],
505                    stake_unit,
506                ),
507            );
508
509            // 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR is a higher stake and should be first in the list
510            let tx_digest = TransactionDigest::from([2; 32]);
511            let stake_unit: StakeUnit = 5000;
512            let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
513            conflicting_txes.insert(
514                tx_digest,
515                (
516                    vec![
517                        (authority_name, object_ref_1),
518                        (authority_name, object_ref_2),
519                    ],
520                    stake_unit,
521                ),
522            );
523
524            let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
525
526            let error_object: ErrorObjectOwned =
527                Error::QuorumDriverError(quorum_driver_error).into();
528            let expected_code = expect!["-32002"];
529            expected_code.assert_eq(&error_object.code().to_string());
530            let expected_message = expect![[r#"
531                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:
532                - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 50.0)
533                    - 0x0000000000000000000000000000000000000000000000000000000000000000
534                    - 0x0000000000000000000000000000000000000000000000000000000000000001
535                - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 40.0)
536                    - 0x0000000000000000000000000000000000000000000000000000000000000000
537                    - 0x0000000000000000000000000000000000000000000000000000000000000001"#]];
538            expected_message.assert_eq(error_object.message());
539            let expected_data = expect![[
540                r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"],["0x0000000000000000000000000000000000000000000000000000000000000001",0,"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi"]],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[["0x0000000000000000000000000000000000000000000000000000000000000000",0,"11111111111111111111111111111111"],["0x0000000000000000000000000000000000000000000000000000000000000001",0,"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi"]]}"#
541            ]];
542            let actual_data = error_object.data().unwrap().to_string();
543            expected_data.assert_eq(&actual_data);
544        }
545
546        #[test]
547        fn test_non_recoverable_transaction_error() {
548            let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
549                errors: vec![
550                    (
551                        SuiErrorKind::UserInputError {
552                            error: UserInputError::GasBalanceTooLow {
553                                gas_balance: 10,
554                                needed_gas_amount: 100,
555                            },
556                        }
557                        .into(),
558                        0,
559                        vec![],
560                    ),
561                    (
562                        SuiErrorKind::UserInputError {
563                            error: UserInputError::ObjectVersionUnavailableForConsumption {
564                                provided_obj_ref: test_object_ref(0),
565                                current_version: 10.into(),
566                            },
567                        }
568                        .into(),
569                        0,
570                        vec![],
571                    ),
572                ],
573            };
574
575            let error_object: ErrorObjectOwned =
576                Error::QuorumDriverError(quorum_driver_error).into();
577            let expected_code = expect!["-32002"];
578            expected_code.assert_eq(&error_object.code().to_string());
579            let expected_message = expect![
580                "Transaction validator signing 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"
581            ];
582            expected_message.assert_eq(error_object.message());
583        }
584
585        #[test]
586        fn test_non_recoverable_transaction_error_with_transient_errors() {
587            let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
588                errors: vec![
589                    (
590                        SuiErrorKind::UserInputError {
591                            error: UserInputError::ObjectNotFound {
592                                object_id: test_object_ref(0).0,
593                                version: None,
594                            },
595                        }
596                        .into(),
597                        0,
598                        vec![],
599                    ),
600                    (
601                        SuiErrorKind::RpcError("Hello".to_string(), "Testing".to_string()).into(),
602                        0,
603                        vec![],
604                    ),
605                ],
606            };
607
608            let error_object: ErrorObjectOwned =
609                Error::QuorumDriverError(quorum_driver_error).into();
610            let expected_code = expect!["-32002"];
611            expected_code.assert_eq(&error_object.code().to_string());
612            let expected_message = expect![
613                "Transaction validator signing 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"
614            ];
615            expected_message.assert_eq(error_object.message());
616        }
617
618        #[test]
619        fn test_quorum_driver_internal_error() {
620            let quorum_driver_error = QuorumDriverError::QuorumDriverInternalError(
621                SuiErrorKind::UnexpectedMessage("test".to_string()).into(),
622            );
623
624            let error_object: ErrorObjectOwned =
625                Error::QuorumDriverError(quorum_driver_error).into();
626            let expected_code = expect!["-32603"];
627            expected_code.assert_eq(&error_object.code().to_string());
628            let expected_message = expect!["Internal error occurred while executing transaction."];
629            expected_message.assert_eq(error_object.message());
630        }
631
632        #[test]
633        fn test_system_overload() {
634            let quorum_driver_error = QuorumDriverError::SystemOverload {
635                overloaded_stake: 10,
636                errors: vec![(
637                    SuiErrorKind::UnexpectedMessage("test".to_string()).into(),
638                    0,
639                    vec![],
640                )],
641            };
642
643            let error_object: ErrorObjectOwned =
644                Error::QuorumDriverError(quorum_driver_error).into();
645            let expected_code = expect!["-32050"];
646            expected_code.assert_eq(&error_object.code().to_string());
647            let expected_message = expect![
648                "Transaction is not processed because 10 of validators by stake are overloaded with certificates pending execution."
649            ];
650            expected_message.assert_eq(error_object.message());
651        }
652    }
653}