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