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