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