1use sui_types::error::ErrorCategory;
5use tonic::Code;
6
7use crate::proto::google::rpc::{BadRequest, ErrorInfo, RetryInfo};
8pub use sui_rpc::proto::sui::rpc::v2::ErrorReason;
9
10pub type Result<T, E = RpcError> = std::result::Result<T, E>;
11
12#[derive(Debug)]
18pub struct RpcError {
19 code: Code,
20 message: Option<String>,
21 details: Option<Box<ErrorDetails>>,
22}
23
24impl RpcError {
25 pub fn new<T: Into<String>>(code: Code, message: T) -> Self {
26 Self {
27 code,
28 message: Some(message.into()),
29 details: None,
30 }
31 }
32
33 pub fn not_found() -> Self {
34 Self {
35 code: Code::NotFound,
36 message: None,
37 details: None,
38 }
39 }
40
41 pub fn into_status_proto(self) -> crate::proto::google::rpc::Status {
42 crate::proto::google::rpc::Status {
43 code: self.code.into(),
44 message: self.message.unwrap_or_default(),
45 details: self
46 .details
47 .map(ErrorDetails::into_status_details)
48 .unwrap_or_default(),
49 }
50 }
51}
52
53impl std::fmt::Display for RpcError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match &self.message {
56 Some(m) => write!(f, "{}: {}", self.code, m),
57 None => write!(f, "{}", self.code),
58 }
59 }
60}
61
62impl std::error::Error for RpcError {}
63
64impl From<RpcError> for tonic::Status {
65 fn from(value: RpcError) -> Self {
66 use prost::Message;
67
68 let code = value.code;
69 let status = value.into_status_proto();
70 let details = status.encode_to_vec().into();
71 let message = status.message;
72
73 tonic::Status::with_details(code, message, details)
74 }
75}
76
77impl From<sui_types::storage::error::Error> for RpcError {
78 fn from(value: sui_types::storage::error::Error) -> Self {
79 use sui_types::storage::error::Kind;
80
81 let code = match value.kind() {
82 Kind::Missing => Code::NotFound,
83 _ => Code::Internal,
84 };
85
86 Self {
87 code,
88 message: Some(value.to_string()),
89 details: None,
90 }
91 }
92}
93
94impl From<anyhow::Error> for RpcError {
95 fn from(value: anyhow::Error) -> Self {
96 Self {
97 code: Code::Internal,
98 message: Some(value.to_string()),
99 details: None,
100 }
101 }
102}
103
104impl From<sui_types::sui_sdk_types_conversions::SdkTypeConversionError> for RpcError {
105 fn from(value: sui_types::sui_sdk_types_conversions::SdkTypeConversionError) -> Self {
106 Self {
107 code: Code::Internal,
108 message: Some(value.to_string()),
109 details: None,
110 }
111 }
112}
113
114impl From<bcs::Error> for RpcError {
115 fn from(value: bcs::Error) -> Self {
116 Self {
117 code: Code::Internal,
118 message: Some(value.to_string()),
119 details: None,
120 }
121 }
122}
123
124impl From<sui_types::transaction_driver_types::TransactionSubmissionError> for RpcError {
125 fn from(error: sui_types::transaction_driver_types::TransactionSubmissionError) -> Self {
126 use itertools::Itertools;
127 use sui_types::error::SuiErrorKind;
128 use sui_types::transaction_driver_types::TransactionSubmissionError::*;
129
130 match error {
131 InvalidUserSignature(err) => {
132 let message = {
133 let err = match err.as_inner() {
134 SuiErrorKind::UserInputError { error } => error.to_string(),
135 _ => err.to_string(),
136 };
137 format!("Invalid user signature: {err}")
138 };
139
140 RpcError::new(Code::InvalidArgument, message)
141 }
142 TransactionDriverInternalError(err) => RpcError::new(Code::Internal, err.to_string()),
143 ObjectsDoubleUsed { conflicting_txes } => {
144 let new_map = conflicting_txes
145 .into_iter()
146 .map(|(digest, (pairs, _))| {
147 (
148 digest,
149 pairs.into_iter().map(|(_, obj_ref)| obj_ref).collect(),
150 )
151 })
152 .collect::<std::collections::BTreeMap<_, Vec<_>>>();
153
154 let message = format!(
155 "Failed to sign transaction by a quorum of validators because of locked objects. Conflicting Transactions:\n{new_map:#?}",
156 );
157
158 RpcError::new(Code::FailedPrecondition, message)
159 }
160 TimeoutBeforeFinality | FailedWithTransientErrorAfterMaximumAttempts { .. } => {
161 RpcError::new(
163 Code::Unavailable,
164 "timed-out before finality could be reached",
165 )
166 }
167 TimeoutBeforeFinalityWithErrors {
168 last_error,
169 attempts,
170 timeout,
171 } => {
172 RpcError::new(
174 Code::Unavailable,
175 format!(
176 "Transaction timed out before finality could be reached. Attempts: {attempts} & timeout: {timeout:?}. Last error: {last_error}"
177 ),
178 )
179 }
180 NonRecoverableTransactionError { errors } => {
181 let new_errors: Vec<String> = errors
182 .into_iter()
183 .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
185 .filter_map(|(err, _, _)| {
186 match err.as_inner() {
187 SuiErrorKind::UserInputError { error } => Some(error.to_string()),
198 _ => {
199 if err.is_retryable().0 {
200 None
201 } else {
202 Some(err.to_string())
203 }
204 }
205 }
206 })
207 .collect();
208
209 assert!(
210 !new_errors.is_empty(),
211 "NonRecoverableTransactionError should have at least one non-retryable error"
212 );
213
214 let error_list = new_errors.join(", ");
215 let error_msg = format!(
216 "Transaction execution failed due to issues with transaction inputs, please review the errors and try again: {}.",
217 error_list
218 );
219
220 RpcError::new(Code::InvalidArgument, error_msg)
221 }
222 TxAlreadyFinalizedWithDifferentUserSignatures => RpcError::new(
223 Code::Aborted,
224 "The transaction is already finalized but with different user signatures",
225 ),
226 SystemOverload { .. } | SystemOverloadRetryAfter { .. } => {
227 RpcError::new(Code::Unavailable, "system is overloaded")
229 }
230 TransactionFailed { category, details } => RpcError::new(
231 match category {
233 ErrorCategory::Internal => Code::Internal,
234 ErrorCategory::Aborted => Code::Aborted,
235 ErrorCategory::InvalidTransaction => Code::InvalidArgument,
236 ErrorCategory::LockConflict => Code::FailedPrecondition,
237 ErrorCategory::ValidatorOverloaded => Code::ResourceExhausted,
238 ErrorCategory::Unavailable => Code::Unavailable,
239 },
240 details,
241 ),
242 }
243 }
244}
245
246impl From<crate::proto::google::rpc::bad_request::FieldViolation> for RpcError {
247 fn from(value: crate::proto::google::rpc::bad_request::FieldViolation) -> Self {
248 BadRequest::from(value).into()
249 }
250}
251
252impl From<BadRequest> for RpcError {
253 fn from(value: BadRequest) -> Self {
254 let message = value
255 .field_violations
256 .first()
257 .map(|violation| violation.description.clone());
258 let details = ErrorDetails::new().with_bad_request(value);
259
260 RpcError {
261 code: Code::InvalidArgument,
262 message,
263 details: Some(Box::new(details)),
264 }
265 }
266}
267
268#[derive(Clone, Debug, Default)]
269pub struct ErrorDetails {
270 error_info: Option<ErrorInfo>,
271 bad_request: Option<BadRequest>,
272 retry_info: Option<RetryInfo>,
273}
274
275impl ErrorDetails {
276 pub fn new() -> Self {
277 Self::default()
278 }
279
280 pub fn error_info(&self) -> Option<&ErrorInfo> {
281 self.error_info.as_ref()
282 }
283
284 pub fn bad_request(&self) -> Option<&BadRequest> {
285 self.bad_request.as_ref()
286 }
287
288 pub fn retry_info(&self) -> Option<&RetryInfo> {
289 self.retry_info.as_ref()
290 }
291
292 pub fn details(&self) -> &[prost_types::Any] {
293 &[]
294 }
295
296 pub fn with_bad_request(mut self, bad_request: BadRequest) -> Self {
297 self.bad_request = Some(bad_request);
298 self
299 }
300
301 #[allow(clippy::boxed_local)]
302 fn into_status_details(self: Box<Self>) -> Vec<prost_types::Any> {
303 let mut details = Vec::new();
304
305 if let Some(error_info) = &self.error_info {
306 details.push(
307 prost_types::Any::from_msg(error_info).expect("Message encoding cannot fail"),
308 );
309 }
310
311 if let Some(bad_request) = &self.bad_request {
312 details.push(
313 prost_types::Any::from_msg(bad_request).expect("Message encoding cannot fail"),
314 );
315 }
316
317 if let Some(retry_info) = &self.retry_info {
318 details.push(
319 prost_types::Any::from_msg(retry_info).expect("Message encoding cannot fail"),
320 );
321 }
322 details
323 }
324}
325
326#[derive(Debug)]
327pub struct ObjectNotFoundError {
328 object_id: sui_sdk_types::Address,
329 version: Option<sui_sdk_types::Version>,
330}
331
332impl ObjectNotFoundError {
333 pub fn new(object_id: sui_sdk_types::Address) -> Self {
334 Self {
335 object_id,
336 version: None,
337 }
338 }
339
340 pub fn new_with_version(
341 object_id: sui_sdk_types::Address,
342 version: sui_sdk_types::Version,
343 ) -> Self {
344 Self {
345 object_id,
346 version: Some(version),
347 }
348 }
349}
350
351impl std::fmt::Display for ObjectNotFoundError {
352 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353 write!(f, "Object {}", self.object_id)?;
354
355 if let Some(version) = self.version {
356 write!(f, " with version {version}")?;
357 }
358
359 write!(f, " not found")
360 }
361}
362
363impl std::error::Error for ObjectNotFoundError {}
364
365impl From<ObjectNotFoundError> for crate::RpcError {
366 fn from(value: ObjectNotFoundError) -> Self {
367 Self::new(tonic::Code::NotFound, value.to_string())
368 }
369}
370
371#[derive(Debug)]
372pub struct CheckpointNotFoundError {
373 sequence_number: Option<u64>,
374 digest: Option<sui_sdk_types::Digest>,
375}
376
377impl CheckpointNotFoundError {
378 pub fn sequence_number(sequence_number: u64) -> Self {
379 Self {
380 sequence_number: Some(sequence_number),
381 digest: None,
382 }
383 }
384
385 pub fn digest(digest: sui_sdk_types::Digest) -> Self {
386 Self {
387 sequence_number: None,
388 digest: Some(digest),
389 }
390 }
391}
392
393impl std::fmt::Display for CheckpointNotFoundError {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 write!(f, "Checkpoint ")?;
396
397 if let Some(s) = self.sequence_number {
398 write!(f, "{s} ")?;
399 }
400
401 if let Some(d) = &self.digest {
402 write!(f, "{d} ")?;
403 }
404
405 write!(f, "not found")
406 }
407}
408
409impl std::error::Error for CheckpointNotFoundError {}
410
411impl From<CheckpointNotFoundError> for crate::RpcError {
412 fn from(value: CheckpointNotFoundError) -> Self {
413 Self::new(tonic::Code::NotFound, value.to_string())
414 }
415}