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