Skip to main content

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