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}
42pub(crate) use fp_ensure;
43
44use crate::{
45    digests::TransactionEventsDigest,
46    execution_status::{CommandIndex, ExecutionFailureStatus},
47};
48
49#[macro_export]
50macro_rules! exit_main {
51    ($result:expr) => {
52        match $result {
53            Ok(_) => (),
54            Err(err) => {
55                let err = format!("{:?}", err);
56                println!("{}", err.bold().red());
57                std::process::exit(1);
58            }
59        }
60    };
61}
62
63#[macro_export]
64macro_rules! make_invariant_violation {
65    ($($args:expr),* $(,)?) => {{
66        if cfg!(debug_assertions) {
67            panic!($($args),*)
68        }
69        ExecutionError::invariant_violation(format!($($args),*))
70    }}
71}
72
73#[macro_export]
74macro_rules! invariant_violation {
75    ($($args:expr),* $(,)?) => {
76        return Err(make_invariant_violation!($($args),*).into())
77    };
78}
79
80#[macro_export]
81macro_rules! assert_invariant {
82    ($cond:expr, $($args:expr),* $(,)?) => {{
83        if !$cond {
84            invariant_violation!($($args),*)
85        }
86    }};
87}
88
89#[derive(
90    Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr, IntoStaticStr,
91)]
92pub enum UserInputError {
93    #[error("Mutable object {object_id} cannot appear more than one in one transaction")]
94    MutableObjectUsedMoreThanOnce { object_id: ObjectID },
95    #[error("Wrong number of parameters for the transaction")]
96    ObjectInputArityViolation,
97    #[error(
98        "Could not find the referenced object {:?} at version {:?}",
99        object_id,
100        version
101    )]
102    ObjectNotFound {
103        object_id: ObjectID,
104        version: Option<SequenceNumber>,
105    },
106    #[error(
107        "Object {provided_obj_ref:?} is not available for consumption, its current version: {current_version:?}"
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
206    #[error(
207        "IOTA payment transactions use first input coin for gas payment, but found a different gas object"
208    )]
209    UnexpectedGasPaymentObject,
210
211    #[error("Wrong initial version given for shared object")]
212    SharedObjectStartingVersionMismatch,
213
214    #[error(
215        "Attempt to transfer object {object_id} that does not have public transfer. Object transfer must be done instead using a distinct Move function call"
216    )]
217    TransferObjectWithoutPublicTransfer { object_id: ObjectID },
218
219    #[error(
220        "TransferObjects, MergeCoin, and Publish cannot have empty arguments. \
221        If MakeMoveVec has empty arguments, it must have a type specified"
222    )]
223    EmptyCommandInput,
224
225    #[error("Transaction is denied: {}", error)]
226    TransactionDenied { error: String },
227
228    #[error("Feature is not supported: {0}")]
229    Unsupported(String),
230
231    #[error("Query transactions with move function input error: {0}")]
232    MoveFunctionInput(String),
233
234    #[error("Verified checkpoint not found for sequence number: {0}")]
235    VerifiedCheckpointNotFound(CheckpointSequenceNumber),
236
237    #[error("Verified checkpoint not found for digest: {0}")]
238    VerifiedCheckpointDigestNotFound(String),
239
240    #[error("Latest checkpoint sequence number not found")]
241    LatestCheckpointSequenceNumberNotFound,
242
243    #[error("Checkpoint contents not found for digest: {0}")]
244    CheckpointContentsNotFound(CheckpointContentsDigest),
245
246    #[error("Genesis transaction not found")]
247    GenesisTransactionNotFound,
248
249    #[error("Transaction {0} not found")]
250    TransactionCursorNotFound(u64),
251
252    #[error(
253        "Object {:?} is a system object and cannot be accessed by user transactions",
254        object_id
255    )]
256    InaccessibleSystemObject { object_id: ObjectID },
257    #[error(
258        "{max_publish_commands} max publish/upgrade commands allowed, {publish_count} provided"
259    )]
260    MaxPublishCountExceeded {
261        max_publish_commands: u64,
262        publish_count: u64,
263    },
264
265    #[error("Immutable parameter provided, mutable parameter expected")]
266    MutableParameterExpected { object_id: ObjectID },
267
268    #[error("Address {address:?} is denied for coin {coin_type}")]
269    AddressDeniedForCoin {
270        address: IotaAddress,
271        coin_type: String,
272    },
273
274    #[error("Commands following a command with Random can only be TransferObjects or MergeCoins")]
275    PostRandomCommandRestrictions,
276
277    // Soft Bundle related errors
278    #[error(
279        "Number of transactions exceeds the maximum allowed ({:?}) in a Soft Bundle",
280        limit
281    )]
282    TooManyTransactionsInSoftBundle { limit: u64 },
283    #[error("Transaction {:?} in Soft Bundle contains no shared objects", digest)]
284    NoSharedObject { digest: TransactionDigest },
285    #[error("Transaction {:?} in Soft Bundle has already been executed", digest)]
286    AlreadyExecuted { digest: TransactionDigest },
287    #[error("At least one certificate in Soft Bundle has already been processed")]
288    CertificateAlreadyProcessed,
289    #[error(
290        "Gas price for transaction {:?} in Soft Bundle mismatch: want {:?}, have {:?}",
291        digest,
292        expected,
293        actual
294    )]
295    GasPriceMismatch {
296        digest: TransactionDigest,
297        expected: u64,
298        actual: u64,
299    },
300
301    #[error("Coin type is globally paused for use: {coin_type}")]
302    CoinTypeGlobalPause { coin_type: String },
303}
304
305#[derive(
306    Eq,
307    PartialEq,
308    Clone,
309    Debug,
310    Serialize,
311    Deserialize,
312    Hash,
313    AsRefStr,
314    IntoStaticStr,
315    JsonSchema,
316    Error,
317)]
318#[serde(tag = "code", rename = "ObjectResponseError", rename_all = "camelCase")]
319pub enum IotaObjectResponseError {
320    #[error("Object {:?} does not exist", object_id)]
321    NotExists { object_id: ObjectID },
322    #[error("Cannot find dynamic field for parent object {:?}", parent_object_id)]
323    DynamicFieldNotFound { parent_object_id: ObjectID },
324    #[error(
325        "Object has been deleted object_id: {:?} at version: {:?} in digest {:?}",
326        object_id,
327        version,
328        digest
329    )]
330    Deleted {
331        object_id: ObjectID,
332        /// Object version.
333        version: SequenceNumber,
334        /// Base64 string representing the object digest
335        digest: ObjectDigest,
336    },
337    #[error("Unknown Error")]
338    Unknown,
339    #[error("Display Error: {:?}", error)]
340    Display { error: String },
341    // TODO: also integrate IotaPastObjectResponse (VersionNotFound,  VersionTooHigh)
342}
343
344/// Custom error type for Iota.
345#[derive(
346    Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr, IntoStaticStr,
347)]
348pub enum IotaError {
349    #[error("Error checking transaction input objects: {:?}", error)]
350    UserInput { error: UserInputError },
351
352    #[error("Error checking transaction object: {:?}", error)]
353    IotaObjectResponse { error: IotaObjectResponseError },
354
355    #[error("Expecting a single owner, shared ownership found")]
356    UnexpectedOwnerType,
357
358    #[error("There are already {queue_len} transactions pending, above threshold of {threshold}")]
359    TooManyTransactionsPendingExecution { queue_len: usize, threshold: usize },
360
361    #[error("There are too many transactions pending in consensus")]
362    TooManyTransactionsPendingConsensus,
363
364    #[error(
365        "Input {object_id} already has {queue_len} transactions pending, above threshold of {threshold}"
366    )]
367    TooManyTransactionsPendingOnObject {
368        object_id: ObjectID,
369        queue_len: usize,
370        threshold: usize,
371    },
372
373    #[error(
374        "Input {object_id} has a transaction {txn_age_sec} seconds old pending, above threshold of {threshold} seconds"
375    )]
376    TooOldTransactionPendingOnObject {
377        object_id: ObjectID,
378        txn_age_sec: u64,
379        threshold: u64,
380    },
381
382    #[error("Soft bundle must only contain transactions of UserTransaction kind")]
383    InvalidTxKindInSoftBundle,
384
385    // Signature verification
386    #[error("Signature is not valid: {}", error)]
387    InvalidSignature { error: String },
388    #[error("Required Signature from {expected} is absent {:?}", actual)]
389    SignerSignatureAbsent {
390        expected: String,
391        actual: Vec<String>,
392    },
393    #[error("Expect {expected} signer signatures but got {actual}")]
394    SignerSignatureNumberMismatch { expected: usize, actual: usize },
395    #[error("Value was not signed by the correct sender: {}", error)]
396    IncorrectSigner { error: String },
397    #[error(
398        "Value was not signed by a known authority. signer: {:?}, index: {:?}, committee: {committee}",
399        signer,
400        index
401    )]
402    UnknownSigner {
403        signer: Option<String>,
404        index: Option<u32>,
405        committee: Box<Committee>,
406    },
407    #[error(
408        "Validator {:?} responded multiple signatures for the same message, conflicting: {:?}",
409        signer,
410        conflicting_sig
411    )]
412    StakeAggregatorRepeatedSigner {
413        signer: AuthorityName,
414        conflicting_sig: bool,
415    },
416    // TODO: Used for distinguishing between different occurrences of invalid signatures, to allow
417    // retries in some cases.
418    #[error(
419        "Signature is not valid, but a retry may result in a valid one: {}",
420        error
421    )]
422    PotentiallyTemporarilyInvalidSignature { error: String },
423
424    // Certificate verification and execution
425    #[error(
426        "Signature or certificate from wrong epoch, expected {expected_epoch}, got {actual_epoch}"
427    )]
428    WrongEpoch {
429        expected_epoch: EpochId,
430        actual_epoch: EpochId,
431    },
432    #[error("Signatures in a certificate must form a quorum")]
433    CertificateRequiresQuorum,
434    #[error("Transaction certificate processing failed: {err}")]
435    ErrorWhileProcessingCertificate { err: String },
436    #[error(
437        "Failed to get a quorum of signed effects when processing transaction: {effects_map:?}"
438    )]
439    QuorumFailedToGetEffectsQuorumWhenProcessingTransaction {
440        effects_map: BTreeMap<TransactionEffectsDigest, (Vec<AuthorityName>, StakeUnit)>,
441    },
442    #[error(
443        "Failed to verify Tx certificate with executed effects, error: {error:?}, validator: {validator_name:?}"
444    )]
445    FailedToVerifyTxCertWithExecutedEffects {
446        validator_name: AuthorityName,
447        error: String,
448    },
449    #[error("Transaction is already finalized but with different user signatures")]
450    TxAlreadyFinalizedWithDifferentUserSigs,
451
452    // Account access
453    #[error("Invalid authenticator")]
454    InvalidAuthenticator,
455    #[error("Invalid address")]
456    InvalidAddress,
457    #[error("Invalid transaction digest.")]
458    InvalidTransactionDigest,
459
460    #[error("Invalid digest length. Expected {expected}, got {actual}")]
461    InvalidDigestLength { expected: usize, actual: usize },
462    #[error("Invalid DKG message size")]
463    InvalidDkgMessageSize,
464
465    #[error("Unexpected message.")]
466    UnexpectedMessage,
467
468    // Move module publishing related errors
469    #[error("Failed to verify the Move module, reason: {error:?}.")]
470    ModuleVerificationFailure { error: String },
471    #[error("Failed to deserialize the Move module, reason: {error:?}.")]
472    ModuleDeserializationFailure { error: String },
473    #[error("Failed to publish the Move module(s), reason: {error}")]
474    ModulePublishFailure { error: String },
475    #[error("Failed to build Move modules: {error}.")]
476    ModuleBuildFailure { error: String },
477
478    // Move call related errors
479    #[error("Function resolution failure: {error:?}.")]
480    FunctionNotFound { error: String },
481    #[error("Module not found in package: {module_name:?}.")]
482    ModuleNotFound { module_name: String },
483    #[error("Type error while binding function arguments: {error:?}.")]
484    Type { error: String },
485    #[error("Circular object ownership detected")]
486    CircularObjectOwnership,
487
488    // Internal state errors
489    #[error("Attempt to re-initialize a transaction lock for objects {:?}.", refs)]
490    ObjectLockAlreadyInitialized { refs: Vec<ObjectRef> },
491    #[error(
492        "Object {obj_ref:?} already locked by a different transaction: {pending_transaction:?}"
493    )]
494    ObjectLockConflict {
495        obj_ref: ObjectRef,
496        pending_transaction: TransactionDigest,
497    },
498    #[error(
499        "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:?}"
500    )]
501    ObjectLockedAtFutureEpoch {
502        obj_refs: Vec<ObjectRef>,
503        locked_epoch: EpochId,
504        new_epoch: EpochId,
505        locked_by_tx: TransactionDigest,
506    },
507    #[error("{TRANSACTION_NOT_FOUND_MSG_PREFIX} [{:?}].", digest)]
508    TransactionNotFound { digest: TransactionDigest },
509    #[error("{TRANSACTIONS_NOT_FOUND_MSG_PREFIX} [{:?}].", digests)]
510    TransactionsNotFound { digests: Vec<TransactionDigest> },
511    #[error("Could not find the referenced transaction events [{digest:?}].")]
512    TransactionEventsNotFound { digest: TransactionEventsDigest },
513    #[error(
514        "Attempt to move to `Executed` state an transaction that has already been executed: {:?}.",
515        digest
516    )]
517    TransactionAlreadyExecuted { digest: TransactionDigest },
518    #[error("Object ID did not have the expected type")]
519    BadObjectType { error: String },
520    #[error("Fail to retrieve Object layout for {st}")]
521    FailObjectLayout { st: String },
522
523    #[error("Execution invariant violated")]
524    ExecutionInvariantViolation,
525    #[error("Validator {authority:?} is faulty in a Byzantine manner: {reason:?}")]
526    ByzantineAuthoritySuspicion {
527        authority: AuthorityName,
528        reason: String,
529    },
530    #[error(
531        "Attempted to access {object} through parent {given_parent}, \
532        but it's actual parent is {actual_owner}"
533    )]
534    InvalidChildObjectAccess {
535        object: ObjectID,
536        given_parent: ObjectID,
537        actual_owner: Owner,
538    },
539
540    #[error("Authority Error: {error:?}")]
541    GenericAuthority { error: String },
542
543    #[error("Generic Bridge Error: {error:?}")]
544    GenericBridge { error: String },
545
546    #[error("Failed to dispatch subscription: {error:?}")]
547    FailedToDispatchSubscription { error: String },
548
549    #[error("Failed to serialize Owner: {error:?}")]
550    OwnerFailedToSerialize { error: String },
551
552    #[error("Failed to deserialize fields into JSON: {error:?}")]
553    ExtraFieldFailedToDeserialize { error: String },
554
555    #[error("Failed to execute transaction locally by Orchestrator: {error:?}")]
556    TransactionOrchestratorLocalExecution { error: String },
557
558    // Errors returned by authority and client read API's
559    #[error("Failure serializing transaction in the requested format: {:?}", error)]
560    TransactionSerialization { error: String },
561    #[error("Failure serializing object in the requested format: {:?}", error)]
562    ObjectSerialization { error: String },
563    #[error("Failure deserializing object in the requested format: {:?}", error)]
564    ObjectDeserialization { error: String },
565    #[error("Event store component is not active on this node")]
566    NoEventStore,
567
568    // Client side error
569    #[error("Too many authority errors were detected for {}: {:?}", action, errors)]
570    TooManyIncorrectAuthorities {
571        errors: Vec<(AuthorityName, IotaError)>,
572        action: String,
573    },
574    #[error("Invalid transaction range query to the fullnode: {:?}", error)]
575    FullNodeInvalidTxRangeQuery { error: String },
576
577    // Errors related to the authority-consensus interface.
578    #[error("Failed to submit transaction to consensus: {0}")]
579    FailedToSubmitToConsensus(String),
580    #[error("Failed to connect with consensus node: {0}")]
581    ConsensusConnectionBroken(String),
582    #[error("Failed to execute handle_consensus_transaction on Iota: {0}")]
583    HandleConsensusTransactionFailure(String),
584
585    // Cryptography errors.
586    #[error("Signature key generation error: {0}")]
587    SignatureKeyGen(String),
588    #[error("Key Conversion Error: {0}")]
589    KeyConversion(String),
590    #[error("Invalid Private Key provided")]
591    InvalidPrivateKey,
592
593    // Unsupported Operations on Fullnode
594    #[error("Fullnode does not support handle_certificate")]
595    FullNodeCantHandleCertificate,
596
597    // Epoch related errors.
598    #[error("Validator temporarily stopped processing transactions due to epoch change")]
599    ValidatorHaltedAtEpochEnd,
600    #[error("Operations for epoch {0} have ended")]
601    EpochEnded(EpochId),
602    #[error("Error when advancing epoch: {:?}", error)]
603    AdvanceEpoch { error: String },
604
605    #[error("Transaction Expired")]
606    TransactionExpired,
607
608    // These are errors that occur when an RPC fails and is simply the utf8 message sent in a
609    // Tonic::Status
610    #[error("{1} - {0}")]
611    Rpc(String, String),
612
613    #[error("Method not allowed")]
614    InvalidRpcMethod,
615
616    // TODO: We should fold this into UserInputError::Unsupported.
617    #[error("Use of disabled feature: {:?}", error)]
618    UnsupportedFeature { error: String },
619
620    #[error("Unable to communicate with the Quorum Driver channel: {:?}", error)]
621    QuorumDriverCommunication { error: String },
622
623    #[error("Operation timed out")]
624    Timeout,
625
626    #[error("Error executing {0}")]
627    Execution(String),
628
629    #[error("Invalid committee composition")]
630    InvalidCommittee(String),
631
632    #[error("Missing committee information for epoch {0}")]
633    MissingCommitteeAtEpoch(EpochId),
634
635    #[error("Index store not available on this Fullnode.")]
636    IndexStoreNotAvailable,
637
638    #[error("Failed to read dynamic field from table in the object store: {0}")]
639    DynamicFieldRead(String),
640
641    #[error("Failed to read or deserialize system state related data structures on-chain: {0}")]
642    IotaSystemStateRead(String),
643
644    #[error("Failed to read or deserialize bridge related data structures on-chain: {0}")]
645    IotaBridgeRead(String),
646
647    #[error("Unexpected version error: {0}")]
648    UnexpectedVersion(String),
649
650    #[error("Message version is not supported at the current protocol version: {error}")]
651    WrongMessageVersion { error: String },
652
653    #[error("unknown error: {0}")]
654    Unknown(String),
655
656    #[error("Failed to perform file operation: {0}")]
657    FileIO(String),
658
659    #[error("Failed to get JWK")]
660    JWKRetrieval,
661
662    #[error("Storage error: {0}")]
663    Storage(String),
664
665    #[error(
666        "Validator cannot handle the request at the moment. Please retry after at least {retry_after_secs} seconds."
667    )]
668    ValidatorOverloadedRetryAfter { retry_after_secs: u64 },
669
670    #[error("Too many requests")]
671    TooManyRequests,
672
673    #[error("The request did not contain a certificate")]
674    NoCertificateProvided,
675}
676
677#[repr(u64)]
678#[expect(non_camel_case_types)]
679#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
680/// Sub-status codes for the `UNKNOWN_VERIFICATION_ERROR` VM Status Code which
681/// provides more context TODO: add more Vm Status errors. We use
682/// `UNKNOWN_VERIFICATION_ERROR` as a catchall for now.
683pub enum VMMVerifierErrorSubStatusCode {
684    MULTIPLE_RETURN_VALUES_NOT_ALLOWED = 0,
685    INVALID_OBJECT_CREATION = 1,
686}
687
688#[repr(u64)]
689#[expect(non_camel_case_types)]
690#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
691/// Sub-status codes for the `MEMORY_LIMIT_EXCEEDED` VM Status Code which
692/// provides more context
693pub enum VMMemoryLimitExceededSubStatusCode {
694    EVENT_COUNT_LIMIT_EXCEEDED = 0,
695    EVENT_SIZE_LIMIT_EXCEEDED = 1,
696    NEW_ID_COUNT_LIMIT_EXCEEDED = 2,
697    DELETED_ID_COUNT_LIMIT_EXCEEDED = 3,
698    TRANSFER_ID_COUNT_LIMIT_EXCEEDED = 4,
699    OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED = 5,
700    OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED = 6,
701    TOTAL_EVENT_SIZE_LIMIT_EXCEEDED = 7,
702}
703
704pub type IotaResult<T = ()> = Result<T, IotaError>;
705pub type UserInputResult<T = ()> = Result<T, UserInputError>;
706
707impl From<iota_protocol_config::Error> for IotaError {
708    fn from(error: iota_protocol_config::Error) -> Self {
709        IotaError::WrongMessageVersion { error: error.0 }
710    }
711}
712
713impl From<ExecutionError> for IotaError {
714    fn from(error: ExecutionError) -> Self {
715        IotaError::Execution(error.to_string())
716    }
717}
718
719impl From<Status> for IotaError {
720    fn from(status: Status) -> Self {
721        if status.message() == "Too many requests" {
722            return Self::TooManyRequests;
723        }
724        let result = bcs::from_bytes::<IotaError>(status.details());
725        if let Ok(iota_error) = result {
726            iota_error
727        } else {
728            Self::Rpc(
729                status.message().to_owned(),
730                status.code().description().to_owned(),
731            )
732        }
733    }
734}
735
736impl From<TypedStoreError> for IotaError {
737    fn from(e: TypedStoreError) -> Self {
738        Self::Storage(e.to_string())
739    }
740}
741
742impl From<crate::storage::error::Error> for IotaError {
743    fn from(e: crate::storage::error::Error) -> Self {
744        Self::Storage(e.to_string())
745    }
746}
747
748impl From<IotaError> for Status {
749    fn from(error: IotaError) -> Self {
750        let bytes = bcs::to_bytes(&error).unwrap();
751        Status::with_details(tonic::Code::Internal, error.to_string(), bytes.into())
752    }
753}
754
755impl From<ExecutionErrorKind> for IotaError {
756    fn from(kind: ExecutionErrorKind) -> Self {
757        ExecutionError::from_kind(kind).into()
758    }
759}
760
761impl From<&str> for IotaError {
762    fn from(error: &str) -> Self {
763        IotaError::GenericAuthority {
764            error: error.to_string(),
765        }
766    }
767}
768
769impl From<String> for IotaError {
770    fn from(error: String) -> Self {
771        IotaError::GenericAuthority { error }
772    }
773}
774
775impl TryFrom<IotaError> for UserInputError {
776    type Error = anyhow::Error;
777
778    fn try_from(err: IotaError) -> Result<Self, Self::Error> {
779        match err {
780            IotaError::UserInput { error } => Ok(error),
781            other => anyhow::bail!("error {:?} is not UserInput", other),
782        }
783    }
784}
785
786impl From<UserInputError> for IotaError {
787    fn from(error: UserInputError) -> Self {
788        IotaError::UserInput { error }
789    }
790}
791
792impl From<IotaObjectResponseError> for IotaError {
793    fn from(error: IotaObjectResponseError) -> Self {
794        IotaError::IotaObjectResponse { error }
795    }
796}
797
798impl IotaError {
799    pub fn individual_error_indicates_epoch_change(&self) -> bool {
800        matches!(
801            self,
802            IotaError::ValidatorHaltedAtEpochEnd | IotaError::MissingCommitteeAtEpoch(_)
803        )
804    }
805
806    /// Returns if the error is retryable and if the error's retryability is
807    /// explicitly categorized.
808    /// There should be only a handful of retryable errors. For now we list
809    /// common non-retryable error below to help us find more retryable
810    /// errors in logs.
811    pub fn is_retryable(&self) -> (bool, bool) {
812        let retryable = match self {
813            IotaError::Rpc { .. } => true,
814
815            // Reconfig error
816            IotaError::ValidatorHaltedAtEpochEnd => true,
817            IotaError::MissingCommitteeAtEpoch(..) => true,
818            IotaError::WrongEpoch { .. } => true,
819            IotaError::EpochEnded { .. } => true,
820
821            IotaError::UserInput { error } => {
822                match error {
823                    // Only ObjectNotFound and DependentPackageNotFound is potentially retryable
824                    UserInputError::ObjectNotFound { .. } => true,
825                    UserInputError::DependentPackageNotFound { .. } => true,
826                    _ => false,
827                }
828            }
829
830            IotaError::PotentiallyTemporarilyInvalidSignature { .. } => true,
831
832            // Overload errors
833            IotaError::TooManyTransactionsPendingExecution { .. } => true,
834            IotaError::TooManyTransactionsPendingOnObject { .. } => true,
835            IotaError::TooOldTransactionPendingOnObject { .. } => true,
836            IotaError::TooManyTransactionsPendingConsensus => true,
837            IotaError::ValidatorOverloadedRetryAfter { .. } => true,
838
839            // Non retryable error
840            IotaError::Execution(..) => false,
841            IotaError::ByzantineAuthoritySuspicion { .. } => false,
842            IotaError::QuorumFailedToGetEffectsQuorumWhenProcessingTransaction { .. } => false,
843            IotaError::TxAlreadyFinalizedWithDifferentUserSigs => false,
844            IotaError::FailedToVerifyTxCertWithExecutedEffects { .. } => false,
845            IotaError::ObjectLockConflict { .. } => false,
846
847            // NB: This is not an internal overload, but instead an imposed rate
848            // limit / blocking of a client. It must be non-retryable otherwise
849            // we will make the threat worse through automatic retries.
850            IotaError::TooManyRequests => false,
851
852            // For all un-categorized errors, return here with categorized = false.
853            _ => return (false, false),
854        };
855
856        (retryable, true)
857    }
858
859    pub fn is_object_or_package_not_found(&self) -> bool {
860        match self {
861            IotaError::UserInput { error } => {
862                matches!(
863                    error,
864                    UserInputError::ObjectNotFound { .. }
865                        | UserInputError::DependentPackageNotFound { .. }
866                )
867            }
868            _ => false,
869        }
870    }
871
872    pub fn is_overload(&self) -> bool {
873        matches!(
874            self,
875            IotaError::TooManyTransactionsPendingExecution { .. }
876                | IotaError::TooManyTransactionsPendingOnObject { .. }
877                | IotaError::TooOldTransactionPendingOnObject { .. }
878                | IotaError::TooManyTransactionsPendingConsensus
879        )
880    }
881
882    pub fn is_retryable_overload(&self) -> bool {
883        matches!(self, IotaError::ValidatorOverloadedRetryAfter { .. })
884    }
885
886    pub fn retry_after_secs(&self) -> u64 {
887        match self {
888            IotaError::ValidatorOverloadedRetryAfter { retry_after_secs } => *retry_after_secs,
889            _ => 0,
890        }
891    }
892}
893
894impl Ord for IotaError {
895    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
896        Ord::cmp(self.as_ref(), other.as_ref())
897    }
898}
899
900impl PartialOrd for IotaError {
901    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
902        Some(self.cmp(other))
903    }
904}
905
906type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
907
908pub type ExecutionErrorKind = ExecutionFailureStatus;
909
910#[derive(Debug)]
911pub struct ExecutionError {
912    inner: Box<ExecutionErrorInner>,
913}
914
915#[derive(Debug)]
916struct ExecutionErrorInner {
917    kind: ExecutionErrorKind,
918    source: Option<BoxError>,
919    command: Option<CommandIndex>,
920}
921
922impl ExecutionError {
923    pub fn new(kind: ExecutionErrorKind, source: Option<BoxError>) -> Self {
924        Self {
925            inner: Box::new(ExecutionErrorInner {
926                kind,
927                source,
928                command: None,
929            }),
930        }
931    }
932
933    pub fn new_with_source<E: Into<BoxError>>(kind: ExecutionErrorKind, source: E) -> Self {
934        Self::new(kind, Some(source.into()))
935    }
936
937    pub fn invariant_violation<E: Into<BoxError>>(source: E) -> Self {
938        Self::new_with_source(ExecutionFailureStatus::InvariantViolation, source)
939    }
940
941    pub fn with_command_index(mut self, command: CommandIndex) -> Self {
942        self.inner.command = Some(command);
943        self
944    }
945
946    pub fn from_kind(kind: ExecutionErrorKind) -> Self {
947        Self::new(kind, None)
948    }
949
950    pub fn kind(&self) -> &ExecutionErrorKind {
951        &self.inner.kind
952    }
953
954    pub fn command(&self) -> Option<CommandIndex> {
955        self.inner.command
956    }
957
958    pub fn source(&self) -> &Option<BoxError> {
959        &self.inner.source
960    }
961
962    pub fn to_execution_status(&self) -> (ExecutionFailureStatus, Option<CommandIndex>) {
963        (self.kind().clone(), self.command())
964    }
965}
966
967impl std::fmt::Display for ExecutionError {
968    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
969        write!(f, "ExecutionError: {:?}", self)
970    }
971}
972
973impl std::error::Error for ExecutionError {
974    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
975        self.inner.source.as_ref().map(|e| &**e as _)
976    }
977}
978
979impl From<ExecutionErrorKind> for ExecutionError {
980    fn from(kind: ExecutionErrorKind) -> Self {
981        Self::from_kind(kind)
982    }
983}
984
985pub fn command_argument_error(e: CommandArgumentError, arg_idx: usize) -> ExecutionError {
986    ExecutionError::from_kind(ExecutionErrorKind::command_argument_error(
987        e,
988        arg_idx as u16,
989    ))
990}