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