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