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