1use std::collections::BTreeMap;
6
7use fastcrypto::error::FastCryptoError;
8use hyper::header::InvalidHeaderValue;
9use iota_json_rpc_api::{
10 TRANSACTION_EXECUTION_CLIENT_ERROR_CODE, TRANSACTION_NOT_FOUND_ERROR_CODE,
11 TRANSIENT_ERROR_CODE, error_object_from_rpc,
12};
13use iota_json_rpc_types::IotaObjectResponseError;
14use iota_names::error::IotaNamesError;
15use iota_types::{
16 error::{IotaError, UserInputError},
17 quorum_driver_types::QuorumDriverError,
18};
19use jsonrpsee::{
20 core::{ClientError as RpcError, RegisterMethodError},
21 types::{
22 ErrorObject, ErrorObjectOwned,
23 error::{CALL_EXECUTION_FAILED_CODE, ErrorCode, INTERNAL_ERROR_CODE},
24 },
25};
26use thiserror::Error;
27use tokio::task::JoinError;
28
29use crate::authority_state::StateReadError;
30
31pub type RpcInterimResult<T = ()> = Result<T, Error>;
32
33#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum Error {
36 #[error(transparent)]
37 Iota(IotaError),
38
39 #[error(transparent)]
40 Internal(#[from] anyhow::Error),
41
42 #[error("Deserialization error: {0}")]
43 Bcs(#[from] bcs::Error),
44 #[error("Unexpected error: {0}")]
45 Unexpected(String),
46
47 #[error(transparent)]
48 RPCServer(#[from] RpcError),
49 #[error(transparent)]
50 RPCRegisterMethod(#[from] RegisterMethodError),
51
52 #[error(transparent)]
53 InvalidHeaderValue(#[from] InvalidHeaderValue),
54
55 #[error(transparent)]
56 UserInput(#[from] UserInputError),
57
58 #[error(transparent)]
59 Encoding(#[from] eyre::Report),
60
61 #[error(transparent)]
62 TokioJoin(#[from] JoinError),
63
64 #[error(transparent)]
65 QuorumDriver(#[from] QuorumDriverError),
66
67 #[error(transparent)]
68 FastCrypto(#[from] FastCryptoError),
69
70 #[error(transparent)]
71 IotaObjectResponse(#[from] IotaObjectResponseError),
72
73 #[error(transparent)]
74 IotaRpcInput(#[from] IotaRpcInputError),
75
76 #[error(transparent)]
78 StateRead(#[from] StateReadError),
79
80 #[error("Unsupported Feature: {0}")]
81 UnsupportedFeature(String),
82
83 #[error(transparent)]
84 IotaNames(#[from] IotaNamesError),
85}
86
87impl From<IotaError> for Error {
88 fn from(e: IotaError) -> Self {
89 match e {
90 IotaError::UserInput { error } => Self::UserInput(error),
91 IotaError::UnsupportedFeature { error } => Self::UnsupportedFeature(error),
92 IotaError::IndexStoreNotAvailable => Self::UnsupportedFeature(
93 "Required indexes are not available on this node".to_string(),
94 ),
95 other => Self::Iota(other),
96 }
97 }
98}
99
100impl From<Error> for RpcError {
101 fn from(e: Error) -> RpcError {
103 match e {
104 Error::UserInput(_) | Error::UnsupportedFeature(_) => RpcError::Call(
105 ErrorObject::owned::<()>(ErrorCode::InvalidRequest.code(), e.to_string(), None),
106 ),
107 Error::IotaObjectResponse(err) => match err {
108 IotaObjectResponseError::NotExists { .. }
109 | IotaObjectResponseError::DynamicFieldNotFound { .. }
110 | IotaObjectResponseError::Deleted { .. }
111 | IotaObjectResponseError::Display { .. } => {
112 RpcError::Call(ErrorObject::owned::<()>(
113 ErrorCode::InvalidParams.code(),
114 err.to_string(),
115 None,
116 ))
117 }
118 _ => RpcError::Call(ErrorObject::owned::<()>(
119 CALL_EXECUTION_FAILED_CODE,
120 err.to_string(),
121 None,
122 )),
123 },
124 Error::IotaRpcInput(err) => RpcError::Call(ErrorObject::owned::<()>(
125 ErrorCode::InvalidParams.code(),
126 err.to_string(),
127 None,
128 )),
129 Error::Iota(iota_error) => match iota_error {
130 IotaError::TransactionNotFound { .. } => RpcError::Call(ErrorObject::owned::<()>(
131 TRANSACTION_NOT_FOUND_ERROR_CODE,
132 iota_error.to_string(),
133 None,
134 )),
135 IotaError::TransactionsNotFound { .. }
136 | IotaError::TransactionEventsNotFound { .. } => {
137 RpcError::Call(ErrorObject::owned::<()>(
138 ErrorCode::InvalidParams.code(),
139 iota_error.to_string(),
140 None,
141 ))
142 }
143 _ => RpcError::Call(ErrorObject::owned::<()>(
144 CALL_EXECUTION_FAILED_CODE,
145 iota_error.to_string(),
146 None,
147 )),
148 },
149 Error::StateRead(err) => match err {
150 StateReadError::Client(_) => RpcError::Call(ErrorObject::owned::<()>(
151 ErrorCode::InvalidParams.code(),
152 err.to_string(),
153 None,
154 )),
155 _ => {
156 let error_object = ErrorObject::owned::<()>(
157 jsonrpsee::types::error::INTERNAL_ERROR_CODE,
158 err.to_string(),
159 None,
160 );
161 RpcError::Call(error_object)
162 }
163 },
164 Error::QuorumDriver(err) => {
165 let error_msg = err.to_error_message();
166 let mut data = serde_json::json!({ "reason": err.reason() });
171 if let QuorumDriverError::SystemOverloadRetryAfter {
172 retry_after_secs, ..
173 } = &err
174 {
175 data["retry_after_secs"] = (*retry_after_secs).into();
176 }
177
178 match err {
179 QuorumDriverError::InvalidUserSignature { .. }
180 | QuorumDriverError::InvalidTransaction { .. }
181 | QuorumDriverError::RejectedByValidators { .. }
182 | QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures
183 | QuorumDriverError::NonRecoverableTransactionError { .. } => {
184 let error_object = ErrorObject::owned(
185 TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
186 error_msg,
187 Some(data),
188 );
189 RpcError::Call(error_object)
190 }
191 QuorumDriverError::ObjectsDoubleUsed { conflicting_txes } => {
192 let new_map = conflicting_txes
193 .into_iter()
194 .map(|(digest, (pairs, _))| {
195 (
196 digest,
197 pairs.into_iter().map(|(_, obj_ref)| obj_ref).collect(),
198 )
199 })
200 .collect::<BTreeMap<_, Vec<_>>>();
201
202 let error_object = ErrorObject::owned(
203 TRANSACTION_EXECUTION_CLIENT_ERROR_CODE,
204 error_msg,
205 Some(new_map),
206 );
207 RpcError::Call(error_object)
208 }
209 QuorumDriverError::TimeoutBeforeFinality
210 | QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. }
211 | QuorumDriverError::SystemOverload { .. }
212 | QuorumDriverError::SystemOverloadRetryAfter { .. } => {
213 let error_object =
214 ErrorObject::owned(TRANSIENT_ERROR_CODE, error_msg, Some(data));
215 RpcError::Call(error_object)
216 }
217 QuorumDriverError::QuorumDriverInternal(_) => {
218 let error_object =
219 ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, error_msg, None);
220 RpcError::Call(error_object)
221 }
222 }
223 }
224 _ => RpcError::Call(ErrorObject::owned::<()>(
225 CALL_EXECUTION_FAILED_CODE,
226 e.to_string(),
227 None,
228 )),
229 }
230 }
231}
232
233impl From<Error> for ErrorObjectOwned {
234 fn from(value: Error) -> Self {
235 error_object_from_rpc(value.into())
236 }
237}
238
239#[derive(Debug, Error)]
240pub enum IotaRpcInputError {
241 #[error("Input contains duplicates")]
242 ContainsDuplicates,
243
244 #[error("Input exceeds limit of {0}")]
245 SizeLimitExceeded(String),
246
247 #[error("{0}")]
248 GenericNotFound(String),
249
250 #[error("{0}")]
251 GenericInvalid(String),
252
253 #[error("Unsupported protocol version requested. Min supported: {0}, max supported: {1}")]
254 ProtocolVersionUnsupported(u64, u64),
255
256 #[error("{0}")]
257 CannotParseIotaStructTag(String),
258
259 #[error(transparent)]
260 Base64(#[from] eyre::Report),
261
262 #[error("Deserialization error: {0}")]
263 Bcs(#[from] bcs::Error),
264
265 #[error(transparent)]
266 FastCrypto(#[from] FastCryptoError),
267
268 #[error(transparent)]
269 Anyhow(#[from] anyhow::Error),
270
271 #[error(transparent)]
272 UserInput(#[from] UserInputError),
273}
274
275impl From<IotaRpcInputError> for RpcError {
276 fn from(e: IotaRpcInputError) -> Self {
277 RpcError::Call(ErrorObject::owned::<()>(
278 ErrorCode::InvalidParams.code(),
279 e.to_string(),
280 None,
281 ))
282 }
283}
284
285impl From<IotaRpcInputError> for ErrorObjectOwned {
286 fn from(value: IotaRpcInputError) -> Self {
287 error_object_from_rpc(value.into())
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use expect_test::expect;
294 use iota_sdk_types::{ObjectDigest, ObjectId, ObjectReference, TransactionDigest, Version};
295 use iota_types::{
296 base_types::AuthorityName,
297 committee::StakeUnit,
298 crypto::{AuthorityPublicKey, AuthorityPublicKeyBytes},
299 };
300
301 use super::*;
302
303 fn test_object_ref() -> ObjectReference {
304 ObjectReference::new(
305 ObjectId::ZERO,
306 Version::from_u64(0),
307 ObjectDigest::new([0; 32]),
308 )
309 }
310
311 #[test]
312 fn transaction_not_found_uses_dedicated_error_code() {
313 let error = Error::Iota(IotaError::TransactionNotFound {
314 digest: TransactionDigest::from([1; 32]),
315 });
316
317 let rpc_error: RpcError = error.into();
318 let error_object = error_object_from_rpc(rpc_error);
319
320 assert_eq!(error_object.code(), TRANSACTION_NOT_FOUND_ERROR_CODE);
321 }
322
323 mod match_quorum_driver_error_tests {
324 use super::*;
325
326 #[test]
327 fn test_invalid_user_signature() {
328 let quorum_driver_error =
329 QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature {
330 error: "Test inner invalid signature".to_string(),
331 });
332
333 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
334
335 let error_object = error_object_from_rpc(rpc_error);
336 let expected_code = expect!["-32002"];
337 expected_code.assert_eq(&error_object.code().to_string());
338 let expected_message = expect![
339 "Invalid user signature: Signature is not valid: Test inner invalid signature"
340 ];
341 expected_message.assert_eq(error_object.message());
342 let expected_data = expect![[r#"{"reason":"invalid_user_signature"}"#]];
343 expected_data.assert_eq(&error_object.data().unwrap().to_string());
344 }
345
346 #[test]
347 fn test_invalid_transaction_vs_rejected_by_validators() {
348 let inner = || IotaError::Unknown("Test rejection".to_string());
352
353 let error_object = error_object_from_rpc(
354 Error::QuorumDriver(QuorumDriverError::InvalidTransaction(inner())).into(),
355 );
356 let expected_code = expect!["-32002"];
357 expected_code.assert_eq(&error_object.code().to_string());
358 let expected_message = expect!["Invalid transaction: unknown error: Test rejection"];
359 expected_message.assert_eq(error_object.message());
360 let expected_data = expect![[r#"{"reason":"invalid_transaction"}"#]];
361 expected_data.assert_eq(&error_object.data().unwrap().to_string());
362
363 let error_object = error_object_from_rpc(
364 Error::QuorumDriver(QuorumDriverError::RejectedByValidators(inner())).into(),
365 );
366 let expected_code = expect!["-32002"];
367 expected_code.assert_eq(&error_object.code().to_string());
368 let expected_message = expect!["Invalid transaction: unknown error: Test rejection"];
369 expected_message.assert_eq(error_object.message());
370 let expected_data = expect![[r#"{"reason":"rejected_by_validators"}"#]];
371 expected_data.assert_eq(&error_object.data().unwrap().to_string());
372 }
373
374 #[test]
375 fn test_timeout_before_finality() {
376 let quorum_driver_error = QuorumDriverError::TimeoutBeforeFinality;
377
378 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
379
380 let error_object = error_object_from_rpc(rpc_error);
381 let expected_code = expect!["-32050"];
382 expected_code.assert_eq(&error_object.code().to_string());
383 let expected_message = expect!["Transaction timed out before reaching finality"];
384 expected_message.assert_eq(error_object.message());
385 let expected_data = expect![[r#"{"reason":"timeout_before_finality"}"#]];
386 expected_data.assert_eq(&error_object.data().unwrap().to_string());
387 }
388
389 #[test]
390 fn test_failed_with_transient_error_after_maximum_attempts() {
391 let quorum_driver_error =
392 QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
393 total_attempts: 10,
394 };
395
396 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
397
398 let error_object = error_object_from_rpc(rpc_error);
399 let expected_code = expect!["-32050"];
400 expected_code.assert_eq(&error_object.code().to_string());
401 let expected_message = expect![
402 "Transaction failed to reach finality with transient error after 10 attempts."
403 ];
404 expected_message.assert_eq(error_object.message());
405 let expected_data =
406 expect![[r#"{"reason":"failed_with_transient_error_after_maximum_attempts"}"#]];
407 expected_data.assert_eq(&error_object.data().unwrap().to_string());
408 }
409
410 #[test]
411 fn test_objects_double_used() {
412 use iota_types::crypto::VerifyingKey;
413 let mut conflicting_txes: BTreeMap<
414 TransactionDigest,
415 (Vec<(AuthorityName, ObjectReference)>, StakeUnit),
416 > = BTreeMap::new();
417 let tx_digest = TransactionDigest::from([1; 32]);
418 let object_ref = test_object_ref();
419 let stake_unit: StakeUnit = 8000;
422 let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
423 conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
424
425 let tx_digest = TransactionDigest::from([2; 32]);
427 let stake_unit: StakeUnit = 500;
428 let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
429 conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
430
431 let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
432
433 let error_object: ErrorObjectOwned = Error::QuorumDriver(quorum_driver_error).into();
434
435 let expected_code = expect!["-32002"];
436 expected_code.assert_eq(&error_object.code().to_string());
437 println!("error_object.message() {}", error_object.message());
438 let expected_message = expect![[r#"
439 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:
440 - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 80.0)
441 - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 5.0)"#]];
442 expected_message.assert_eq(error_object.message());
443 let expected_data = expect![[
444 r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[{"object_id":"0x0000000000000000000000000000000000000000000000000000000000000000","version":"0","digest":"11111111111111111111111111111111"}],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[{"object_id":"0x0000000000000000000000000000000000000000000000000000000000000000","version":"0","digest":"11111111111111111111111111111111"}]}"#
445 ]];
446 let actual_data = error_object.data().unwrap().to_string();
447 expected_data.assert_eq(&actual_data);
448 }
449
450 #[test]
451 fn test_objects_double_used_equivocated() {
452 use iota_types::crypto::VerifyingKey;
453 let mut conflicting_txes: BTreeMap<
454 TransactionDigest,
455 (Vec<(AuthorityName, ObjectReference)>, StakeUnit),
456 > = BTreeMap::new();
457 let tx_digest = TransactionDigest::from([1; 32]);
458 let object_ref = test_object_ref();
459
460 let stake_unit: StakeUnit = 4000;
462 let authority_name = AuthorityPublicKeyBytes([0; AuthorityPublicKey::LENGTH]);
463 conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
464
465 let tx_digest = TransactionDigest::from([2; 32]);
468 let stake_unit: StakeUnit = 5000;
469 let authority_name = AuthorityPublicKeyBytes([1; AuthorityPublicKey::LENGTH]);
470 conflicting_txes.insert(tx_digest, (vec![(authority_name, object_ref)], stake_unit));
471
472 let quorum_driver_error = QuorumDriverError::ObjectsDoubleUsed { conflicting_txes };
473
474 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
475
476 let error_object = error_object_from_rpc(rpc_error);
477 let expected_code = expect!["-32002"];
478 expected_code.assert_eq(&error_object.code().to_string());
479 let expected_message = expect![[r#"
480 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:
481 - 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR (stake 50.0)
482 - 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (stake 40.0)"#]];
483 expected_message.assert_eq(error_object.message());
484 let expected_data = expect![[
485 r#"{"4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi":[{"object_id":"0x0000000000000000000000000000000000000000000000000000000000000000","version":"0","digest":"11111111111111111111111111111111"}],"8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR":[{"object_id":"0x0000000000000000000000000000000000000000000000000000000000000000","version":"0","digest":"11111111111111111111111111111111"}]}"#
486 ]];
487 let actual_data = error_object.data().unwrap().to_string();
488 expected_data.assert_eq(&actual_data);
489 }
490
491 #[test]
492 fn test_non_recoverable_transaction_error() {
493 let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
494 errors: vec![
495 (
496 IotaError::UserInput {
497 error: UserInputError::GasBalanceTooLow {
498 gas_balance: 10,
499 needed_gas_amount: 100,
500 },
501 },
502 0,
503 vec![],
504 ),
505 (
506 IotaError::UserInput {
507 error: UserInputError::ObjectVersionUnavailableForConsumption {
508 provided_obj_ref: test_object_ref(),
509 current_version: 10.into(),
510 },
511 },
512 0,
513 vec![],
514 ),
515 ],
516 };
517
518 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
519
520 let error_object = error_object_from_rpc(rpc_error);
521 let expected_code = expect!["-32002"];
522 expected_code.assert_eq(&error_object.code().to_string());
523 let expected_message = expect![
524 "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 0 Digest 11111111111111111111111111111111 is not available for consumption, current version: 10"
525 ];
526 expected_message.assert_eq(error_object.message());
527 let expected_data = expect![[r#"{"reason":"non_recoverable_transaction_error"}"#]];
528 expected_data.assert_eq(&error_object.data().unwrap().to_string());
529 }
530
531 #[test]
532 fn test_non_recoverable_transaction_error_with_transient_errors() {
533 let quorum_driver_error = QuorumDriverError::NonRecoverableTransactionError {
534 errors: vec![
535 (
536 IotaError::UserInput {
537 error: UserInputError::ObjectNotFound {
538 object_id: test_object_ref().object_id,
539 version: None,
540 },
541 },
542 0,
543 vec![],
544 ),
545 (
546 IotaError::Rpc("Hello".to_string(), "Testing".to_string()),
547 0,
548 vec![],
549 ),
550 ],
551 };
552
553 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
554
555 let error_object = error_object_from_rpc(rpc_error);
556 let expected_code = expect!["-32002"];
557 expected_code.assert_eq(&error_object.code().to_string());
558 let expected_message = expect![
559 "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"
560 ];
561 expected_message.assert_eq(error_object.message());
562 }
563
564 #[test]
565 fn test_quorum_driver_internal_error() {
566 let quorum_driver_error =
567 QuorumDriverError::QuorumDriverInternal(IotaError::UnexpectedMessage);
568
569 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
570
571 let error_object = error_object_from_rpc(rpc_error);
572 let expected_code = expect!["-32603"];
573 expected_code.assert_eq(&error_object.code().to_string());
574 let expected_message = expect!["Internal error occurred while executing transaction."];
575 expected_message.assert_eq(error_object.message());
576 }
577
578 #[test]
579 fn test_system_overload() {
580 let quorum_driver_error = QuorumDriverError::SystemOverload {
581 overloaded_stake: 10,
582 errors: vec![(IotaError::UnexpectedMessage, 0, vec![])],
583 };
584
585 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
586
587 let error_object = error_object_from_rpc(rpc_error);
588 let expected_code = expect!["-32050"];
589 expected_code.assert_eq(&error_object.code().to_string());
590 let expected_message = expect![
591 "Transaction is not processed because 10 of validators by stake are overloaded with certificates pending execution."
592 ];
593 expected_message.assert_eq(error_object.message());
594 let expected_data = expect![[r#"{"reason":"system_overload"}"#]];
595 expected_data.assert_eq(&error_object.data().unwrap().to_string());
596 }
597
598 #[test]
599 fn test_system_overload_retry_after() {
600 let quorum_driver_error = QuorumDriverError::SystemOverloadRetryAfter {
601 overload_stake: 4000,
602 errors: vec![(IotaError::UnexpectedMessage, 0, vec![])],
603 retry_after_secs: 30,
604 };
605
606 let rpc_error: RpcError = Error::QuorumDriver(quorum_driver_error).into();
607
608 let error_object = error_object_from_rpc(rpc_error);
609 let expected_code = expect!["-32050"];
610 expected_code.assert_eq(&error_object.code().to_string());
611 let expected_message = expect![
612 "Transaction is not processed because 4000 of validators are overloaded and asked client to retry after 30."
613 ];
614 expected_message.assert_eq(error_object.message());
615 let expected_data =
616 expect![[r#"{"reason":"system_overload_retry_after","retry_after_secs":30}"#]];
617 expected_data.assert_eq(&error_object.data().unwrap().to_string());
618 }
619 }
620}