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