fuel_core_txpool/
error.rs

1use fuel_core_types::{
2    fuel_tx::{
3        Address,
4        BlobId,
5        ContractId,
6        TxId,
7        UtxoId,
8        Word,
9    },
10    fuel_types::Nonce,
11    fuel_vm::checked_transaction::CheckError,
12};
13
14use crate::{
15    pending_pool::MissingInput,
16    ports::WasmValidityError,
17};
18
19#[derive(Clone, Debug, derive_more::Display)]
20pub enum Error {
21    #[display(fmt = "Gas price not found for block height {_0}")]
22    GasPriceNotFound(String),
23    #[display(fmt = "Database error: {_0}")]
24    Database(String),
25    #[display(fmt = "Storage error: {_0}")]
26    Storage(String),
27    #[display(fmt = "Blacklisted error: {_0}")]
28    Blacklisted(BlacklistedError),
29    #[display(fmt = "Transaction collided: {_0}")]
30    Collided(CollisionReason),
31    #[display(fmt = "Transaction input validation failed: {_0}")]
32    InputValidation(InputValidationError),
33    #[display(fmt = "Transaction dependency error: {_0}")]
34    Dependency(DependencyError),
35    #[display(fmt = "Invalid transaction data: {_0:?}")]
36    ConsensusValidity(CheckError),
37    #[display(fmt = "Error with Wasm validity: {:?}", _0)]
38    WasmValidity(WasmValidityError),
39    #[display(fmt = "Mint transaction is not allowed")]
40    MintIsDisallowed,
41    #[display(fmt = "Pool limit is hit, try to increase gas_price")]
42    NotInsertedLimitHit,
43    #[display(fmt = "Transaction is removed: {_0}")]
44    Removed(RemovedReason),
45    #[display(fmt = "Transaction has been skipped during block insertion: {_0}")]
46    SkippedTransaction(String),
47    #[display(fmt = "Too much transactions are in queue to be inserted. Can't add more")]
48    TooManyQueuedTransactions,
49    #[display(fmt = "Unable send a request because service is closed")]
50    ServiceCommunicationFailed,
51    #[display(fmt = "Request failed to be sent because service queue is full")]
52    ServiceQueueFull,
53    #[display(fmt = "The provided max fee can't cover the transaction cost. \
54        The minimal gas price should be {minimal_gas_price:?}, \
55        while it is {max_gas_price_from_fee:?}")]
56    InsufficientMaxFee {
57        /// The max gas price from the fee.
58        max_gas_price_from_fee: Word,
59        /// The minimum gas price required by TxPool.
60        minimal_gas_price: Word,
61    },
62    #[display(fmt = "The message input {_0:#x} was already spent")]
63    MessageInputWasAlreadySpent(Nonce),
64    #[display(fmt = "The UTXO input {_0:#x} was already spent")]
65    UtxoInputWasAlreadySpent(UtxoId),
66}
67
68impl Error {
69    pub fn is_duplicate_tx(&self) -> bool {
70        matches!(
71            self,
72            Error::InputValidation(InputValidationError::DuplicateTxId(_))
73        )
74    }
75}
76
77#[derive(Clone, Debug, derive_more::Display)]
78pub enum RemovedReason {
79    #[display(
80        fmt = "Transaction was removed because it was less worth than a new one (id: {_0}) that has been inserted"
81    )]
82    LessWorth(TxId),
83    #[display(
84        fmt = "Transaction expired because it exceeded the configured time to live `tx-pool-ttl`."
85    )]
86    Ttl,
87}
88
89#[derive(Clone, Debug, derive_more::Display)]
90pub enum BlacklistedError {
91    #[display(fmt = "The UTXO `{_0}` is blacklisted")]
92    BlacklistedUTXO(UtxoId),
93    #[display(fmt = "The owner `{_0}` is blacklisted")]
94    BlacklistedOwner(Address),
95    #[display(fmt = "The contract `{_0}` is blacklisted")]
96    BlacklistedContract(ContractId),
97    #[display(fmt = "The message `{_0}` is blacklisted")]
98    BlacklistedMessage(Nonce),
99}
100
101#[derive(Clone, Debug, derive_more::Display)]
102pub enum DependencyError {
103    #[display(fmt = "Collision is also a dependency")]
104    NotInsertedCollisionIsDependency,
105    #[display(fmt = "Transaction chain dependency is already too big")]
106    NotInsertedChainDependencyTooBig,
107    #[display(fmt = "The dependent transaction creates a diamond problem, \
108    causing cycles in the dependency graph.")]
109    DependentTransactionIsADiamondDeath,
110    #[display(fmt = "The transaction depends on a blob transaction")]
111    NotInsertedDependentOnBlob,
112}
113
114#[derive(Clone, Debug)]
115pub(crate) enum InsertionErrorType {
116    Error(Error),
117    MissingInputs(Vec<MissingInput>),
118}
119
120impl From<Error> for InsertionErrorType {
121    fn from(e: Error) -> Self {
122        InsertionErrorType::Error(e)
123    }
124}
125
126impl From<InputValidationErrorType> for InsertionErrorType {
127    fn from(e: InputValidationErrorType) -> Self {
128        match e {
129            InputValidationErrorType::Inconsistency(e) => InsertionErrorType::Error(e),
130            InputValidationErrorType::MissingInputs(e) => {
131                InsertionErrorType::MissingInputs(e)
132            }
133        }
134    }
135}
136
137pub(crate) enum InputValidationErrorType {
138    Inconsistency(Error),
139    // Don't add a variant with this type in `InputValidationError` to not break public API
140    MissingInputs(Vec<MissingInput>),
141}
142
143#[derive(Clone, Debug, derive_more::Display)]
144pub enum InputValidationError {
145    #[display(fmt = "Input output mismatch. Coin owner is different from expected input")]
146    NotInsertedIoWrongOwner,
147    #[display(fmt = "Input output mismatch. Coin output does not match expected input")]
148    NotInsertedIoWrongAmount,
149    #[display(
150        fmt = "Input output mismatch. Coin output asset_id does not match expected inputs"
151    )]
152    NotInsertedIoWrongAssetId,
153    #[display(fmt = "Input message does not match the values from database")]
154    NotInsertedIoMessageMismatch,
155    #[display(fmt = "Input output mismatch. Expected coin but output is contract")]
156    NotInsertedIoContractOutput,
157    #[display(
158        fmt = "Message id {_0:#x} does not match any received message from the DA layer."
159    )]
160    NotInsertedInputMessageUnknown(Nonce),
161    #[display(fmt = "Input dependent on a Change or Variable output")]
162    NotInsertedInputDependentOnChangeOrVariable,
163    #[display(fmt = "UTXO input does not exist: {_0:#x}")]
164    NotInsertedInputContractDoesNotExist(ContractId),
165    #[display(fmt = "BlobId is already taken {_0:#x}")]
166    NotInsertedBlobIdAlreadyTaken(BlobId),
167    #[display(fmt = "Input coin does not match the values from database")]
168    NotInsertedIoCoinMismatch,
169    #[display(fmt = "Wrong number of outputs: {_0}")]
170    WrongOutputNumber(String),
171    #[display(fmt = "UTXO (id: {_0}) does not exist")]
172    UtxoNotFound(UtxoId),
173    #[display(fmt = "Max gas can't be 0")]
174    MaxGasZero,
175    #[display(fmt = "Transaction id already exists (id: {_0})")]
176    DuplicateTxId(TxId),
177}
178
179#[derive(Debug, Clone, derive_more::Display)]
180pub enum CollisionReason {
181    #[display(
182        fmt = "Transaction with the same UTXO (id: {_0}) already exists and is more worth it"
183    )]
184    Utxo(UtxoId),
185    #[display(
186        fmt = "Transaction that create the same contract (id: {_0}) already exists and is more worth it"
187    )]
188    ContractCreation(ContractId),
189    #[display(
190        fmt = "Transaction that use the same blob (id: {_0}) already exists and is more worth it"
191    )]
192    Blob(BlobId),
193    #[display(
194        fmt = "Transaction that use the same message (id: {_0}) already exists and is more worth it"
195    )]
196    Message(Nonce),
197    #[display(fmt = "This transaction have an unknown collision")]
198    Unknown,
199    #[display(
200        fmt = "This transaction have dependencies and is colliding with multiple transactions"
201    )]
202    MultipleCollisions,
203}
204
205impl From<CheckError> for Error {
206    fn from(e: CheckError) -> Self {
207        Error::ConsensusValidity(e)
208    }
209}