1use 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 #[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 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 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 .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
228 .filter_map(|(err, _, _)| {
229 match err.as_inner() {
230 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 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 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 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 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 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}