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