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    // Soft Bundle related errors
326    #[error("Number of transactions ({size}) exceeds the maximum allowed ({limit}) in a batch")]
327    TooManyTransactionsInBatch { size: usize, limit: u64 },
328    #[error(
329        "Total transactions size ({size}) bytes exceeds the maximum allowed ({limit}) bytes in a Soft Bundle"
330    )]
331    TotalTransactionSizeTooLargeInBatch { size: usize, limit: u64 },
332    #[error("Transaction {digest} in Soft Bundle contains no shared objects")]
333    NoSharedObjectError { digest: TransactionDigest },
334    #[error("Transaction {digest} in Soft Bundle has already been executed")]
335    AlreadyExecutedInSoftBundleError { digest: TransactionDigest },
336    #[error("At least one certificate in Soft Bundle has already been processed")]
337    CertificateAlreadyProcessed,
338    #[error("Transaction {digest} was already executed")]
339    TransactionAlreadyExecuted { digest: TransactionDigest },
340    #[error(
341        "Gas price for transaction {digest} in Soft Bundle mismatch: want {expected}, have {actual}"
342    )]
343    GasPriceMismatchError {
344        digest: TransactionDigest,
345        expected: u64,
346        actual: u64,
347    },
348
349    #[error("Coin type is globally paused for use: {coin_type}")]
350    CoinTypeGlobalPause { coin_type: String },
351
352    #[error("Invalid identifier found in the transaction: {error}")]
353    InvalidIdentifier { error: String },
354
355    #[error("Object used as owned is not owned")]
356    NotOwnedObjectError,
357
358    #[error("Invalid withdraw reservation: {error}")]
359    InvalidWithdrawReservation { error: String },
360
361    #[error("Transaction with empty gas payment must specify an expiration.")]
362    MissingTransactionExpiration,
363
364    #[error("Invalid transaction expiration: {error}")]
365    InvalidExpiration { error: String },
366
367    #[error("Transaction chain ID {provided} does not match network chain ID {expected}.")]
368    InvalidChainId { provided: String, expected: String },
369
370    #[error("Transaction {digest} appears more than once in the request")]
371    RepeatedTransactions { digest: TransactionDigest },
372
373    #[error("Validator {proposer} is not an allowed proposer of this transaction")]
374    ProposerNotAllowed { proposer: u32 },
375}
376
377#[derive(
378    Eq,
379    PartialEq,
380    Clone,
381    Debug,
382    Serialize,
383    Deserialize,
384    Hash,
385    AsRefStr,
386    IntoStaticStr,
387    JsonSchema,
388    Error,
389)]
390#[serde(tag = "code", rename = "ObjectResponseError", rename_all = "camelCase")]
391pub enum SuiObjectResponseError {
392    #[error("Object {object_id} does not exist")]
393    NotExists { object_id: ObjectID },
394    #[error("Cannot find dynamic field for parent object {parent_object_id}")]
395    DynamicFieldNotFound { parent_object_id: ObjectID },
396    #[error(
397        "Object has been deleted object_id: {object_id} at version: {version:?} in digest {digest}"
398    )]
399    Deleted {
400        object_id: ObjectID,
401        /// Object version.
402        version: SequenceNumber,
403        /// Base64 string representing the object digest
404        digest: ObjectDigest,
405    },
406    #[error("Unknown Error")]
407    Unknown,
408    #[error("Display Error: {error}")]
409    DisplayError { error: String },
410    // TODO: also integrate SuiPastObjectResponse (VersionNotFound,  VersionTooHigh)
411}
412
413/// Custom error type for Sui.
414#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Error, Hash)]
415#[error(transparent)]
416pub struct SuiError(#[from] pub Box<SuiErrorKind>);
417
418/// Custom error type for Sui.
419#[derive(
420    Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr, IntoStaticStr,
421)]
422pub enum SuiErrorKind {
423    #[error("Error checking transaction input objects: {error}")]
424    UserInputError { error: UserInputError },
425
426    #[error("Error checking transaction object: {error}")]
427    SuiObjectResponseError { error: SuiObjectResponseError },
428
429    #[error("Expecting a single owner, shared ownership found")]
430    UnexpectedOwnerType,
431
432    #[error("There are already {queue_len} transactions pending, above threshold of {threshold}")]
433    TooManyTransactionsPendingExecution { queue_len: usize, threshold: usize },
434
435    #[error("There are too many transactions pending in consensus")]
436    TooManyTransactionsPendingConsensus,
437
438    #[error(
439        "Input {object_id} already has {queue_len} transactions pending, above threshold of {threshold}"
440    )]
441    TooManyTransactionsPendingOnObject {
442        object_id: ObjectID,
443        queue_len: usize,
444        threshold: usize,
445    },
446
447    #[error(
448        "Input {object_id} has a transaction {txn_age_sec} seconds old pending, above threshold of {threshold} seconds"
449    )]
450    TooOldTransactionPendingOnObject {
451        object_id: ObjectID,
452        txn_age_sec: u64,
453        threshold: u64,
454    },
455
456    #[error("Soft bundle must only contain transactions of UserTransaction kind")]
457    InvalidTxKindInSoftBundle,
458
459    // Signature verification
460    #[error("Signature is not valid: {}", error)]
461    InvalidSignature { error: String },
462    #[error("Required Signature from {expected} is absent {:?}", actual)]
463    SignerSignatureAbsent {
464        expected: String,
465        actual: Vec<String>,
466    },
467    #[error("Expect {expected} signer signatures but got {actual}")]
468    SignerSignatureNumberMismatch { expected: usize, actual: usize },
469    #[error("Value was not signed by the correct sender: {}", error)]
470    IncorrectSigner { error: String },
471    #[error(
472        "Value was not signed by a known authority. signer: {:?}, index: {:?}, committee: {committee}",
473        signer,
474        index
475    )]
476    UnknownSigner {
477        signer: Option<String>,
478        index: Option<u32>,
479        committee: Box<Committee>,
480    },
481    #[error(
482        "Validator {:?} responded multiple signatures for the same message, conflicting: {:?}",
483        signer,
484        conflicting_sig
485    )]
486    StakeAggregatorRepeatedSigner {
487        signer: AuthorityName,
488        conflicting_sig: bool,
489    },
490    // TODO: Used for distinguishing between different occurrences of invalid signatures, to allow retries in some cases.
491    #[error(
492        "Signature is not valid, but a retry may result in a valid one: {}",
493        error
494    )]
495    PotentiallyTemporarilyInvalidSignature { error: String },
496
497    // Certificate verification and execution
498    #[error(
499        "Signature or certificate from wrong epoch, expected {expected_epoch}, got {actual_epoch}"
500    )]
501    WrongEpoch {
502        expected_epoch: EpochId,
503        actual_epoch: EpochId,
504    },
505    #[error("Signatures in a certificate must form a quorum")]
506    CertificateRequiresQuorum,
507    #[allow(non_camel_case_types)]
508    #[error("DEPRECATED")]
509    DEPRECATED_ErrorWhileProcessingCertificate,
510    #[error(
511        "Failed to get a quorum of signed effects when processing transaction: {effects_map:?}"
512    )]
513    QuorumFailedToGetEffectsQuorumWhenProcessingTransaction {
514        effects_map: BTreeMap<TransactionEffectsDigest, (Vec<AuthorityName>, StakeUnit)>,
515    },
516    #[error(
517        "Failed to verify Tx certificate with executed effects, error: {error:?}, validator: {validator_name:?}"
518    )]
519    FailedToVerifyTxCertWithExecutedEffects {
520        validator_name: AuthorityName,
521        error: String,
522    },
523    #[error("Transaction is already finalized but with different user signatures")]
524    TxAlreadyFinalizedWithDifferentUserSigs,
525
526    // Account access
527    #[error("Invalid authenticator")]
528    InvalidAuthenticator,
529    #[error("Invalid address")]
530    InvalidAddress,
531    #[error("Invalid transaction digest.")]
532    InvalidTransactionDigest,
533
534    #[error("Invalid digest length. Expected {expected}, got {actual}")]
535    InvalidDigestLength { expected: usize, actual: usize },
536    #[error("Invalid DKG message size")]
537    InvalidDkgMessageSize,
538
539    #[error("Unexpected message: {0}")]
540    UnexpectedMessage(String),
541
542    // Move module publishing related errors
543    #[error("Failed to verify the Move module, reason: {error}.")]
544    ModuleVerificationFailure { error: String },
545    #[error("Failed to deserialize the Move module, reason: {error}.")]
546    ModuleDeserializationFailure { error: String },
547    #[error("Failed to publish the Move module(s), reason: {error}")]
548    ModulePublishFailure { error: String },
549    #[error("Failed to build Move modules: {error}.")]
550    ModuleBuildFailure { error: String },
551
552    // Move call related errors
553    #[error("Function resolution failure: {error}.")]
554    FunctionNotFound { error: String },
555    #[error("Module not found in package: {module_name}.")]
556    ModuleNotFound { module_name: String },
557    #[error("Type error while binding function arguments: {error}.")]
558    TypeError { error: String },
559    #[error("Circular object ownership detected")]
560    CircularObjectOwnership,
561
562    // Internal state errors
563    #[error("Attempt to re-initialize a transaction lock for objects {:?}.", refs)]
564    ObjectLockAlreadyInitialized { refs: Vec<ObjectRef> },
565    #[error(
566        "Object {obj_ref:?} already locked by a different transaction: {pending_transaction:?}"
567    )]
568    ObjectLockConflict {
569        obj_ref: ObjectRef,
570        pending_transaction: TransactionDigest,
571    },
572    #[error(
573        "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:?}"
574    )]
575    ObjectLockedAtFutureEpoch {
576        obj_refs: Vec<ObjectRef>,
577        locked_epoch: EpochId,
578        new_epoch: EpochId,
579        locked_by_tx: TransactionDigest,
580    },
581    #[error("{TRANSACTION_NOT_FOUND_MSG_PREFIX} [{:?}].", digest)]
582    TransactionNotFound { digest: TransactionDigest },
583    #[error("{TRANSACTIONS_NOT_FOUND_MSG_PREFIX} [{:?}].", digests)]
584    TransactionsNotFound { digests: Vec<TransactionDigest> },
585    #[error("Could not find the referenced transaction events [{digest:?}].")]
586    TransactionEventsNotFound { digest: TransactionDigest },
587    #[error("Could not find the referenced transaction effects [{digest:?}].")]
588    TransactionEffectsNotFound { digest: TransactionDigest },
589    #[error(
590        "Attempt to move to `Executed` state an transaction that has already been executed: {:?}.",
591        digest
592    )]
593    TransactionAlreadyExecuted { digest: TransactionDigest },
594    #[error("Transaction reject reason not found for transaction {digest:?}")]
595    TransactionRejectReasonNotFound { digest: TransactionDigest },
596    #[error("Object ID did not have the expected type")]
597    BadObjectType { error: String },
598    #[error("Fail to retrieve Object layout for {st}")]
599    FailObjectLayout { st: String },
600
601    #[error("Execution invariant violated")]
602    ExecutionInvariantViolation,
603    #[error("Validator {authority:?} is faulty in a Byzantine manner: {reason:?}")]
604    ByzantineAuthoritySuspicion {
605        authority: AuthorityName,
606        reason: String,
607    },
608    #[allow(non_camel_case_types)]
609    #[serde(rename = "StorageError")]
610    #[error("DEPRECATED")]
611    DEPRECATED_StorageError,
612    #[allow(non_camel_case_types)]
613    #[serde(rename = "GenericStorageError")]
614    #[error("DEPRECATED")]
615    DEPRECATED_GenericStorageError,
616    #[error(
617        "Attempted to access {object} through parent {given_parent}, \
618        but it's actual parent is {actual_owner}"
619    )]
620    InvalidChildObjectAccess {
621        object: ObjectID,
622        given_parent: ObjectID,
623        actual_owner: Owner,
624    },
625
626    #[allow(non_camel_case_types)]
627    #[serde(rename = "StorageMissingFieldError")]
628    #[error("DEPRECATED")]
629    DEPRECATED_StorageMissingFieldError,
630    #[allow(non_camel_case_types)]
631    #[serde(rename = "StorageCorruptedFieldError")]
632    #[error("DEPRECATED")]
633    DEPRECATED_StorageCorruptedFieldError,
634
635    #[error("Authority Error: {error}")]
636    GenericAuthorityError { error: String },
637
638    #[error("Generic Bridge Error: {error}")]
639    GenericBridgeError { error: String },
640
641    #[error("Failed to dispatch subscription: {error}")]
642    FailedToDispatchSubscription { error: String },
643
644    #[error("Failed to serialize Owner: {error}")]
645    OwnerFailedToSerialize { error: String },
646
647    #[error("Failed to deserialize fields into JSON: {error}")]
648    ExtraFieldFailedToDeserialize { error: String },
649
650    #[error("Failed to execute transaction locally by Orchestrator: {error}")]
651    TransactionOrchestratorLocalExecutionError { error: String },
652
653    // Errors returned by authority and client read API's
654    #[error("Failure serializing transaction in the requested format: {error}")]
655    TransactionSerializationError { error: String },
656    #[error("Failure deserializing transaction from the provided format: {error}")]
657    TransactionDeserializationError { error: String },
658    #[error("Failure serializing transaction effects from the provided format: {error}")]
659    TransactionEffectsSerializationError { error: String },
660    #[error("Failure deserializing transaction effects from the provided format: {error}")]
661    TransactionEffectsDeserializationError { error: String },
662    #[error("Failure serializing transaction events from the provided format: {error}")]
663    TransactionEventsSerializationError { error: String },
664    #[error("Failure deserializing transaction events from the provided format: {error}")]
665    TransactionEventsDeserializationError { error: String },
666    #[error("Failure serializing object in the requested format: {error}")]
667    ObjectSerializationError { error: String },
668    #[error("Failure deserializing object in the requested format: {error}")]
669    ObjectDeserializationError { error: String },
670    #[error("Event store component is not active on this node")]
671    NoEventStore,
672
673    // Client side error
674    #[error("Too many authority errors were detected for {}: {:?}", action, errors)]
675    TooManyIncorrectAuthorities {
676        errors: Vec<(AuthorityName, SuiError)>,
677        action: String,
678    },
679    #[error("Invalid transaction range query to the fullnode: {error}")]
680    FullNodeInvalidTxRangeQuery { error: String },
681
682    // Errors related to the authority-consensus interface.
683    #[error("Failed to submit transaction to consensus: {0}")]
684    FailedToSubmitToConsensus(String),
685    #[error("Failed to connect with consensus node: {0}")]
686    ConsensusConnectionBroken(String),
687    #[error("Failed to execute handle_consensus_transaction on Sui: {0}")]
688    HandleConsensusTransactionFailure(String),
689
690    // Cryptography errors.
691    #[error("Signature key generation error: {0}")]
692    SignatureKeyGenError(String),
693    #[error("Key Conversion Error: {0}")]
694    KeyConversionError(String),
695    #[error("Invalid Private Key provided")]
696    InvalidPrivateKey,
697
698    // Unsupported Operations on Fullnode
699    #[error("Fullnode does not support handle_certificate")]
700    FullNodeCantHandleCertificate,
701
702    // Epoch related errors.
703    #[error("Validator temporarily stopped processing transactions due to epoch change")]
704    ValidatorHaltedAtEpochEnd,
705    #[error("Operations for epoch {0} have ended")]
706    EpochEnded(EpochId),
707    #[error("Error when advancing epoch: {error}")]
708    AdvanceEpochError { error: String },
709
710    #[error("Transaction Expired")]
711    TransactionExpired,
712
713    // These are errors that occur when an RPC fails and is simply the utf8 message sent in a
714    // Tonic::Status
715    #[error("{1} - {0}")]
716    RpcError(String, String),
717
718    #[error("Method not allowed")]
719    InvalidRpcMethodError,
720
721    #[error("Use of disabled feature: {error}")]
722    UnsupportedFeatureError { error: String },
723
724    #[error("Unable to communicate with the Quorum Driver channel: {error}")]
725    QuorumDriverCommunicationError { error: String },
726
727    #[error("Operation timed out")]
728    TimeoutError,
729
730    #[error("Error executing {0}")]
731    ExecutionError(String),
732
733    #[error("Invalid committee composition")]
734    InvalidCommittee(String),
735
736    #[error("Missing committee information for epoch {0}")]
737    MissingCommitteeAtEpoch(EpochId),
738
739    #[error("Index store not available on this Fullnode.")]
740    IndexStoreNotAvailable,
741
742    #[error("Failed to read dynamic field from table in the object store: {0}")]
743    DynamicFieldReadError(String),
744
745    #[error("Failed to read or deserialize system state related data structures on-chain: {0}")]
746    SuiSystemStateReadError(String),
747
748    #[error("Failed to read or deserialize bridge related data structures on-chain: {0}")]
749    SuiBridgeReadError(String),
750
751    #[error("Unexpected version error: {0}")]
752    UnexpectedVersion(String),
753
754    #[error("Message version is not supported at the current protocol version: {error}")]
755    WrongMessageVersion { error: String },
756
757    #[error("unknown error: {0}")]
758    Unknown(String),
759
760    #[error("Failed to perform file operation: {0}")]
761    FileIOError(String),
762
763    #[error("Failed to get JWK")]
764    JWKRetrievalError,
765
766    #[error("Storage error: {0}")]
767    Storage(String),
768
769    #[error(
770        "Validator cannot handle the request at the moment. Please retry after at least {retry_after_secs} seconds."
771    )]
772    ValidatorOverloadedRetryAfter { retry_after_secs: u64 },
773
774    #[error("Too many requests")]
775    TooManyRequests,
776
777    #[error("The request did not contain a certificate")]
778    NoCertificateProvidedError,
779
780    #[error("Nitro attestation verify failed: {0}")]
781    NitroAttestationFailedToVerify(String),
782
783    #[error("Failed to serialize {type_info}, error: {error}")]
784    GrpcMessageSerializeError { type_info: String, error: String },
785
786    #[error("Failed to deserialize {type_info}, error: {error}")]
787    GrpcMessageDeserializeError { type_info: String, error: String },
788
789    #[error(
790        "Validator consensus rounds are lagging behind. last committed leader round: {last_committed_round}, requested round: {round}"
791    )]
792    ValidatorConsensusLagging {
793        round: u32,
794        last_committed_round: u32,
795    },
796
797    #[error("Invalid admin request: {0}")]
798    InvalidAdminRequest(String),
799
800    #[error("Invalid request: {0}")]
801    InvalidRequest(String),
802
803    #[error(
804        "The current set of aliases for a required signer changed after the transaction was submitted"
805    )]
806    AliasesChanged,
807
808    // Retriable by client because another validator can create the correct claim.
809    #[error("Object {object_id} not found among input objects.")]
810    ImmutableObjectClaimNotFoundInInput { object_id: ObjectID },
811
812    // Retriable by client because another validator can create the correct claim.
813    #[error("Immutable object {object_id} was not included in immutable claims.")]
814    ImmutableObjectNotClaimed { object_id: ObjectID },
815
816    // Retriable by client because the object can be frozen in the future.
817    #[error(
818        "Claimed object {claimed_object_id} is not immutable. Found object ref: {found_object_ref:?}"
819    )]
820    InvalidImmutableObjectClaim {
821        claimed_object_id: ObjectID,
822        found_object_ref: ObjectRef,
823    },
824
825    #[error(
826        "Transaction was outbid by higher-gas-price transactions in the admission queue (current minimum gas price required: {min_gas_price})"
827    )]
828    TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: u64 },
829
830    #[error("Transaction {digest} is being processed post-consensus: {status}")]
831    TransactionProcessing {
832        digest: TransactionDigest,
833        status: String,
834    },
835
836    #[error("Transaction {digest} has been recently submitted to this validator.")]
837    TransactionSubmitted { digest: TransactionDigest },
838}
839
840#[repr(u64)]
841#[allow(non_camel_case_types)]
842#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
843/// Sub-status codes for the `UNKNOWN_VERIFICATION_ERROR` VM Status Code which provides more context
844/// TODO: add more Vm Status errors. We use `UNKNOWN_VERIFICATION_ERROR` as a catchall for now.
845pub enum VMMVerifierErrorSubStatusCode {
846    MULTIPLE_RETURN_VALUES_NOT_ALLOWED = 0,
847    INVALID_OBJECT_CREATION = 1,
848}
849
850#[repr(u64)]
851#[allow(non_camel_case_types)]
852#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
853/// Sub-status codes for the `MEMORY_LIMIT_EXCEEDED` VM Status Code which provides more context
854pub enum VMMemoryLimitExceededSubStatusCode {
855    EVENT_COUNT_LIMIT_EXCEEDED = 0,
856    EVENT_SIZE_LIMIT_EXCEEDED = 1,
857    NEW_ID_COUNT_LIMIT_EXCEEDED = 2,
858    DELETED_ID_COUNT_LIMIT_EXCEEDED = 3,
859    TRANSFER_ID_COUNT_LIMIT_EXCEEDED = 4,
860    OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED = 5,
861    OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED = 6,
862    TOTAL_EVENT_SIZE_LIMIT_EXCEEDED = 7,
863    SCRATCH_SIZE_LIMIT_EXCEEDED = 8,
864}
865
866pub type SuiResult<T = ()> = Result<T, SuiError>;
867pub type UserInputResult<T = ()> = Result<T, UserInputError>;
868
869impl From<SuiErrorKind> for SuiError {
870    fn from(error: SuiErrorKind) -> Self {
871        SuiError(Box::new(error))
872    }
873}
874
875impl std::ops::Deref for SuiError {
876    type Target = SuiErrorKind;
877
878    fn deref(&self) -> &Self::Target {
879        &self.0
880    }
881}
882
883impl From<sui_protocol_config::Error> for SuiError {
884    fn from(error: sui_protocol_config::Error) -> Self {
885        SuiErrorKind::WrongMessageVersion { error: error.0 }.into()
886    }
887}
888
889impl From<ExecutionError> for SuiError {
890    fn from(error: ExecutionError) -> Self {
891        SuiErrorKind::ExecutionError(error.to_string()).into()
892    }
893}
894
895impl From<Status> for SuiError {
896    fn from(status: Status) -> Self {
897        if status.message() == "Too many requests" {
898            return SuiErrorKind::TooManyRequests.into();
899        }
900
901        let result = bcs::from_bytes::<SuiError>(status.details());
902        if let Ok(sui_error) = result {
903            sui_error
904        } else {
905            SuiErrorKind::RpcError(
906                status.message().to_owned(),
907                status.code().description().to_owned(),
908            )
909            .into()
910        }
911    }
912}
913
914impl From<TypedStoreError> for SuiError {
915    fn from(e: TypedStoreError) -> Self {
916        SuiErrorKind::Storage(e.to_string()).into()
917    }
918}
919
920impl From<crate::storage::error::Error> for SuiError {
921    fn from(e: crate::storage::error::Error) -> Self {
922        SuiErrorKind::Storage(e.to_string()).into()
923    }
924}
925
926impl From<SuiErrorKind> for Status {
927    fn from(error: SuiErrorKind) -> Self {
928        let bytes = bcs::to_bytes(&error).unwrap();
929        Status::with_details(tonic::Code::Internal, error.to_string(), bytes.into())
930    }
931}
932
933impl From<SuiError> for Status {
934    fn from(error: SuiError) -> Self {
935        Status::from(error.into_inner())
936    }
937}
938
939impl From<ExecutionErrorKind> for SuiError {
940    fn from(kind: ExecutionErrorKind) -> Self {
941        ExecutionError::from_kind(kind).into()
942    }
943}
944
945impl From<&str> for SuiError {
946    fn from(error: &str) -> Self {
947        SuiErrorKind::GenericAuthorityError {
948            error: error.to_string(),
949        }
950        .into()
951    }
952}
953
954impl From<String> for SuiError {
955    fn from(error: String) -> Self {
956        SuiErrorKind::GenericAuthorityError { error }.into()
957    }
958}
959
960impl TryFrom<SuiErrorKind> for UserInputError {
961    type Error = anyhow::Error;
962
963    fn try_from(err: SuiErrorKind) -> Result<Self, Self::Error> {
964        match err {
965            SuiErrorKind::UserInputError { error } => Ok(error),
966            other => anyhow::bail!("error {:?} is not UserInputError", other),
967        }
968    }
969}
970
971impl TryFrom<SuiError> for UserInputError {
972    type Error = anyhow::Error;
973
974    fn try_from(err: SuiError) -> Result<Self, Self::Error> {
975        err.into_inner().try_into()
976    }
977}
978
979impl From<UserInputError> for SuiError {
980    fn from(error: UserInputError) -> Self {
981        SuiErrorKind::UserInputError { error }.into()
982    }
983}
984
985impl From<SuiObjectResponseError> for SuiError {
986    fn from(error: SuiObjectResponseError) -> Self {
987        SuiErrorKind::SuiObjectResponseError { error }.into()
988    }
989}
990
991impl PartialEq<SuiErrorKind> for SuiError {
992    fn eq(&self, other: &SuiErrorKind) -> bool {
993        &*self.0 == other
994    }
995}
996
997impl PartialEq<SuiError> for SuiErrorKind {
998    fn eq(&self, other: &SuiError) -> bool {
999        self == &*other.0
1000    }
1001}
1002
1003impl SuiError {
1004    pub fn as_inner(&self) -> &SuiErrorKind {
1005        &self.0
1006    }
1007
1008    pub fn into_inner(self) -> SuiErrorKind {
1009        *self.0
1010    }
1011}
1012
1013impl SuiErrorKind {
1014    /// Returns the variant name of the error. Sub-variants within UserInputError are unpacked too.
1015    pub fn to_variant_name(&self) -> &'static str {
1016        match &self {
1017            SuiErrorKind::UserInputError { error } => error.into(),
1018            _ => self.into(),
1019        }
1020    }
1021
1022    pub fn individual_error_indicates_epoch_change(&self) -> bool {
1023        matches!(
1024            self,
1025            SuiErrorKind::ValidatorHaltedAtEpochEnd | SuiErrorKind::MissingCommitteeAtEpoch(_)
1026        )
1027    }
1028
1029    /// Returns if the error is retryable and if the error's retryability is
1030    /// explicitly categorized.
1031    /// There should be only a handful of retryable errors. For now we list common
1032    /// non-retryable error below to help us find more retryable errors in logs.
1033    pub fn is_retryable(&self) -> (bool, bool) {
1034        let retryable = match self {
1035            // Network error
1036            SuiErrorKind::RpcError { .. } => true,
1037
1038            // Reconfig error
1039            SuiErrorKind::ValidatorHaltedAtEpochEnd => true,
1040            SuiErrorKind::MissingCommitteeAtEpoch(..) => true,
1041            SuiErrorKind::WrongEpoch { .. } => true,
1042            SuiErrorKind::EpochEnded(..) => true,
1043
1044            SuiErrorKind::UserInputError { error } => {
1045                match error {
1046                    // Only ObjectNotFound and DependentPackageNotFound is potentially retryable
1047                    UserInputError::ObjectNotFound { .. } => true,
1048                    UserInputError::DependentPackageNotFound { .. } => true,
1049                    _ => false,
1050                }
1051            }
1052
1053            SuiErrorKind::PotentiallyTemporarilyInvalidSignature { .. } => true,
1054
1055            // Overload errors
1056            SuiErrorKind::TooManyTransactionsPendingExecution { .. } => true,
1057            SuiErrorKind::TooManyTransactionsPendingOnObject { .. } => true,
1058            SuiErrorKind::TooOldTransactionPendingOnObject { .. } => true,
1059            SuiErrorKind::TooManyTransactionsPendingConsensus => true,
1060            SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. } => true,
1061            SuiErrorKind::ValidatorOverloadedRetryAfter { .. } => true,
1062
1063            // The transaction is already being processed by consensus, so a fresh
1064            // submission is pointless. The client should retry by waiting for effects
1065            // rather than resubmitting.
1066            SuiErrorKind::TransactionProcessing { .. } => true,
1067            SuiErrorKind::TransactionSubmitted { .. } => true,
1068
1069            // Non retryable error
1070            SuiErrorKind::ExecutionError(..) => false,
1071            SuiErrorKind::ByzantineAuthoritySuspicion { .. } => false,
1072            SuiErrorKind::QuorumFailedToGetEffectsQuorumWhenProcessingTransaction { .. } => false,
1073            SuiErrorKind::TxAlreadyFinalizedWithDifferentUserSigs => false,
1074            SuiErrorKind::FailedToVerifyTxCertWithExecutedEffects { .. } => false,
1075            SuiErrorKind::ObjectLockConflict { .. } => false,
1076
1077            // NB: This is not an internal overload, but instead an imposed rate
1078            // limit / blocking of a client. It must be non-retryable otherwise
1079            // we will make the threat worse through automatic retries.
1080            SuiErrorKind::TooManyRequests => false,
1081
1082            // For all un-categorized errors, return here with categorized = false.
1083            _ => return (false, false),
1084        };
1085
1086        (retryable, true)
1087    }
1088
1089    pub fn is_object_or_package_not_found(&self) -> bool {
1090        match self {
1091            SuiErrorKind::UserInputError { error } => {
1092                matches!(
1093                    error,
1094                    UserInputError::ObjectNotFound { .. }
1095                        | UserInputError::DependentPackageNotFound { .. }
1096                )
1097            }
1098            _ => false,
1099        }
1100    }
1101
1102    pub fn is_overload(&self) -> bool {
1103        matches!(
1104            self,
1105            SuiErrorKind::TooManyTransactionsPendingExecution { .. }
1106                | SuiErrorKind::TooManyTransactionsPendingOnObject { .. }
1107                | SuiErrorKind::TooOldTransactionPendingOnObject { .. }
1108                | SuiErrorKind::TooManyTransactionsPendingConsensus
1109                | SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
1110        )
1111    }
1112
1113    pub fn is_retryable_overload(&self) -> bool {
1114        matches!(self, SuiErrorKind::ValidatorOverloadedRetryAfter { .. })
1115    }
1116
1117    pub fn retry_after_secs(&self) -> u64 {
1118        match self {
1119            SuiErrorKind::ValidatorOverloadedRetryAfter { retry_after_secs } => *retry_after_secs,
1120            _ => 0,
1121        }
1122    }
1123
1124    /// Categorizes SuiError into ErrorCategory.
1125    pub fn categorize(&self) -> ErrorCategory {
1126        match self {
1127            SuiErrorKind::UserInputError { error } => {
1128                match error {
1129                    // ObjectNotFound and DependentPackageNotFound are potentially valid because the missing
1130                    // input can be created by other transactions.
1131                    UserInputError::ObjectNotFound { .. } => ErrorCategory::Aborted,
1132                    UserInputError::DependentPackageNotFound { .. } => ErrorCategory::Aborted,
1133                    // Other UserInputError variants indeed indicate invalid transaction.
1134                    _ => ErrorCategory::InvalidTransaction,
1135                }
1136            }
1137
1138            SuiErrorKind::InvalidSignature { .. }
1139            | SuiErrorKind::SignerSignatureAbsent { .. }
1140            | SuiErrorKind::SignerSignatureNumberMismatch { .. }
1141            | SuiErrorKind::IncorrectSigner { .. }
1142            | SuiErrorKind::UnknownSigner { .. }
1143            | SuiErrorKind::TransactionExpired => ErrorCategory::InvalidTransaction,
1144
1145            SuiErrorKind::ObjectLockConflict { .. } => ErrorCategory::LockConflict,
1146
1147            SuiErrorKind::Unknown { .. }
1148            | SuiErrorKind::GrpcMessageSerializeError { .. }
1149            | SuiErrorKind::GrpcMessageDeserializeError { .. }
1150            | SuiErrorKind::ByzantineAuthoritySuspicion { .. }
1151            | SuiErrorKind::InvalidTxKindInSoftBundle
1152            | SuiErrorKind::UnsupportedFeatureError { .. }
1153            | SuiErrorKind::InvalidRequest { .. } => ErrorCategory::Internal,
1154
1155            SuiErrorKind::TooManyTransactionsPendingExecution { .. }
1156            | SuiErrorKind::TooManyTransactionsPendingOnObject { .. }
1157            | SuiErrorKind::TooOldTransactionPendingOnObject { .. }
1158            | SuiErrorKind::TooManyTransactionsPendingConsensus
1159            | SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
1160            | SuiErrorKind::ValidatorOverloadedRetryAfter { .. } => {
1161                ErrorCategory::ValidatorOverloaded
1162            }
1163
1164            SuiErrorKind::TimeoutError => ErrorCategory::Unavailable,
1165
1166            // Other variants are assumed to be retriable with new transaction submissions.
1167            _ => ErrorCategory::Aborted,
1168        }
1169    }
1170}
1171
1172impl Ord for SuiError {
1173    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1174        Ord::cmp(self.as_ref(), other.as_ref())
1175    }
1176}
1177
1178impl PartialOrd for SuiError {
1179    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1180        Some(self.cmp(other))
1181    }
1182}
1183
1184impl std::fmt::Debug for SuiError {
1185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1186        self.as_inner().fmt(f)
1187    }
1188}
1189
1190pub(crate) type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
1191pub type ExecutionErrorMetadata = BTreeMap<String, String>;
1192
1193/// A trait for execution errors that provides common methods for accessing error information and creating new errors.
1194pub trait ExecutionErrorTrait:
1195    From<ExecutionError> + Debug + std::error::Error + Send + Sync + Sized + 'static
1196{
1197    fn new(
1198        failure: ExecutionFailure,
1199        source: Option<BoxError>,
1200        metadata: ExecutionErrorMetadata,
1201    ) -> Self;
1202
1203    fn from_execution_failure(failure: ExecutionFailure) -> Self {
1204        Self::new(failure, None, ExecutionErrorMetadata::default())
1205    }
1206
1207    fn from_kind(kind: ExecutionErrorKind) -> Self {
1208        Self::from_execution_failure(ExecutionFailure::new(kind, None))
1209    }
1210
1211    fn new_with_source<E>(kind: ExecutionErrorKind, source: E) -> Self
1212    where
1213        E: Into<BoxError>,
1214    {
1215        Self::new(
1216            ExecutionFailure::new(kind, None),
1217            Some(source.into()),
1218            ExecutionErrorMetadata::default(),
1219        )
1220    }
1221
1222    fn with_command_index(self, command: CommandIndex) -> Self;
1223    fn kind(&self) -> &ExecutionErrorKind;
1224    fn command(&self) -> Option<CommandIndex>;
1225
1226    fn to_execution_failure(&self) -> ExecutionFailure {
1227        ExecutionFailure::new(self.kind().clone(), self.command())
1228    }
1229}
1230
1231#[derive(Debug)]
1232pub struct ExecutionError {
1233    inner: Box<ExecutionErrorInner>,
1234}
1235
1236#[derive(Debug)]
1237struct ExecutionErrorInner {
1238    kind: ExecutionErrorKind,
1239    source: Option<BoxError>,
1240    command: Option<CommandIndex>,
1241}
1242
1243impl ExecutionError {
1244    pub fn new(kind: ExecutionErrorKind, source: Option<BoxError>) -> Self {
1245        Self {
1246            inner: Box::new(ExecutionErrorInner {
1247                kind,
1248                source,
1249                command: None,
1250            }),
1251        }
1252    }
1253
1254    pub fn new_with_source<E: Into<BoxError>>(kind: ExecutionErrorKind, source: E) -> Self {
1255        Self::new(kind, Some(source.into()))
1256    }
1257
1258    pub fn invariant_violation<E: Into<BoxError>>(source: E) -> Self {
1259        Self::new_with_source(ExecutionErrorKind::InvariantViolation, source)
1260    }
1261
1262    pub fn with_command_index(mut self, command: CommandIndex) -> Self {
1263        self.inner.command = Some(command);
1264        self
1265    }
1266
1267    pub fn from_kind(kind: ExecutionErrorKind) -> Self {
1268        Self::new(kind, None)
1269    }
1270
1271    pub fn kind(&self) -> &ExecutionErrorKind {
1272        &self.inner.kind
1273    }
1274
1275    pub fn command(&self) -> Option<CommandIndex> {
1276        self.inner.command
1277    }
1278
1279    pub fn source(&self) -> &Option<BoxError> {
1280        &self.inner.source
1281    }
1282
1283    pub fn to_execution_status(&self) -> (ExecutionErrorKind, Option<CommandIndex>) {
1284        (self.kind().clone(), self.command())
1285    }
1286}
1287
1288impl ExecutionErrorTrait for ExecutionError {
1289    fn new(
1290        failure: ExecutionFailure,
1291        source: Option<BoxError>,
1292        _metadata: ExecutionErrorMetadata,
1293    ) -> Self {
1294        let ExecutionFailure { error, command } = failure;
1295        let err = ExecutionError::new(error, source);
1296        if let Some(command) = command {
1297            err.with_command_index(command)
1298        } else {
1299            err
1300        }
1301    }
1302
1303    fn with_command_index(self, command: CommandIndex) -> Self {
1304        self.with_command_index(command)
1305    }
1306
1307    fn kind(&self) -> &ExecutionErrorKind {
1308        self.kind()
1309    }
1310
1311    fn command(&self) -> Option<CommandIndex> {
1312        self.command()
1313    }
1314}
1315
1316#[derive(Debug)]
1317pub struct ExecutionErrorContext {
1318    kind: ExecutionErrorKind,
1319    metadata: ExecutionErrorMetadata,
1320    source: Option<BoxError>,
1321    command: Option<CommandIndex>,
1322}
1323
1324impl ExecutionErrorContext {
1325    pub fn kind(&self) -> &ExecutionErrorKind {
1326        &self.kind
1327    }
1328
1329    pub fn command(&self) -> Option<CommandIndex> {
1330        self.command
1331    }
1332
1333    pub fn metadata_with_source(&self) -> Option<ExecutionErrorMetadata> {
1334        let mut metadata = self.metadata.clone();
1335        if let Some(source) = self.source.as_ref() {
1336            metadata.insert("source".to_string(), source.to_string());
1337        }
1338
1339        (!metadata.is_empty()).then_some(metadata)
1340    }
1341
1342    pub fn to_execution_status(&self) -> (ExecutionErrorKind, Option<CommandIndex>) {
1343        (self.kind().clone(), self.command())
1344    }
1345}
1346
1347impl ExecutionErrorTrait for ExecutionErrorContext {
1348    fn new(
1349        failure: ExecutionFailure,
1350        source: Option<BoxError>,
1351        metadata: ExecutionErrorMetadata,
1352    ) -> Self {
1353        let ExecutionFailure { error, command } = failure;
1354        Self {
1355            kind: error,
1356            metadata,
1357            source,
1358            command,
1359        }
1360    }
1361
1362    fn with_command_index(self, command: CommandIndex) -> Self {
1363        Self {
1364            command: Some(command),
1365            ..self
1366        }
1367    }
1368
1369    fn kind(&self) -> &ExecutionErrorKind {
1370        self.kind()
1371    }
1372
1373    fn command(&self) -> Option<CommandIndex> {
1374        self.command()
1375    }
1376}
1377
1378impl std::fmt::Display for ExecutionErrorContext {
1379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1380        write!(f, "ExecutionErrorContext: {:?}", self)
1381    }
1382}
1383
1384impl std::error::Error for ExecutionErrorContext {
1385    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1386        self.source.as_deref().map(|e| e as _)
1387    }
1388}
1389
1390impl From<ExecutionErrorKind> for ExecutionErrorContext {
1391    fn from(kind: ExecutionErrorKind) -> Self {
1392        <Self as ExecutionErrorTrait>::from_kind(kind)
1393    }
1394}
1395
1396impl From<ExecutionFailure> for ExecutionErrorContext {
1397    fn from(value: ExecutionFailure) -> Self {
1398        <Self as ExecutionErrorTrait>::from_execution_failure(value)
1399    }
1400}
1401
1402impl From<ExecutionError> for ExecutionErrorContext {
1403    fn from(value: ExecutionError) -> Self {
1404        let ExecutionError { inner } = value;
1405        let ExecutionErrorInner {
1406            kind,
1407            source,
1408            command,
1409        } = *inner;
1410        Self {
1411            kind,
1412            metadata: BTreeMap::new(),
1413            source,
1414            command,
1415        }
1416    }
1417}
1418
1419impl From<ExecutionErrorContext> for ExecutionError {
1420    fn from(value: ExecutionErrorContext) -> Self {
1421        let ExecutionErrorContext {
1422            kind,
1423            metadata: _,
1424            source,
1425            command,
1426        } = value;
1427        let err = ExecutionError::new(kind, source);
1428        if let Some(command) = command {
1429            err.with_command_index(command)
1430        } else {
1431            err
1432        }
1433    }
1434}
1435
1436impl std::fmt::Display for ExecutionError {
1437    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1438        write!(f, "ExecutionError: {:?}", self)
1439    }
1440}
1441
1442impl std::error::Error for ExecutionError {
1443    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1444        self.inner.source.as_ref().map(|e| &**e as _)
1445    }
1446}
1447
1448impl From<ExecutionErrorKind> for ExecutionError {
1449    fn from(kind: ExecutionErrorKind) -> Self {
1450        Self::from_kind(kind)
1451    }
1452}
1453
1454impl From<ExecutionFailure> for ExecutionError {
1455    fn from(value: ExecutionFailure) -> Self {
1456        <Self as ExecutionErrorTrait>::from_execution_failure(value)
1457    }
1458}
1459
1460pub fn command_argument_error(e: CommandArgumentError, arg_idx: usize) -> ExecutionError {
1461    ExecutionError::from_kind(ExecutionErrorKind::command_argument_error(
1462        e,
1463        arg_idx as u16,
1464    ))
1465}
1466
1467/// Types of SuiError.
1468#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, IntoStaticStr)]
1469pub enum ErrorCategory {
1470    // A generic error that is retriable with new transaction resubmissions.
1471    Aborted,
1472    // Any validator or full node can check if a transaction is valid.
1473    InvalidTransaction,
1474    // Lock conflict on the transaction input.
1475    LockConflict,
1476    // Unexpected client error, for example generating invalid request or entering into invalid state.
1477    // And unexpected error from the remote peer. The validator may be malicious or there is a software bug.
1478    Internal,
1479    // Validator is overloaded.
1480    ValidatorOverloaded,
1481    // Target validator is down or there are network issues.
1482    Unavailable,
1483}
1484
1485impl ErrorCategory {
1486    // Whether the failure is retriable with new transaction submission.
1487    pub fn is_submission_retriable(&self) -> bool {
1488        matches!(
1489            self,
1490            ErrorCategory::Aborted
1491                | ErrorCategory::ValidatorOverloaded
1492                | ErrorCategory::Unavailable
1493        )
1494    }
1495}