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_NOT_FOUND_MSG_PREFIX} [{digest}]")]
521    TransactionNotFound { digest: TransactionDigest },
522    #[error("{TRANSACTIONS_NOT_FOUND_MSG_PREFIX} [{digests:?}]")]
523    TransactionsNotFound { digests: Vec<TransactionDigest> },
524    #[error("Could not find the referenced transaction events [{digest}]")]
525    TransactionEventsNotFound { digest: TransactionDigest },
526    #[error(
527        "Attempt to move to `Executed` state an transaction that has already been executed: {digest}"
528    )]
529    TransactionAlreadyExecuted { digest: TransactionDigest },
530    #[error("Object ID did not have the expected type")]
531    BadObjectType { error: String },
532    #[error("Fail to retrieve Object layout for {st}")]
533    FailObjectLayout { st: String },
534
535    #[error("Execution invariant violated")]
536    ExecutionInvariantViolation,
537    #[error("Validator {authority:?} is faulty in a Byzantine manner: {reason}")]
538    ByzantineAuthoritySuspicion {
539        authority: AuthorityName,
540        reason: String,
541    },
542    #[error(
543        "Attempted to access {object} through parent {given_parent}, \
544        but it's actual parent is {actual_owner}"
545    )]
546    InvalidChildObjectAccess {
547        object: ObjectId,
548        given_parent: ObjectId,
549        actual_owner: Owner,
550    },
551
552    #[error("Authority Error: {error}")]
553    GenericAuthority { error: String },
554
555    #[error("Failed to dispatch subscription: {error}")]
556    FailedToDispatchSubscription { error: String },
557
558    #[error("Failed to serialize Owner: {error}")]
559    OwnerFailedToSerialize { error: String },
560
561    #[error("Failed to deserialize fields into JSON: {error}")]
562    ExtraFieldFailedToDeserialize { error: String },
563
564    #[error("Failed to execute transaction locally by Orchestrator: {error}")]
565    TransactionOrchestratorLocalExecution { error: String },
566
567    // Errors returned by authority and client read API's
568    #[error("Failure serializing transaction in the requested format: {error}")]
569    TransactionSerialization { error: String },
570    #[error("Failure serializing object in the requested format: {error}")]
571    ObjectSerialization { error: String },
572    #[error("Failure deserializing object in the requested format: {error}")]
573    ObjectDeserialization { error: String },
574    #[error("Failure deserializing runtime module metadata in the requested format: {error}")]
575    RuntimeModuleMetadataDeserialization { error: String },
576    #[error("Event store component is not active on this node")]
577    NoEventStore,
578
579    // Client side error
580    #[error("Too many authority errors were detected for {action}: {errors:?}")]
581    TooManyIncorrectAuthorities {
582        errors: Vec<(AuthorityName, IotaError)>,
583        action: String,
584    },
585    #[error("Invalid transaction range query to the fullnode: {error}")]
586    FullNodeInvalidTxRangeQuery { error: String },
587
588    // Errors related to the authority-consensus interface.
589    #[error("Failed to submit transaction to consensus: {0}")]
590    FailedToSubmitToConsensus(String),
591    #[error("Failed to connect with consensus node: {0}")]
592    ConsensusConnectionBroken(String),
593    #[error("Failed to execute handle_consensus_transaction on Iota: {0}")]
594    HandleConsensusTransactionFailure(String),
595
596    // Cryptography errors.
597    #[error("Signature key generation error: {0}")]
598    SignatureKeyGen(String),
599    #[error("Key Conversion Error: {0}")]
600    KeyConversion(String),
601    #[error("Invalid Private Key provided")]
602    InvalidPrivateKey,
603
604    // Unsupported Operations on Fullnode
605    #[error("Fullnode does not support handle_certificate")]
606    FullNodeCantHandleCertificate,
607    #[error("Fullnode does not support ValidatorV2 endpoints")]
608    FullNodeCantHandleValidatorV2,
609    #[error("Fullnode does not support handle_authority_capabilities")]
610    FullNodeCantHandleAuthorityCapabilities,
611
612    // Epoch related errors.
613    #[error("Validator temporarily stopped processing transactions due to epoch change")]
614    ValidatorHaltedAtEpochEnd,
615    #[error("Operations for epoch {0} have ended")]
616    EpochEnded(EpochId),
617    #[error("Error when advancing epoch: {error}")]
618    AdvanceEpoch { error: String },
619
620    #[error("Transaction Expired")]
621    TransactionExpired,
622
623    // These are errors that occur when an RPC fails and is simply the utf8 message sent in a
624    // Tonic::Status
625    #[error("{1} - {0}")]
626    Rpc(String, String),
627
628    #[error("Method not allowed")]
629    InvalidRpcMethod,
630
631    // TODO: We should fold this into UserInputError::Unsupported.
632    #[error("Use of disabled feature: {error}")]
633    UnsupportedFeature { error: String },
634
635    #[error("Unable to communicate with the Quorum Driver channel: {error}")]
636    QuorumDriverCommunication { error: String },
637
638    #[error("Operation timed out")]
639    Timeout,
640
641    #[error("Error executing {0}")]
642    Execution(String),
643
644    #[error("Invalid committee composition")]
645    InvalidCommittee(String),
646
647    #[error("Missing committee information for epoch {0}")]
648    MissingCommitteeAtEpoch(EpochId),
649
650    #[error("Index store not available on this Fullnode")]
651    IndexStoreNotAvailable,
652
653    #[error("Failed to read dynamic field from table in the object store: {0}")]
654    DynamicFieldRead(String),
655
656    #[error("Failed to read or deserialize system state related data structures on-chain: {0}")]
657    IotaSystemStateRead(String),
658
659    #[error("Unexpected version error: {0}")]
660    UnexpectedVersion(String),
661
662    #[error("Message version is not supported at the current protocol version: {error}")]
663    WrongMessageVersion { error: String },
664
665    #[error("unknown error: {0}")]
666    Unknown(String),
667
668    #[error("Failed to perform file operation: {0}")]
669    FileIO(String),
670
671    #[error("Failed to get JWK")]
672    JWKRetrieval,
673
674    #[error("Storage error: {0}")]
675    Storage(String),
676
677    #[error(
678        "Validator cannot handle the request at the moment. Please retry after at least {retry_after_secs} seconds"
679    )]
680    ValidatorOverloadedRetryAfter { retry_after_secs: u64 },
681
682    #[error("Too many requests")]
683    TooManyRequests,
684
685    #[error("The request did not contain a certificate")]
686    NoCertificateProvided,
687
688    #[error("Invalid admin request: {0}")]
689    InvalidAdminRequest(String),
690
691    #[error("Could not find the referenced transaction effects [{digest}]")]
692    TransactionEffectsNotFound { digest: TransactionDigest },
693}
694
695#[repr(u64)]
696#[expect(non_camel_case_types)]
697#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
698/// Sub-status codes for the `UNKNOWN_VERIFICATION_ERROR` VM Status Code which
699/// provides more context TODO: add more Vm Status errors. We use
700/// `UNKNOWN_VERIFICATION_ERROR` as a catchall for now.
701pub enum VMMVerifierErrorSubStatusCode {
702    MULTIPLE_RETURN_VALUES_NOT_ALLOWED = 0,
703    INVALID_OBJECT_CREATION = 1,
704}
705
706#[repr(u64)]
707#[expect(non_camel_case_types)]
708#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
709/// Sub-status codes for the `MEMORY_LIMIT_EXCEEDED` VM Status Code which
710/// provides more context
711pub enum VMMemoryLimitExceededSubStatusCode {
712    EVENT_COUNT_LIMIT_EXCEEDED = 0,
713    EVENT_SIZE_LIMIT_EXCEEDED = 1,
714    NEW_ID_COUNT_LIMIT_EXCEEDED = 2,
715    DELETED_ID_COUNT_LIMIT_EXCEEDED = 3,
716    TRANSFER_ID_COUNT_LIMIT_EXCEEDED = 4,
717    OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED = 5,
718    OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED = 6,
719    TOTAL_EVENT_SIZE_LIMIT_EXCEEDED = 7,
720}
721
722pub type IotaResult<T = ()> = Result<T, IotaError>;
723pub type UserInputResult<T = ()> = Result<T, UserInputError>;
724
725impl From<iota_protocol_config::Error> for IotaError {
726    fn from(error: iota_protocol_config::Error) -> Self {
727        IotaError::WrongMessageVersion { error: error.0 }
728    }
729}
730
731impl From<ExecutionError> for IotaError {
732    fn from(error: ExecutionError) -> Self {
733        IotaError::Execution(error.to_string())
734    }
735}
736
737#[cfg(not(target_arch = "wasm32"))]
738impl From<Status> for IotaError {
739    fn from(status: Status) -> Self {
740        if status.message() == "Too many requests" {
741            return Self::TooManyRequests;
742        }
743        let result = bcs::from_bytes::<IotaError>(status.details());
744        if let Ok(iota_error) = result {
745            iota_error
746        } else {
747            Self::Rpc(
748                status.message().to_owned(),
749                status.code().description().to_owned(),
750            )
751        }
752    }
753}
754
755impl From<TypedStoreError> for IotaError {
756    fn from(e: TypedStoreError) -> Self {
757        Self::Storage(e.to_string())
758    }
759}
760
761impl From<crate::storage::error::Error> for IotaError {
762    fn from(e: crate::storage::error::Error) -> Self {
763        Self::Storage(e.to_string())
764    }
765}
766
767#[cfg(not(target_arch = "wasm32"))]
768impl From<IotaError> for Status {
769    fn from(error: IotaError) -> Self {
770        let bytes = bcs::to_bytes(&error).unwrap();
771        Status::with_details(tonic::Code::Internal, error.to_string(), bytes.into())
772    }
773}
774
775impl From<ExecutionErrorKind> for IotaError {
776    fn from(kind: ExecutionErrorKind) -> Self {
777        ExecutionError::from_kind(kind).into()
778    }
779}
780
781impl From<&str> for IotaError {
782    fn from(error: &str) -> Self {
783        IotaError::GenericAuthority {
784            error: error.to_string(),
785        }
786    }
787}
788
789impl From<String> for IotaError {
790    fn from(error: String) -> Self {
791        IotaError::GenericAuthority { error }
792    }
793}
794
795impl TryFrom<IotaError> for UserInputError {
796    type Error = anyhow::Error;
797
798    fn try_from(err: IotaError) -> Result<Self, Self::Error> {
799        match err {
800            IotaError::UserInput { error } => Ok(error),
801            other => anyhow::bail!("error `{other}` is not UserInput"),
802        }
803    }
804}
805
806impl From<UserInputError> for IotaError {
807    fn from(error: UserInputError) -> Self {
808        IotaError::UserInput { error }
809    }
810}
811
812impl IotaError {
813    pub fn individual_error_indicates_epoch_change(&self) -> bool {
814        matches!(
815            self,
816            IotaError::ValidatorHaltedAtEpochEnd | IotaError::MissingCommitteeAtEpoch(_)
817        )
818    }
819
820    /// Returns if the error is retryable and if the error's retryability is
821    /// explicitly categorized.
822    /// There should be only a handful of retryable errors. For now we list
823    /// common non-retryable error below to help us find more retryable
824    /// errors in logs.
825    pub fn is_retryable(&self) -> (bool, bool) {
826        let retryable = match self {
827            IotaError::Rpc { .. } => true,
828
829            // Reconfig error
830            IotaError::ValidatorHaltedAtEpochEnd => true,
831            IotaError::MissingCommitteeAtEpoch(..) => true,
832            IotaError::WrongEpoch { .. } => true,
833            IotaError::EpochEnded { .. } => true,
834
835            IotaError::UserInput { error } => {
836                match error {
837                    // Only ObjectNotFound and DependentPackageNotFound is potentially retryable
838                    UserInputError::ObjectNotFound { .. } => true,
839                    UserInputError::DependentPackageNotFound { .. } => true,
840                    _ => false,
841                }
842            }
843
844            IotaError::PotentiallyTemporarilyInvalidSignature { .. } => true,
845
846            // Overload errors
847            IotaError::TooManyTransactionsPendingExecution { .. } => true,
848            IotaError::TooManyTransactionsPendingOnObject { .. } => true,
849            IotaError::TooOldTransactionPendingOnObject { .. } => true,
850            IotaError::TooManyTransactionsPendingConsensus => true,
851            IotaError::ValidatorOverloadedRetryAfter { .. } => true,
852
853            // Transient consensus failure — other validators likely unaffected
854            IotaError::FailedToSubmitToConsensus(..) => true,
855
856            // Non retryable error
857            IotaError::Execution(..) => false,
858            IotaError::ByzantineAuthoritySuspicion { .. } => false,
859            IotaError::QuorumFailedToGetEffectsQuorumWhenProcessingTransaction { .. } => false,
860            IotaError::TxAlreadyFinalizedWithDifferentUserSigs => false,
861            IotaError::FailedToVerifyTxCertWithExecutedEffects { .. } => false,
862            IotaError::ObjectLockConflict { .. } => false,
863
864            // NB: This is not an internal overload, but instead an imposed rate
865            // limit / blocking of a client. It must be non-retryable otherwise
866            // we will make the threat worse through automatic retries.
867            IotaError::TooManyRequests => false,
868
869            // Signature errors — non-retryable, invalid input
870            IotaError::InvalidSignature { .. } => false,
871            IotaError::SignerSignatureAbsent { .. } => false,
872            IotaError::SignerSignatureNumberMismatch { .. } => false,
873            IotaError::IncorrectSigner { .. } => false,
874            IotaError::UnknownSigner { .. } => false,
875            IotaError::InvalidAuthenticator => false,
876
877            // Transaction lifecycle — non-retryable
878            IotaError::TransactionExpired => false,
879
880            // Fullnode-internal aggregation errors — non-retryable
881            IotaError::StakeAggregatorRepeatedSigner { .. } => false,
882            IotaError::CertificateRequiresQuorum => false,
883
884            // For all un-categorized errors, return here with categorized = false.
885            _ => return (false, false),
886        };
887
888        (retryable, true)
889    }
890
891    pub fn is_object_or_package_not_found(&self) -> bool {
892        match self {
893            IotaError::UserInput { error } => {
894                matches!(
895                    error,
896                    UserInputError::ObjectNotFound { .. }
897                        | UserInputError::DependentPackageNotFound { .. }
898                )
899            }
900            _ => false,
901        }
902    }
903
904    pub fn is_overload(&self) -> bool {
905        matches!(
906            self,
907            IotaError::TooManyTransactionsPendingExecution { .. }
908                | IotaError::TooManyTransactionsPendingOnObject { .. }
909                | IotaError::TooOldTransactionPendingOnObject { .. }
910                | IotaError::TooManyTransactionsPendingConsensus
911        )
912    }
913
914    pub fn is_retryable_overload(&self) -> bool {
915        matches!(self, IotaError::ValidatorOverloadedRetryAfter { .. })
916    }
917
918    /// Returns `true` for errors caused by storage or epoch-lifecycle
919    /// failures (RocksDB, epoch store closed) rather than semantic transaction
920    /// problems. Used by post-consensus validation to distinguish fatal errors
921    /// (halt the commit) from per-transaction drops.
922    pub fn is_storage_or_epoch_error(&self) -> bool {
923        matches!(
924            self,
925            IotaError::Storage(..)
926                | IotaError::EpochEnded(..)
927                | IotaError::ValidatorHaltedAtEpochEnd
928        )
929    }
930
931    pub fn retry_after_secs(&self) -> u64 {
932        match self {
933            IotaError::ValidatorOverloadedRetryAfter { retry_after_secs } => *retry_after_secs,
934            _ => 0,
935        }
936    }
937}
938
939/// Categorizes IotaError into ErrorCategory.
940pub fn categorize(error: &IotaError) -> ErrorCategory {
941    match error {
942        IotaError::UserInput { error } => match error {
943            UserInputError::ObjectNotFound { .. } => ErrorCategory::Aborted,
944            UserInputError::DependentPackageNotFound { .. } => ErrorCategory::Aborted,
945            _ => ErrorCategory::InvalidTransaction,
946        },
947        IotaError::InvalidSignature { .. }
948        | IotaError::SignerSignatureAbsent { .. }
949        | IotaError::SignerSignatureNumberMismatch { .. }
950        | IotaError::IncorrectSigner { .. }
951        | IotaError::UnknownSigner { .. }
952        | IotaError::TransactionExpired => ErrorCategory::InvalidTransaction,
953
954        IotaError::ObjectLockConflict { .. } => ErrorCategory::LockConflict,
955
956        IotaError::TooManyTransactionsPendingExecution { .. }
957        | IotaError::TooManyTransactionsPendingOnObject { .. }
958        | IotaError::TooOldTransactionPendingOnObject { .. }
959        | IotaError::TooManyTransactionsPendingConsensus
960        | IotaError::ValidatorOverloadedRetryAfter { .. } => ErrorCategory::ValidatorOverloaded,
961
962        _ => ErrorCategory::Aborted,
963    }
964}
965
966/// Types of IotaError categories for retry decisions.
967#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, IntoStaticStr)]
968pub enum ErrorCategory {
969    /// A generic error that is retriable with new transaction resubmissions.
970    Aborted,
971    /// Any validator or full node can check if a transaction is valid.
972    InvalidTransaction,
973    /// Lock conflict on the transaction input.
974    LockConflict,
975    /// Unexpected client error, for example generating invalid request or
976    /// entering into invalid state. And unexpected error from the remote
977    /// peer.
978    Internal,
979    /// Validator is overloaded.
980    ValidatorOverloaded,
981    /// Target validator is down or there are network issues.
982    Unavailable,
983}
984
985impl ErrorCategory {
986    /// Whether the failure is retriable with new transaction submission.
987    pub fn is_submission_retriable(&self) -> bool {
988        matches!(
989            self,
990            ErrorCategory::Aborted
991                | ErrorCategory::ValidatorOverloaded
992                | ErrorCategory::Unavailable
993        )
994    }
995}
996
997impl IotaError {
998    /// Categorizes this error into an ErrorCategory.
999    pub fn categorize(&self) -> ErrorCategory {
1000        categorize(self)
1001    }
1002}
1003
1004impl Ord for IotaError {
1005    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1006        Ord::cmp(self.as_ref(), other.as_ref())
1007    }
1008}
1009
1010impl PartialOrd for IotaError {
1011    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1012        Some(self.cmp(other))
1013    }
1014}
1015
1016type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
1017
1018pub type ExecutionErrorKind = ExecutionFailureStatus;
1019
1020#[derive(Debug)]
1021pub struct ExecutionError {
1022    inner: Box<ExecutionErrorInner>,
1023}
1024
1025#[derive(Debug)]
1026struct ExecutionErrorInner {
1027    kind: ExecutionErrorKind,
1028    source: Option<BoxError>,
1029    command: Option<u64>,
1030}
1031
1032impl ExecutionError {
1033    pub fn new(kind: ExecutionErrorKind, source: Option<BoxError>) -> Self {
1034        Self {
1035            inner: Box::new(ExecutionErrorInner {
1036                kind,
1037                source,
1038                command: None,
1039            }),
1040        }
1041    }
1042
1043    pub fn new_with_source<E: Into<BoxError>>(kind: ExecutionErrorKind, source: E) -> Self {
1044        Self::new(kind, Some(source.into()))
1045    }
1046
1047    pub fn invariant_violation<E: Into<BoxError>>(source: E) -> Self {
1048        Self::new_with_source(ExecutionFailureStatus::InvariantViolation, source)
1049    }
1050
1051    pub fn with_command_index(mut self, command: u64) -> Self {
1052        self.inner.command = Some(command);
1053        self
1054    }
1055
1056    /// Rewrap this error, produced while executing a Move authenticator, as a
1057    /// [`ExecutionFailureStatus::MoveAuthenticationError`]. The command index
1058    /// is dropped: it referred to a command of the authenticator's own
1059    /// programmable transaction and is meaningless in the transaction's
1060    /// effects, where it would otherwise collide with the first command of the
1061    /// programmable transaction.
1062    pub fn into_move_authentication_error(self) -> Self {
1063        let ExecutionErrorInner { kind, source, .. } = *self.inner;
1064        Self::new(
1065            ExecutionFailureStatus::MoveAuthenticationError {
1066                error: Box::new(kind),
1067            },
1068            source,
1069        )
1070    }
1071
1072    pub fn from_kind(kind: ExecutionErrorKind) -> Self {
1073        Self::new(kind, None)
1074    }
1075
1076    pub fn kind(&self) -> &ExecutionErrorKind {
1077        &self.inner.kind
1078    }
1079
1080    pub fn command(&self) -> Option<u64> {
1081        self.inner.command
1082    }
1083
1084    pub fn source(&self) -> &Option<BoxError> {
1085        &self.inner.source
1086    }
1087
1088    pub fn to_execution_status(&self) -> (ExecutionFailureStatus, Option<u64>) {
1089        (self.kind().clone(), self.command())
1090    }
1091}
1092
1093impl std::fmt::Display for ExecutionError {
1094    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095        write!(f, "{}: {}", self.inner.kind.as_ref(), self.inner.kind)?;
1096        if let Some(source) = self.inner.source.as_ref() {
1097            write!(f, "; caused by: {source}")?;
1098        }
1099        if let Some(command) = self.inner.command {
1100            write!(f, "; at command index: {command}")?;
1101        }
1102        Ok(())
1103    }
1104}
1105
1106impl std::error::Error for ExecutionError {
1107    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1108        self.inner.source.as_ref().map(|e| &**e as _)
1109    }
1110}
1111
1112impl From<ExecutionErrorKind> for ExecutionError {
1113    fn from(kind: ExecutionErrorKind) -> Self {
1114        Self::from_kind(kind)
1115    }
1116}
1117
1118pub fn command_argument_error(e: CommandArgumentError, arg_idx: usize) -> ExecutionError {
1119    ExecutionError::from_kind(ExecutionErrorKind::command_argument_error(
1120        e,
1121        arg_idx as u16,
1122    ))
1123}