Skip to main content

sui_types/
error.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use crate::{
6    base_types::*,
7    committee::{Committee, EpochId, StakeUnit},
8    digests::CheckpointContentsDigest,
9    execution_status::{CommandArgumentError, CommandIndex, ExecutionErrorKind, ExecutionFailure},
10    messages_checkpoint::CheckpointSequenceNumber,
11    object::Owner,
12};
13
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use std::{collections::BTreeMap, fmt::Debug, slice::SliceIndex};
17use strum_macros::{AsRefStr, IntoStaticStr};
18use thiserror::Error;
19use tonic::Status;
20use typed_store_error::TypedStoreError;
21
22pub const TRANSACTION_NOT_FOUND_MSG_PREFIX: &str = "Could not find the referenced transaction";
23pub const TRANSACTIONS_NOT_FOUND_MSG_PREFIX: &str = "Could not find the referenced transactions";
24
25#[macro_export]
26macro_rules! fp_bail {
27    ($e:expr) => {
28        return Err($e)
29    };
30}
31
32#[macro_export(local_inner_macros)]
33macro_rules! fp_ensure {
34    ($cond:expr, $e:expr) => {
35        if !($cond) {
36            fp_bail!($e);
37        }
38    };
39}
40
41#[macro_export]
42macro_rules! exit_main {
43    ($result:expr) => {
44        match $result {
45            Ok(_) => (),
46            Err(err) => {
47                let err = format!("{:?}", err);
48                println!("{}", err.bold().red());
49                std::process::exit(1);
50            }
51        }
52    };
53}
54
55#[macro_export]
56macro_rules! make_invariant_violation {
57    ($($args:expr),* $(,)?) => {{
58        if cfg!(debug_assertions) {
59            panic!($($args),*)
60        }
61        $crate::error::ExecutionError::invariant_violation(format!($($args),*))
62    }}
63}
64
65#[macro_export]
66macro_rules! invariant_violation {
67    ($($args:expr),* $(,)?) => {
68        return Err(make_invariant_violation!($($args),*).into())
69    };
70}
71
72#[macro_export]
73macro_rules! assert_invariant {
74    ($cond:expr, $($args:expr),* $(,)?) => {{
75        if !$cond {
76            invariant_violation!($($args),*)
77        }
78    }};
79}
80
81/// A helper macro for performing a checked cast from one type to another, returning a
82/// ExecutionError invariant violation if the cast fails.
83#[macro_export]
84macro_rules! checked_as {
85    ($value:expr, $target_type:ty) => {{
86        let v = $value;
87        <$target_type>::try_from(v).map_err(|e| {
88            $crate::make_invariant_violation!(
89                "Value {} cannot be safely cast to {}: {:?}",
90                v,
91                stringify!($target_type),
92                e
93            )
94        })
95    }};
96}
97
98/// A trait for safe indexing into collections that returns a ExecutionError as long as the
99/// collection implements `AsRef<[T]>`.
100/// This is useful for avoiding panics on out-of-bounds access, and instead returning a proper
101/// error.
102pub trait SafeIndex<T> {
103    /// Get a reference to the element at the given `index`, or return invariant violation error
104    /// if the index is out of bounds.
105    fn safe_get<'a, I>(&'a self, index: I) -> Result<&'a I::Output, ExecutionError>
106    where
107        I: SliceIndex<[T]>,
108        T: 'a;
109
110    /// Get a mutable reference to the element at the given `index`, or return invariant violation
111    /// error if the index is out of bounds.
112    fn safe_get_mut<'a, I>(&'a mut self, index: I) -> Result<&'a mut I::Output, ExecutionError>
113    where
114        I: SliceIndex<[T]>,
115        T: 'a;
116}
117
118impl<T, C> SafeIndex<T> for C
119where
120    C: AsRef<[T]> + AsMut<[T]>,
121{
122    fn safe_get<'a, I>(&'a self, index: I) -> Result<&'a I::Output, ExecutionError>
123    where
124        I: SliceIndex<[T]>,
125        T: 'a,
126    {
127        let slice = self.as_ref();
128        let len = slice.len();
129        slice.get(index).ok_or_else(|| {
130            crate::make_invariant_violation!("Index out of bounds for collection of length {}", len)
131        })
132    }
133
134    fn safe_get_mut<'a, I>(&'a mut self, index: I) -> Result<&'a mut I::Output, ExecutionError>
135    where
136        I: SliceIndex<[T]>,
137        T: 'a,
138    {
139        let slice = self.as_mut();
140        let len = slice.len();
141        slice.get_mut(index).ok_or_else(|| {
142            crate::make_invariant_violation!("Index out of bounds for collection of length {}", len)
143        })
144    }
145}
146
147#[derive(
148    Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr, IntoStaticStr,
149)]
150pub enum UserInputError {
151    #[error("Mutable object {object_id} cannot appear more than one in one transaction")]
152    MutableObjectUsedMoreThanOnce { object_id: ObjectID },
153    #[error("Wrong number of parameters for the transaction")]
154    ObjectInputArityViolation,
155    #[error(
156        "Could not find the referenced object {} at version {:?}",
157        object_id,
158        version
159    )]
160    ObjectNotFound {
161        object_id: ObjectID,
162        version: Option<SequenceNumber>,
163    },
164    #[error(
165        "Transaction needs to be rebuilt because object {} version {} ({}) is unavailable for consumption, current version: {current_version}",
166        .provided_obj_ref.0, .provided_obj_ref.1, .provided_obj_ref.2
167    )]
168    ObjectVersionUnavailableForConsumption {
169        provided_obj_ref: ObjectRef,
170        current_version: SequenceNumber,
171    },
172    #[error("Package verification failed: {err}")]
173    PackageVerificationTimeout { err: String },
174    #[error("Dependent package not found on-chain: {package_id}")]
175    DependentPackageNotFound { package_id: ObjectID },
176    #[error("Mutable parameter provided, immutable parameter expected")]
177    ImmutableParameterExpectedError { object_id: ObjectID },
178    #[error("Size limit exceeded: {limit} is {value}")]
179    SizeLimitExceeded { limit: String, value: String },
180    #[error(
181        "Object {child_id} is owned by object {parent_id}. \
182        Objects owned by other objects cannot be used as input arguments"
183    )]
184    InvalidChildObjectArgument {
185        child_id: ObjectID,
186        parent_id: ObjectID,
187    },
188    #[error("Invalid Object digest for object {object_id}. Expected digest : {expected_digest}")]
189    InvalidObjectDigest {
190        object_id: ObjectID,
191        expected_digest: ObjectDigest,
192    },
193    #[error("Sequence numbers above the maximal value are not usable for transfers")]
194    InvalidSequenceNumber,
195    #[error("A move object is expected, instead a move package is passed: {object_id}")]
196    MovePackageAsObject { object_id: ObjectID },
197    #[error("A move package is expected, instead a move object is passed: {object_id}")]
198    MoveObjectAsPackage { object_id: ObjectID },
199    #[error("Transaction was not signed by the correct sender: {}", error)]
200    IncorrectUserSignature { error: String },
201
202    #[error("Object used as shared is not shared")]
203    NotSharedObjectError,
204    #[error("The transaction inputs contain duplicated ObjectRef's")]
205    DuplicateObjectRefInput,
206
207    // Gas related errors
208    #[error("Transaction gas payment missing")]
209    MissingGasPayment,
210    #[error("Gas object is not an owned object with owner: {:?}", owner)]
211    GasObjectNotOwnedObject { owner: Owner },
212    #[error("Gas budget: {gas_budget} is higher than max: {max_budget}")]
213    GasBudgetTooHigh { gas_budget: u64, max_budget: u64 },
214    #[error("Gas budget: {gas_budget} is lower than min: {min_budget}")]
215    GasBudgetTooLow { gas_budget: u64, min_budget: u64 },
216    #[error(
217        "Balance of gas object {gas_balance} is lower than the needed amount: {needed_gas_amount}"
218    )]
219    GasBalanceTooLow {
220        gas_balance: u128,
221        needed_gas_amount: u128,
222    },
223    #[error("Transaction kind does not support Sponsored Transaction")]
224    UnsupportedSponsoredTransactionKind,
225    #[error("Gas price {gas_price} under reference gas price (RGP) {reference_gas_price}")]
226    GasPriceUnderRGP {
227        gas_price: u64,
228        reference_gas_price: u64,
229    },
230    #[error("Gas price cannot exceed {max_gas_price} mist")]
231    GasPriceTooHigh { max_gas_price: u64 },
232    #[error("Object {object_id} is not a gas object")]
233    InvalidGasObject { object_id: ObjectID },
234    #[error("Gas object does not have enough balance to cover minimal gas spend")]
235    InsufficientBalanceToCoverMinimalGas,
236
237    #[error(
238        "Could not find the referenced object {object_id} as the asked version {asked_version:?} is higher than the latest {latest_version:?}"
239    )]
240    ObjectSequenceNumberTooHigh {
241        object_id: ObjectID,
242        asked_version: SequenceNumber,
243        latest_version: SequenceNumber,
244    },
245    #[error("Object deleted at reference ({}, {:?}, {})", object_ref.0, object_ref.1, object_ref.2)]
246    ObjectDeleted { object_ref: ObjectRef },
247    #[error("Invalid Batch Transaction: {error}")]
248    InvalidBatchTransaction { error: String },
249    #[error("This Move function is currently disabled and not available for call")]
250    BlockedMoveFunction,
251    #[error("Empty input coins for Pay related transaction")]
252    EmptyInputCoins,
253
254    #[error(
255        "SUI payment transactions use first input coin for gas payment, but found a different gas object"
256    )]
257    UnexpectedGasPaymentObject,
258
259    #[error("Wrong initial version given for shared object")]
260    SharedObjectStartingVersionMismatch,
261
262    #[error(
263        "Attempt to transfer object {object_id} that does not have public transfer. Object transfer must be done instead using a distinct Move function call"
264    )]
265    TransferObjectWithoutPublicTransferError { object_id: ObjectID },
266
267    #[error(
268        "TransferObjects, MergeCoin, and Publish cannot have empty arguments. \
269        If MakeMoveVec has empty arguments, it must have a type specified"
270    )]
271    EmptyCommandInput,
272
273    #[error("Transaction is denied: {error}")]
274    TransactionDenied { error: String },
275
276    #[error("Feature is not supported: {0}")]
277    Unsupported(String),
278
279    #[error("Query transactions with move function input error: {0}")]
280    MoveFunctionInputError(String),
281
282    #[error("Verified checkpoint not found for sequence number: {0}")]
283    VerifiedCheckpointNotFound(CheckpointSequenceNumber),
284
285    #[error("Verified checkpoint not found for digest: {0}")]
286    VerifiedCheckpointDigestNotFound(String),
287
288    #[error("Latest checkpoint sequence number not found")]
289    LatestCheckpointSequenceNumberNotFound,
290
291    #[error("Checkpoint contents not found for digest: {0}")]
292    CheckpointContentsNotFound(CheckpointContentsDigest),
293
294    #[error("Genesis transaction not found")]
295    GenesisTransactionNotFound,
296
297    #[error("Transaction {0} not found")]
298    TransactionCursorNotFound(u64),
299
300    #[error(
301        "Object {} is a system object and cannot be accessed by user transactions",
302        object_id
303    )]
304    InaccessibleSystemObject { object_id: ObjectID },
305    #[error(
306        "{max_publish_commands} max publish/upgrade commands allowed, {publish_count} provided"
307    )]
308    MaxPublishCountExceeded {
309        max_publish_commands: u64,
310        publish_count: u64,
311    },
312
313    #[error("Immutable parameter provided, mutable parameter expected")]
314    MutableParameterExpected { object_id: ObjectID },
315
316    #[error("Address {address} is denied for coin {coin_type}")]
317    AddressDeniedForCoin {
318        address: SuiAddress,
319        coin_type: String,
320    },
321
322    #[error("Commands following a command with Random can only be TransferObjects or MergeCoins")]
323    PostRandomCommandRestrictions,
324
325    #[error(
326        "Invalid argument at command {command_idx}, argument {argument_idx}: index {index} is out of bounds"
327    )]
328    InvalidArgumentIndex {
329        command_idx: usize,
330        argument_idx: usize,
331        index: u16,
332    },
333
334    // Soft Bundle related errors
335    #[error("Number of transactions ({size}) exceeds the maximum allowed ({limit}) in a batch")]
336    TooManyTransactionsInBatch { size: usize, limit: u64 },
337    #[error(
338        "Total transactions size ({size}) bytes exceeds the maximum allowed ({limit}) bytes in a Soft Bundle"
339    )]
340    TotalTransactionSizeTooLargeInBatch { size: usize, limit: u64 },
341    #[error("Transaction {digest} in Soft Bundle contains no shared objects")]
342    NoSharedObjectError { digest: TransactionDigest },
343    #[error("Transaction {digest} in Soft Bundle has already been executed")]
344    AlreadyExecutedInSoftBundleError { digest: TransactionDigest },
345    #[error("At least one certificate in Soft Bundle has already been processed")]
346    CertificateAlreadyProcessed,
347    #[error("Transaction {digest} was already executed")]
348    TransactionAlreadyExecuted { digest: TransactionDigest },
349    #[error(
350        "Gas price for transaction {digest} in Soft Bundle mismatch: want {expected}, have {actual}"
351    )]
352    GasPriceMismatchError {
353        digest: TransactionDigest,
354        expected: u64,
355        actual: u64,
356    },
357
358    #[error("Coin type is globally paused for use: {coin_type}")]
359    CoinTypeGlobalPause { coin_type: String },
360
361    #[error("Invalid identifier found in the transaction: {error}")]
362    InvalidIdentifier { error: String },
363
364    #[error("Object used as owned is not owned")]
365    NotOwnedObjectError,
366
367    #[error("Invalid withdraw reservation: {error}")]
368    InvalidWithdrawReservation { error: String },
369
370    #[error("Transaction with empty gas payment must specify an expiration.")]
371    MissingTransactionExpiration,
372
373    #[error("Invalid transaction expiration: {error}")]
374    InvalidExpiration { error: String },
375
376    #[error("Transaction chain ID {provided} does not match network chain ID {expected}.")]
377    InvalidChainId { provided: String, expected: String },
378
379    #[error("Transaction {digest} appears more than once in the request")]
380    RepeatedTransactions { digest: TransactionDigest },
381
382    #[error("Validator {proposer} is not an allowed proposer of this transaction")]
383    ProposerNotAllowed { proposer: u32 },
384}
385
386#[derive(
387    Eq,
388    PartialEq,
389    Clone,
390    Debug,
391    Serialize,
392    Deserialize,
393    Hash,
394    AsRefStr,
395    IntoStaticStr,
396    JsonSchema,
397    Error,
398)]
399#[serde(tag = "code", rename = "ObjectResponseError", rename_all = "camelCase")]
400pub enum SuiObjectResponseError {
401    #[error("Object {object_id} does not exist")]
402    NotExists { object_id: ObjectID },
403    #[error("Cannot find dynamic field for parent object {parent_object_id}")]
404    DynamicFieldNotFound { parent_object_id: ObjectID },
405    #[error(
406        "Object has been deleted object_id: {object_id} at version: {version:?} in digest {digest}"
407    )]
408    Deleted {
409        object_id: ObjectID,
410        /// Object version.
411        version: SequenceNumber,
412        /// Base64 string representing the object digest
413        digest: ObjectDigest,
414    },
415    #[error("Unknown Error")]
416    Unknown,
417    #[error("Display Error: {error}")]
418    DisplayError { error: String },
419    // TODO: also integrate SuiPastObjectResponse (VersionNotFound,  VersionTooHigh)
420}
421
422/// Custom error type for Sui.
423#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Error, Hash)]
424#[error(transparent)]
425pub struct SuiError(#[from] pub Box<SuiErrorKind>);
426
427/// Custom error type for Sui.
428#[derive(
429    Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr, IntoStaticStr,
430)]
431pub enum SuiErrorKind {
432    #[error("Error checking transaction input objects: {error}")]
433    UserInputError { error: UserInputError },
434
435    #[error("Error checking transaction object: {error}")]
436    SuiObjectResponseError { error: SuiObjectResponseError },
437
438    #[error("Expecting a single owner, shared ownership found")]
439    UnexpectedOwnerType,
440
441    #[error("There are already {queue_len} transactions pending, above threshold of {threshold}")]
442    TooManyTransactionsPendingExecution { queue_len: usize, threshold: usize },
443
444    #[error("There are too many transactions pending in consensus")]
445    TooManyTransactionsPendingConsensus,
446
447    #[error(
448        "Input {object_id} already has {queue_len} transactions pending, above threshold of {threshold}"
449    )]
450    TooManyTransactionsPendingOnObject {
451        object_id: ObjectID,
452        queue_len: usize,
453        threshold: usize,
454    },
455
456    #[error(
457        "Input {object_id} has a transaction {txn_age_sec} seconds old pending, above threshold of {threshold} seconds"
458    )]
459    TooOldTransactionPendingOnObject {
460        object_id: ObjectID,
461        txn_age_sec: u64,
462        threshold: u64,
463    },
464
465    #[error("Soft bundle must only contain transactions of UserTransaction kind")]
466    InvalidTxKindInSoftBundle,
467
468    // Signature verification
469    #[error("Signature is not valid: {}", error)]
470    InvalidSignature { error: String },
471    #[error("Required Signature from {expected} is absent {:?}", actual)]
472    SignerSignatureAbsent {
473        expected: String,
474        actual: Vec<String>,
475    },
476    #[error("Expect {expected} signer signatures but got {actual}")]
477    SignerSignatureNumberMismatch { expected: usize, actual: usize },
478    #[error("Value was not signed by the correct sender: {}", error)]
479    IncorrectSigner { error: String },
480    #[error(
481        "Value was not signed by a known authority. signer: {:?}, index: {:?}, committee: {committee}",
482        signer,
483        index
484    )]
485    UnknownSigner {
486        signer: Option<String>,
487        index: Option<u32>,
488        committee: Box<Committee>,
489    },
490    #[error(
491        "Validator {:?} responded multiple signatures for the same message, conflicting: {:?}",
492        signer,
493        conflicting_sig
494    )]
495    StakeAggregatorRepeatedSigner {
496        signer: AuthorityName,
497        conflicting_sig: bool,
498    },
499    // TODO: Used for distinguishing between different occurrences of invalid signatures, to allow retries in some cases.
500    #[error(
501        "Signature is not valid, but a retry may result in a valid one: {}",
502        error
503    )]
504    PotentiallyTemporarilyInvalidSignature { error: String },
505
506    // Certificate verification and execution
507    #[error(
508        "Signature or certificate from wrong epoch, expected {expected_epoch}, got {actual_epoch}"
509    )]
510    WrongEpoch {
511        expected_epoch: EpochId,
512        actual_epoch: EpochId,
513    },
514    #[error("Signatures in a certificate must form a quorum")]
515    CertificateRequiresQuorum,
516    #[allow(non_camel_case_types)]
517    #[error("DEPRECATED")]
518    DEPRECATED_ErrorWhileProcessingCertificate,
519    #[error(
520        "Failed to get a quorum of signed effects when processing transaction: {effects_map:?}"
521    )]
522    QuorumFailedToGetEffectsQuorumWhenProcessingTransaction {
523        effects_map: BTreeMap<TransactionEffectsDigest, (Vec<AuthorityName>, StakeUnit)>,
524    },
525    #[error(
526        "Failed to verify Tx certificate with executed effects, error: {error:?}, validator: {validator_name:?}"
527    )]
528    FailedToVerifyTxCertWithExecutedEffects {
529        validator_name: AuthorityName,
530        error: String,
531    },
532    #[error("Transaction is already finalized but with different user signatures")]
533    TxAlreadyFinalizedWithDifferentUserSigs,
534
535    // Account access
536    #[error("Invalid authenticator")]
537    InvalidAuthenticator,
538    #[error("Invalid address")]
539    InvalidAddress,
540    #[error("Invalid transaction digest.")]
541    InvalidTransactionDigest,
542
543    #[error("Invalid digest length. Expected {expected}, got {actual}")]
544    InvalidDigestLength { expected: usize, actual: usize },
545    #[error("Invalid DKG message size")]
546    InvalidDkgMessageSize,
547
548    #[error("Unexpected message: {0}")]
549    UnexpectedMessage(String),
550
551    // Move module publishing related errors
552    #[error("Failed to verify the Move module, reason: {error}.")]
553    ModuleVerificationFailure { error: String },
554    #[error("Failed to deserialize the Move module, reason: {error}.")]
555    ModuleDeserializationFailure { error: String },
556    #[error("Failed to publish the Move module(s), reason: {error}")]
557    ModulePublishFailure { error: String },
558    #[error("Failed to build Move modules: {error}.")]
559    ModuleBuildFailure { error: String },
560
561    // Move call related errors
562    #[error("Function resolution failure: {error}.")]
563    FunctionNotFound { error: String },
564    #[error("Module not found in package: {module_name}.")]
565    ModuleNotFound { module_name: String },
566    #[error("Type error while binding function arguments: {error}.")]
567    TypeError { error: String },
568    #[error("Circular object ownership detected")]
569    CircularObjectOwnership,
570
571    // Internal state errors
572    #[error("Attempt to re-initialize a transaction lock for objects {:?}.", refs)]
573    ObjectLockAlreadyInitialized { refs: Vec<ObjectRef> },
574    #[error(
575        "Object {obj_ref:?} already locked by a different transaction: {pending_transaction:?}"
576    )]
577    ObjectLockConflict {
578        obj_ref: ObjectRef,
579        pending_transaction: TransactionDigest,
580    },
581    #[error(
582        "Objects {obj_refs:?} are already locked by a transaction from a future epoch {locked_epoch:?}), attempt to override with a transaction from epoch {new_epoch:?}"
583    )]
584    ObjectLockedAtFutureEpoch {
585        obj_refs: Vec<ObjectRef>,
586        locked_epoch: EpochId,
587        new_epoch: EpochId,
588        locked_by_tx: TransactionDigest,
589    },
590    #[error("{TRANSACTION_NOT_FOUND_MSG_PREFIX} [{:?}].", digest)]
591    TransactionNotFound { digest: TransactionDigest },
592    #[error("{TRANSACTIONS_NOT_FOUND_MSG_PREFIX} [{:?}].", digests)]
593    TransactionsNotFound { digests: Vec<TransactionDigest> },
594    #[error("Could not find the referenced transaction events [{digest:?}].")]
595    TransactionEventsNotFound { digest: TransactionDigest },
596    #[error("Could not find the referenced transaction effects [{digest:?}].")]
597    TransactionEffectsNotFound { digest: TransactionDigest },
598    #[error(
599        "Attempt to move to `Executed` state an transaction that has already been executed: {:?}.",
600        digest
601    )]
602    TransactionAlreadyExecuted { digest: TransactionDigest },
603    #[error("Transaction reject reason not found for transaction {digest:?}")]
604    TransactionRejectReasonNotFound { digest: TransactionDigest },
605    #[error("Object ID did not have the expected type")]
606    BadObjectType { error: String },
607    #[error("Fail to retrieve Object layout for {st}")]
608    FailObjectLayout { st: String },
609
610    #[error("Execution invariant violated")]
611    ExecutionInvariantViolation,
612    #[error("Validator {authority:?} is faulty in a Byzantine manner: {reason:?}")]
613    ByzantineAuthoritySuspicion {
614        authority: AuthorityName,
615        reason: String,
616    },
617    #[allow(non_camel_case_types)]
618    #[serde(rename = "StorageError")]
619    #[error("DEPRECATED")]
620    DEPRECATED_StorageError,
621    #[allow(non_camel_case_types)]
622    #[serde(rename = "GenericStorageError")]
623    #[error("DEPRECATED")]
624    DEPRECATED_GenericStorageError,
625    #[error(
626        "Attempted to access {object} through parent {given_parent}, \
627        but it's actual parent is {actual_owner}"
628    )]
629    InvalidChildObjectAccess {
630        object: ObjectID,
631        given_parent: ObjectID,
632        actual_owner: Owner,
633    },
634
635    #[allow(non_camel_case_types)]
636    #[serde(rename = "StorageMissingFieldError")]
637    #[error("DEPRECATED")]
638    DEPRECATED_StorageMissingFieldError,
639    #[allow(non_camel_case_types)]
640    #[serde(rename = "StorageCorruptedFieldError")]
641    #[error("DEPRECATED")]
642    DEPRECATED_StorageCorruptedFieldError,
643
644    #[error("Authority Error: {error}")]
645    GenericAuthorityError { error: String },
646
647    #[error("Generic Bridge Error: {error}")]
648    GenericBridgeError { error: String },
649
650    #[error("Failed to dispatch subscription: {error}")]
651    FailedToDispatchSubscription { error: String },
652
653    #[error("Failed to serialize Owner: {error}")]
654    OwnerFailedToSerialize { error: String },
655
656    #[error("Failed to deserialize fields into JSON: {error}")]
657    ExtraFieldFailedToDeserialize { error: String },
658
659    #[error("Failed to execute transaction locally by Orchestrator: {error}")]
660    TransactionOrchestratorLocalExecutionError { error: String },
661
662    // Errors returned by authority and client read API's
663    #[error("Failure serializing transaction in the requested format: {error}")]
664    TransactionSerializationError { error: String },
665    #[error("Failure deserializing transaction from the provided format: {error}")]
666    TransactionDeserializationError { error: String },
667    #[error("Failure serializing transaction effects from the provided format: {error}")]
668    TransactionEffectsSerializationError { error: String },
669    #[error("Failure deserializing transaction effects from the provided format: {error}")]
670    TransactionEffectsDeserializationError { error: String },
671    #[error("Failure serializing transaction events from the provided format: {error}")]
672    TransactionEventsSerializationError { error: String },
673    #[error("Failure deserializing transaction events from the provided format: {error}")]
674    TransactionEventsDeserializationError { error: String },
675    #[error("Failure serializing object in the requested format: {error}")]
676    ObjectSerializationError { error: String },
677    #[error("Failure deserializing object in the requested format: {error}")]
678    ObjectDeserializationError { error: String },
679    #[error("Event store component is not active on this node")]
680    NoEventStore,
681
682    // Client side error
683    #[error("Too many authority errors were detected for {}: {:?}", action, errors)]
684    TooManyIncorrectAuthorities {
685        errors: Vec<(AuthorityName, SuiError)>,
686        action: String,
687    },
688    #[error("Invalid transaction range query to the fullnode: {error}")]
689    FullNodeInvalidTxRangeQuery { error: String },
690
691    // Errors related to the authority-consensus interface.
692    #[error("Failed to submit transaction to consensus: {0}")]
693    FailedToSubmitToConsensus(String),
694    #[error("Failed to connect with consensus node: {0}")]
695    ConsensusConnectionBroken(String),
696    #[error("Failed to execute handle_consensus_transaction on Sui: {0}")]
697    HandleConsensusTransactionFailure(String),
698
699    // Cryptography errors.
700    #[error("Signature key generation error: {0}")]
701    SignatureKeyGenError(String),
702    #[error("Key Conversion Error: {0}")]
703    KeyConversionError(String),
704    #[error("Invalid Private Key provided")]
705    InvalidPrivateKey,
706
707    // Unsupported Operations on Fullnode
708    #[error("Fullnode does not support handle_certificate")]
709    FullNodeCantHandleCertificate,
710
711    // Epoch related errors.
712    #[error("Validator temporarily stopped processing transactions due to epoch change")]
713    ValidatorHaltedAtEpochEnd,
714    #[error("Operations for epoch {0} have ended")]
715    EpochEnded(EpochId),
716    #[error("Error when advancing epoch: {error}")]
717    AdvanceEpochError { error: String },
718
719    #[error("Transaction Expired")]
720    TransactionExpired,
721
722    // These are errors that occur when an RPC fails and is simply the utf8 message sent in a
723    // Tonic::Status
724    #[error("{1} - {0}")]
725    RpcError(String, String),
726
727    #[error("Method not allowed")]
728    InvalidRpcMethodError,
729
730    #[error("Use of disabled feature: {error}")]
731    UnsupportedFeatureError { error: String },
732
733    #[error("Unable to communicate with the Quorum Driver channel: {error}")]
734    QuorumDriverCommunicationError { error: String },
735
736    #[error("Operation timed out")]
737    TimeoutError,
738
739    #[error("Error executing {0}")]
740    ExecutionError(String),
741
742    #[error("Invalid committee composition")]
743    InvalidCommittee(String),
744
745    #[error("Missing committee information for epoch {0}")]
746    MissingCommitteeAtEpoch(EpochId),
747
748    #[error("Failed to read dynamic field from table in the object store: {0}")]
749    DynamicFieldReadError(String),
750
751    #[error("Failed to read or deserialize system state related data structures on-chain: {0}")]
752    SuiSystemStateReadError(String),
753
754    #[error("Failed to read or deserialize bridge related data structures on-chain: {0}")]
755    SuiBridgeReadError(String),
756
757    #[error("Unexpected version error: {0}")]
758    UnexpectedVersion(String),
759
760    #[error("Message version is not supported at the current protocol version: {error}")]
761    WrongMessageVersion { error: String },
762
763    #[error("unknown error: {0}")]
764    Unknown(String),
765
766    #[error("Failed to perform file operation: {0}")]
767    FileIOError(String),
768
769    #[error("Failed to get JWK")]
770    JWKRetrievalError,
771
772    #[error("Storage error: {0}")]
773    Storage(String),
774
775    #[error(
776        "Validator cannot handle the request at the moment. Please retry after at least {retry_after_secs} seconds."
777    )]
778    ValidatorOverloadedRetryAfter { retry_after_secs: u64 },
779
780    #[error("Too many requests")]
781    TooManyRequests,
782
783    #[error("The request did not contain a certificate")]
784    NoCertificateProvidedError,
785
786    #[error("Nitro attestation verify failed: {0}")]
787    NitroAttestationFailedToVerify(String),
788
789    #[error("Failed to serialize {type_info}, error: {error}")]
790    GrpcMessageSerializeError { type_info: String, error: String },
791
792    #[error("Failed to deserialize {type_info}, error: {error}")]
793    GrpcMessageDeserializeError { type_info: String, error: String },
794
795    #[error(
796        "Validator consensus rounds are lagging behind. last committed leader round: {last_committed_round}, requested round: {round}"
797    )]
798    ValidatorConsensusLagging {
799        round: u32,
800        last_committed_round: u32,
801    },
802
803    #[error("Invalid admin request: {0}")]
804    InvalidAdminRequest(String),
805
806    #[error("Invalid request: {0}")]
807    InvalidRequest(String),
808
809    #[error(
810        "The current set of aliases for a required signer changed after the transaction was submitted"
811    )]
812    AliasesChanged,
813
814    // Retriable by client because another validator can create the correct claim.
815    #[error("Object {object_id} not found among input objects.")]
816    ImmutableObjectClaimNotFoundInInput { object_id: ObjectID },
817
818    // Retriable by client because another validator can create the correct claim.
819    #[error("Immutable object {object_id} was not included in immutable claims.")]
820    ImmutableObjectNotClaimed { object_id: ObjectID },
821
822    // Retriable by client because the object can be frozen in the future.
823    #[error(
824        "Claimed object {claimed_object_id} is not immutable. Found object ref: {found_object_ref:?}"
825    )]
826    InvalidImmutableObjectClaim {
827        claimed_object_id: ObjectID,
828        found_object_ref: ObjectRef,
829    },
830
831    #[error(
832        "Transaction was outbid by higher-gas-price transactions in the admission queue (current minimum gas price required: {min_gas_price})"
833    )]
834    TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: u64 },
835
836    #[error("Transaction {digest} is being processed post-consensus: {status}")]
837    TransactionProcessing {
838        digest: TransactionDigest,
839        status: String,
840    },
841
842    #[error("Transaction {digest} has been recently submitted to this validator.")]
843    TransactionSubmitted { digest: TransactionDigest },
844}
845
846#[repr(u64)]
847#[allow(non_camel_case_types)]
848#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
849/// Sub-status codes for the `UNKNOWN_VERIFICATION_ERROR` VM Status Code which provides more context
850/// TODO: add more Vm Status errors. We use `UNKNOWN_VERIFICATION_ERROR` as a catchall for now.
851pub enum VMMVerifierErrorSubStatusCode {
852    MULTIPLE_RETURN_VALUES_NOT_ALLOWED = 0,
853    INVALID_OBJECT_CREATION = 1,
854}
855
856#[repr(u64)]
857#[allow(non_camel_case_types)]
858#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
859/// Sub-status codes for the `MEMORY_LIMIT_EXCEEDED` VM Status Code which provides more context
860pub enum VMMemoryLimitExceededSubStatusCode {
861    EVENT_COUNT_LIMIT_EXCEEDED = 0,
862    EVENT_SIZE_LIMIT_EXCEEDED = 1,
863    NEW_ID_COUNT_LIMIT_EXCEEDED = 2,
864    DELETED_ID_COUNT_LIMIT_EXCEEDED = 3,
865    TRANSFER_ID_COUNT_LIMIT_EXCEEDED = 4,
866    OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED = 5,
867    OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED = 6,
868    TOTAL_EVENT_SIZE_LIMIT_EXCEEDED = 7,
869    SCRATCH_SIZE_LIMIT_EXCEEDED = 8,
870}
871
872pub type SuiResult<T = ()> = Result<T, SuiError>;
873pub type UserInputResult<T = ()> = Result<T, UserInputError>;
874
875impl From<SuiErrorKind> for SuiError {
876    fn from(error: SuiErrorKind) -> Self {
877        SuiError(Box::new(error))
878    }
879}
880
881impl std::ops::Deref for SuiError {
882    type Target = SuiErrorKind;
883
884    fn deref(&self) -> &Self::Target {
885        &self.0
886    }
887}
888
889impl From<sui_protocol_config::Error> for SuiError {
890    fn from(error: sui_protocol_config::Error) -> Self {
891        SuiErrorKind::WrongMessageVersion { error: error.0 }.into()
892    }
893}
894
895impl From<ExecutionError> for SuiError {
896    fn from(error: ExecutionError) -> Self {
897        SuiErrorKind::ExecutionError(error.to_string()).into()
898    }
899}
900
901impl From<Status> for SuiError {
902    fn from(status: Status) -> Self {
903        if status.message() == "Too many requests" {
904            return SuiErrorKind::TooManyRequests.into();
905        }
906
907        let result = bcs::from_bytes::<SuiError>(status.details());
908        if let Ok(sui_error) = result {
909            sui_error
910        } else {
911            SuiErrorKind::RpcError(
912                status.message().to_owned(),
913                status.code().description().to_owned(),
914            )
915            .into()
916        }
917    }
918}
919
920impl From<TypedStoreError> for SuiError {
921    fn from(e: TypedStoreError) -> Self {
922        SuiErrorKind::Storage(e.to_string()).into()
923    }
924}
925
926impl From<crate::storage::error::Error> for SuiError {
927    fn from(e: crate::storage::error::Error) -> Self {
928        SuiErrorKind::Storage(e.to_string()).into()
929    }
930}
931
932impl From<SuiErrorKind> for Status {
933    fn from(error: SuiErrorKind) -> Self {
934        let bytes = bcs::to_bytes(&error).unwrap();
935        Status::with_details(tonic::Code::Internal, error.to_string(), bytes.into())
936    }
937}
938
939impl From<SuiError> for Status {
940    fn from(error: SuiError) -> Self {
941        Status::from(error.into_inner())
942    }
943}
944
945impl From<ExecutionErrorKind> for SuiError {
946    fn from(kind: ExecutionErrorKind) -> Self {
947        ExecutionError::from_kind(kind).into()
948    }
949}
950
951impl From<&str> for SuiError {
952    fn from(error: &str) -> Self {
953        SuiErrorKind::GenericAuthorityError {
954            error: error.to_string(),
955        }
956        .into()
957    }
958}
959
960impl From<String> for SuiError {
961    fn from(error: String) -> Self {
962        SuiErrorKind::GenericAuthorityError { error }.into()
963    }
964}
965
966impl TryFrom<SuiErrorKind> for UserInputError {
967    type Error = anyhow::Error;
968
969    fn try_from(err: SuiErrorKind) -> Result<Self, Self::Error> {
970        match err {
971            SuiErrorKind::UserInputError { error } => Ok(error),
972            other => anyhow::bail!("error {:?} is not UserInputError", other),
973        }
974    }
975}
976
977impl TryFrom<SuiError> for UserInputError {
978    type Error = anyhow::Error;
979
980    fn try_from(err: SuiError) -> Result<Self, Self::Error> {
981        err.into_inner().try_into()
982    }
983}
984
985impl From<UserInputError> for SuiError {
986    fn from(error: UserInputError) -> Self {
987        SuiErrorKind::UserInputError { error }.into()
988    }
989}
990
991impl From<SuiObjectResponseError> for SuiError {
992    fn from(error: SuiObjectResponseError) -> Self {
993        SuiErrorKind::SuiObjectResponseError { error }.into()
994    }
995}
996
997impl PartialEq<SuiErrorKind> for SuiError {
998    fn eq(&self, other: &SuiErrorKind) -> bool {
999        &*self.0 == other
1000    }
1001}
1002
1003impl PartialEq<SuiError> for SuiErrorKind {
1004    fn eq(&self, other: &SuiError) -> bool {
1005        self == &*other.0
1006    }
1007}
1008
1009impl SuiError {
1010    pub fn as_inner(&self) -> &SuiErrorKind {
1011        &self.0
1012    }
1013
1014    pub fn into_inner(self) -> SuiErrorKind {
1015        *self.0
1016    }
1017}
1018
1019impl SuiErrorKind {
1020    /// Returns the variant name of the error. Sub-variants within UserInputError are unpacked too.
1021    pub fn to_variant_name(&self) -> &'static str {
1022        match &self {
1023            SuiErrorKind::UserInputError { error } => error.into(),
1024            _ => self.into(),
1025        }
1026    }
1027
1028    pub fn individual_error_indicates_epoch_change(&self) -> bool {
1029        matches!(
1030            self,
1031            SuiErrorKind::ValidatorHaltedAtEpochEnd | SuiErrorKind::MissingCommitteeAtEpoch(_)
1032        )
1033    }
1034
1035    /// Returns if the error is retryable and if the error's retryability is
1036    /// explicitly categorized.
1037    /// There should be only a handful of retryable errors. For now we list common
1038    /// non-retryable error below to help us find more retryable errors in logs.
1039    pub fn is_retryable(&self) -> (bool, bool) {
1040        let retryable = match self {
1041            // Network error
1042            SuiErrorKind::RpcError { .. } => true,
1043
1044            // Reconfig error
1045            SuiErrorKind::ValidatorHaltedAtEpochEnd => true,
1046            SuiErrorKind::MissingCommitteeAtEpoch(..) => true,
1047            SuiErrorKind::WrongEpoch { .. } => true,
1048            SuiErrorKind::EpochEnded(..) => true,
1049
1050            SuiErrorKind::UserInputError { error } => {
1051                match error {
1052                    // Only ObjectNotFound and DependentPackageNotFound is potentially retryable
1053                    UserInputError::ObjectNotFound { .. } => true,
1054                    UserInputError::DependentPackageNotFound { .. } => true,
1055                    _ => false,
1056                }
1057            }
1058
1059            SuiErrorKind::PotentiallyTemporarilyInvalidSignature { .. } => true,
1060
1061            // Overload errors
1062            SuiErrorKind::TooManyTransactionsPendingExecution { .. } => true,
1063            SuiErrorKind::TooManyTransactionsPendingOnObject { .. } => true,
1064            SuiErrorKind::TooOldTransactionPendingOnObject { .. } => true,
1065            SuiErrorKind::TooManyTransactionsPendingConsensus => true,
1066            SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. } => true,
1067            SuiErrorKind::ValidatorOverloadedRetryAfter { .. } => true,
1068
1069            // The transaction is already being processed by consensus, so a fresh
1070            // submission is pointless. The client should retry by waiting for effects
1071            // rather than resubmitting.
1072            SuiErrorKind::TransactionProcessing { .. } => true,
1073            SuiErrorKind::TransactionSubmitted { .. } => true,
1074
1075            // Non retryable error
1076            SuiErrorKind::ExecutionError(..) => false,
1077            SuiErrorKind::ByzantineAuthoritySuspicion { .. } => false,
1078            SuiErrorKind::QuorumFailedToGetEffectsQuorumWhenProcessingTransaction { .. } => false,
1079            SuiErrorKind::TxAlreadyFinalizedWithDifferentUserSigs => false,
1080            SuiErrorKind::FailedToVerifyTxCertWithExecutedEffects { .. } => false,
1081            SuiErrorKind::ObjectLockConflict { .. } => false,
1082
1083            // NB: This is not an internal overload, but instead an imposed rate
1084            // limit / blocking of a client. It must be non-retryable otherwise
1085            // we will make the threat worse through automatic retries.
1086            SuiErrorKind::TooManyRequests => false,
1087
1088            // For all un-categorized errors, return here with categorized = false.
1089            _ => return (false, false),
1090        };
1091
1092        (retryable, true)
1093    }
1094
1095    pub fn is_object_or_package_not_found(&self) -> bool {
1096        match self {
1097            SuiErrorKind::UserInputError { error } => {
1098                matches!(
1099                    error,
1100                    UserInputError::ObjectNotFound { .. }
1101                        | UserInputError::DependentPackageNotFound { .. }
1102                )
1103            }
1104            _ => false,
1105        }
1106    }
1107
1108    pub fn is_overload(&self) -> bool {
1109        matches!(
1110            self,
1111            SuiErrorKind::TooManyTransactionsPendingExecution { .. }
1112                | SuiErrorKind::TooManyTransactionsPendingOnObject { .. }
1113                | SuiErrorKind::TooOldTransactionPendingOnObject { .. }
1114                | SuiErrorKind::TooManyTransactionsPendingConsensus
1115                | SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
1116        )
1117    }
1118
1119    pub fn is_retryable_overload(&self) -> bool {
1120        matches!(self, SuiErrorKind::ValidatorOverloadedRetryAfter { .. })
1121    }
1122
1123    pub fn retry_after_secs(&self) -> u64 {
1124        match self {
1125            SuiErrorKind::ValidatorOverloadedRetryAfter { retry_after_secs } => *retry_after_secs,
1126            _ => 0,
1127        }
1128    }
1129
1130    /// Categorizes SuiError into ErrorCategory.
1131    pub fn categorize(&self) -> ErrorCategory {
1132        match self {
1133            SuiErrorKind::UserInputError { error } => {
1134                match error {
1135                    // ObjectNotFound and DependentPackageNotFound are potentially valid because the missing
1136                    // input can be created by other transactions.
1137                    UserInputError::ObjectNotFound { .. } => ErrorCategory::Aborted,
1138                    UserInputError::DependentPackageNotFound { .. } => ErrorCategory::Aborted,
1139                    // Other UserInputError variants indeed indicate invalid transaction.
1140                    _ => ErrorCategory::InvalidTransaction,
1141                }
1142            }
1143
1144            SuiErrorKind::InvalidSignature { .. }
1145            | SuiErrorKind::SignerSignatureAbsent { .. }
1146            | SuiErrorKind::SignerSignatureNumberMismatch { .. }
1147            | SuiErrorKind::IncorrectSigner { .. }
1148            | SuiErrorKind::UnknownSigner { .. }
1149            | SuiErrorKind::TransactionExpired => ErrorCategory::InvalidTransaction,
1150
1151            SuiErrorKind::ObjectLockConflict { .. } => ErrorCategory::LockConflict,
1152
1153            SuiErrorKind::Unknown { .. }
1154            | SuiErrorKind::GrpcMessageSerializeError { .. }
1155            | SuiErrorKind::GrpcMessageDeserializeError { .. }
1156            | SuiErrorKind::ByzantineAuthoritySuspicion { .. }
1157            | SuiErrorKind::InvalidTxKindInSoftBundle
1158            | SuiErrorKind::UnsupportedFeatureError { .. }
1159            | SuiErrorKind::InvalidRequest { .. } => ErrorCategory::Internal,
1160
1161            SuiErrorKind::TooManyTransactionsPendingExecution { .. }
1162            | SuiErrorKind::TooManyTransactionsPendingOnObject { .. }
1163            | SuiErrorKind::TooOldTransactionPendingOnObject { .. }
1164            | SuiErrorKind::TooManyTransactionsPendingConsensus
1165            | SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
1166            | SuiErrorKind::ValidatorOverloadedRetryAfter { .. } => {
1167                ErrorCategory::ValidatorOverloaded
1168            }
1169
1170            SuiErrorKind::TimeoutError => ErrorCategory::Unavailable,
1171
1172            // Other variants are assumed to be retriable with new transaction submissions.
1173            _ => ErrorCategory::Aborted,
1174        }
1175    }
1176}
1177
1178impl Ord for SuiError {
1179    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1180        Ord::cmp(self.as_ref(), other.as_ref())
1181    }
1182}
1183
1184impl PartialOrd for SuiError {
1185    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1186        Some(self.cmp(other))
1187    }
1188}
1189
1190impl std::fmt::Debug for SuiError {
1191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1192        self.as_inner().fmt(f)
1193    }
1194}
1195
1196pub(crate) type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
1197pub type ExecutionErrorMetadata = BTreeMap<String, String>;
1198
1199/// A trait for execution errors that provides common methods for accessing error information and creating new errors.
1200pub trait ExecutionErrorTrait:
1201    From<ExecutionError> + Debug + std::error::Error + Send + Sync + Sized + 'static
1202{
1203    fn new(
1204        failure: ExecutionFailure,
1205        source: Option<BoxError>,
1206        metadata: ExecutionErrorMetadata,
1207    ) -> Self;
1208
1209    fn from_execution_failure(failure: ExecutionFailure) -> Self {
1210        Self::new(failure, None, ExecutionErrorMetadata::default())
1211    }
1212
1213    fn from_kind(kind: ExecutionErrorKind) -> Self {
1214        Self::from_execution_failure(ExecutionFailure::new(kind, None))
1215    }
1216
1217    fn new_with_source<E>(kind: ExecutionErrorKind, source: E) -> Self
1218    where
1219        E: Into<BoxError>,
1220    {
1221        Self::new(
1222            ExecutionFailure::new(kind, None),
1223            Some(source.into()),
1224            ExecutionErrorMetadata::default(),
1225        )
1226    }
1227
1228    fn with_command_index(self, command: CommandIndex) -> Self;
1229    fn kind(&self) -> &ExecutionErrorKind;
1230    fn command(&self) -> Option<CommandIndex>;
1231
1232    fn to_execution_failure(&self) -> ExecutionFailure {
1233        ExecutionFailure::new(self.kind().clone(), self.command())
1234    }
1235}
1236
1237#[derive(Debug)]
1238pub struct ExecutionError {
1239    inner: Box<ExecutionErrorInner>,
1240}
1241
1242#[derive(Debug)]
1243struct ExecutionErrorInner {
1244    kind: ExecutionErrorKind,
1245    source: Option<BoxError>,
1246    command: Option<CommandIndex>,
1247}
1248
1249impl ExecutionError {
1250    pub fn new(kind: ExecutionErrorKind, source: Option<BoxError>) -> Self {
1251        Self {
1252            inner: Box::new(ExecutionErrorInner {
1253                kind,
1254                source,
1255                command: None,
1256            }),
1257        }
1258    }
1259
1260    pub fn new_with_source<E: Into<BoxError>>(kind: ExecutionErrorKind, source: E) -> Self {
1261        Self::new(kind, Some(source.into()))
1262    }
1263
1264    pub fn invariant_violation<E: Into<BoxError>>(source: E) -> Self {
1265        Self::new_with_source(ExecutionErrorKind::InvariantViolation, source)
1266    }
1267
1268    pub fn with_command_index(mut self, command: CommandIndex) -> Self {
1269        self.inner.command = Some(command);
1270        self
1271    }
1272
1273    pub fn from_kind(kind: ExecutionErrorKind) -> Self {
1274        Self::new(kind, None)
1275    }
1276
1277    pub fn kind(&self) -> &ExecutionErrorKind {
1278        &self.inner.kind
1279    }
1280
1281    pub fn command(&self) -> Option<CommandIndex> {
1282        self.inner.command
1283    }
1284
1285    pub fn source(&self) -> &Option<BoxError> {
1286        &self.inner.source
1287    }
1288
1289    pub fn to_execution_status(&self) -> (ExecutionErrorKind, Option<CommandIndex>) {
1290        (self.kind().clone(), self.command())
1291    }
1292}
1293
1294impl ExecutionErrorTrait for ExecutionError {
1295    fn new(
1296        failure: ExecutionFailure,
1297        source: Option<BoxError>,
1298        _metadata: ExecutionErrorMetadata,
1299    ) -> Self {
1300        let ExecutionFailure { error, command } = failure;
1301        let err = ExecutionError::new(error, source);
1302        if let Some(command) = command {
1303            err.with_command_index(command)
1304        } else {
1305            err
1306        }
1307    }
1308
1309    fn with_command_index(self, command: CommandIndex) -> Self {
1310        self.with_command_index(command)
1311    }
1312
1313    fn kind(&self) -> &ExecutionErrorKind {
1314        self.kind()
1315    }
1316
1317    fn command(&self) -> Option<CommandIndex> {
1318        self.command()
1319    }
1320}
1321
1322#[derive(Debug)]
1323pub struct ExecutionErrorContext {
1324    kind: ExecutionErrorKind,
1325    metadata: ExecutionErrorMetadata,
1326    source: Option<BoxError>,
1327    command: Option<CommandIndex>,
1328}
1329
1330impl ExecutionErrorContext {
1331    pub fn kind(&self) -> &ExecutionErrorKind {
1332        &self.kind
1333    }
1334
1335    pub fn command(&self) -> Option<CommandIndex> {
1336        self.command
1337    }
1338
1339    pub fn metadata_with_source(&self) -> Option<ExecutionErrorMetadata> {
1340        let mut metadata = self.metadata.clone();
1341        if let Some(source) = self.source.as_ref() {
1342            metadata.insert("source".to_string(), source.to_string());
1343        }
1344
1345        (!metadata.is_empty()).then_some(metadata)
1346    }
1347
1348    pub fn to_execution_status(&self) -> (ExecutionErrorKind, Option<CommandIndex>) {
1349        (self.kind().clone(), self.command())
1350    }
1351}
1352
1353impl ExecutionErrorTrait for ExecutionErrorContext {
1354    fn new(
1355        failure: ExecutionFailure,
1356        source: Option<BoxError>,
1357        metadata: ExecutionErrorMetadata,
1358    ) -> Self {
1359        let ExecutionFailure { error, command } = failure;
1360        Self {
1361            kind: error,
1362            metadata,
1363            source,
1364            command,
1365        }
1366    }
1367
1368    fn with_command_index(self, command: CommandIndex) -> Self {
1369        Self {
1370            command: Some(command),
1371            ..self
1372        }
1373    }
1374
1375    fn kind(&self) -> &ExecutionErrorKind {
1376        self.kind()
1377    }
1378
1379    fn command(&self) -> Option<CommandIndex> {
1380        self.command()
1381    }
1382}
1383
1384impl std::fmt::Display for ExecutionErrorContext {
1385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1386        write!(f, "ExecutionErrorContext: {:?}", self)
1387    }
1388}
1389
1390impl std::error::Error for ExecutionErrorContext {
1391    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1392        self.source.as_deref().map(|e| e as _)
1393    }
1394}
1395
1396impl From<ExecutionErrorKind> for ExecutionErrorContext {
1397    fn from(kind: ExecutionErrorKind) -> Self {
1398        <Self as ExecutionErrorTrait>::from_kind(kind)
1399    }
1400}
1401
1402impl From<ExecutionFailure> for ExecutionErrorContext {
1403    fn from(value: ExecutionFailure) -> Self {
1404        <Self as ExecutionErrorTrait>::from_execution_failure(value)
1405    }
1406}
1407
1408impl From<ExecutionError> for ExecutionErrorContext {
1409    fn from(value: ExecutionError) -> Self {
1410        let ExecutionError { inner } = value;
1411        let ExecutionErrorInner {
1412            kind,
1413            source,
1414            command,
1415        } = *inner;
1416        Self {
1417            kind,
1418            metadata: BTreeMap::new(),
1419            source,
1420            command,
1421        }
1422    }
1423}
1424
1425impl From<ExecutionErrorContext> for ExecutionError {
1426    fn from(value: ExecutionErrorContext) -> Self {
1427        let ExecutionErrorContext {
1428            kind,
1429            metadata: _,
1430            source,
1431            command,
1432        } = value;
1433        let err = ExecutionError::new(kind, source);
1434        if let Some(command) = command {
1435            err.with_command_index(command)
1436        } else {
1437            err
1438        }
1439    }
1440}
1441
1442impl std::fmt::Display for ExecutionError {
1443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1444        write!(f, "ExecutionError: {:?}", self)
1445    }
1446}
1447
1448impl std::error::Error for ExecutionError {
1449    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1450        self.inner.source.as_ref().map(|e| &**e as _)
1451    }
1452}
1453
1454impl From<ExecutionErrorKind> for ExecutionError {
1455    fn from(kind: ExecutionErrorKind) -> Self {
1456        Self::from_kind(kind)
1457    }
1458}
1459
1460impl From<ExecutionFailure> for ExecutionError {
1461    fn from(value: ExecutionFailure) -> Self {
1462        <Self as ExecutionErrorTrait>::from_execution_failure(value)
1463    }
1464}
1465
1466pub fn command_argument_error(e: CommandArgumentError, arg_idx: usize) -> ExecutionError {
1467    ExecutionError::from_kind(ExecutionErrorKind::command_argument_error(
1468        e,
1469        arg_idx as u16,
1470    ))
1471}
1472
1473/// Types of SuiError.
1474#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, IntoStaticStr)]
1475pub enum ErrorCategory {
1476    // A generic error that is retriable with new transaction resubmissions.
1477    Aborted,
1478    // Any validator or full node can check if a transaction is valid.
1479    InvalidTransaction,
1480    // Lock conflict on the transaction input.
1481    LockConflict,
1482    // Unexpected client error, for example generating invalid request or entering into invalid state.
1483    // And unexpected error from the remote peer. The validator may be malicious or there is a software bug.
1484    Internal,
1485    // Validator is overloaded.
1486    ValidatorOverloaded,
1487    // Target validator is down or there are network issues.
1488    Unavailable,
1489}
1490
1491impl ErrorCategory {
1492    // Whether the failure is retriable with new transaction submission.
1493    pub fn is_submission_retriable(&self) -> bool {
1494        matches!(
1495            self,
1496            ErrorCategory::Aborted
1497                | ErrorCategory::ValidatorOverloaded
1498                | ErrorCategory::Unavailable
1499        )
1500    }
1501}