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