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