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