iota_types/
error.rs

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