Skip to main content

solana_transaction_error/
lib.rs

1#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#[cfg(feature = "serde")]
4use serde_derive::{Deserialize, Serialize};
5#[cfg(feature = "frozen-abi")]
6use solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample};
7use {core::fmt, solana_instruction_error::InstructionError, solana_sanitize::SanitizeError};
8
9pub type TransactionResult<T> = Result<T, TransactionError>;
10
11/// Reasons a transaction might be rejected.
12#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
15#[derive(Debug, PartialEq, Eq, Clone)]
16pub enum TransactionError {
17    /// An account is already being processed in another transaction in a way
18    /// that does not support parallelism
19    AccountInUse,
20
21    /// A `Pubkey` appears twice in the transaction's `account_keys`.  Instructions can reference
22    /// `Pubkey`s more than once but the message must contain a list with no duplicate keys
23    AccountLoadedTwice,
24
25    /// Attempt to debit an account but found no record of a prior credit.
26    AccountNotFound,
27
28    /// Attempt to load a program that does not exist
29    ProgramAccountNotFound,
30
31    /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction
32    InsufficientFundsForFee,
33
34    /// This account may not be used to pay transaction fees
35    InvalidAccountForFee,
36
37    /// The bank has seen this transaction before. This can occur under normal operation
38    /// when a UDP packet is duplicated, as a user error from a client not updating
39    /// its `recent_blockhash`, or as a double-spend attack.
40    AlreadyProcessed,
41
42    /// The bank has not seen the given `recent_blockhash` or the transaction is too old and
43    /// the `recent_blockhash` has been discarded.
44    BlockhashNotFound,
45
46    /// An error occurred while processing an instruction. The first element of the tuple
47    /// indicates the instruction index in which the error occurred.
48    InstructionError(u8, InstructionError),
49
50    /// Loader call chain is too deep
51    CallChainTooDeep,
52
53    /// Transaction requires a fee but has no signature present
54    MissingSignatureForFee,
55
56    /// Transaction contains an invalid account reference
57    InvalidAccountIndex,
58
59    /// Transaction did not pass signature verification
60    SignatureFailure,
61
62    /// This program may not be used for executing instructions
63    InvalidProgramForExecution,
64
65    /// Transaction failed to sanitize accounts offsets correctly
66    /// implies that account locks are not taken for this TX, and should
67    /// not be unlocked.
68    SanitizeFailure,
69
70    ClusterMaintenance,
71
72    /// Transaction processing left an account with an outstanding borrowed reference
73    AccountBorrowOutstanding,
74
75    /// Transaction would exceed max Block Cost Limit
76    WouldExceedMaxBlockCostLimit,
77
78    /// Transaction version is unsupported
79    UnsupportedVersion,
80
81    /// Transaction loads a writable account that cannot be written
82    InvalidWritableAccount,
83
84    /// Transaction would exceed max account limit within the block
85    WouldExceedMaxAccountCostLimit,
86
87    /// Transaction would exceed account data limit within the block
88    WouldExceedAccountDataBlockLimit,
89
90    /// Transaction locked too many accounts
91    TooManyAccountLocks,
92
93    /// Address lookup table not found
94    AddressLookupTableNotFound,
95
96    /// Attempted to lookup addresses from an account owned by the wrong program
97    InvalidAddressLookupTableOwner,
98
99    /// Attempted to lookup addresses from an invalid account
100    InvalidAddressLookupTableData,
101
102    /// Address table lookup uses an invalid index
103    InvalidAddressLookupTableIndex,
104
105    /// Transaction leaves an account with a lower balance than rent-exempt minimum
106    InvalidRentPayingAccount,
107
108    /// Transaction would exceed max Vote Cost Limit
109    WouldExceedMaxVoteCostLimit,
110
111    /// Transaction would exceed total account data limit
112    WouldExceedAccountDataTotalLimit,
113
114    /// Transaction contains a duplicate instruction that is not allowed
115    DuplicateInstruction(u8),
116
117    /// Transaction results in an account with insufficient funds for rent
118    InsufficientFundsForRent {
119        account_index: u8,
120    },
121
122    /// Transaction exceeded max loaded accounts data size cap
123    MaxLoadedAccountsDataSizeExceeded,
124
125    /// LoadedAccountsDataSizeLimit set for transaction must be greater than 0.
126    InvalidLoadedAccountsDataSizeLimit,
127
128    /// Sanitized transaction differed before/after feature activation. Needs to be resanitized.
129    ResanitizationNeeded,
130
131    /// Program execution is temporarily restricted on an account.
132    ProgramExecutionTemporarilyRestricted {
133        account_index: u8,
134    },
135
136    /// The total balance before the transaction does not equal the total balance after the transaction
137    UnbalancedTransaction,
138
139    /// Program cache hit max limit.
140    ProgramCacheHitMaxLimit,
141
142    /// Commit cancelled internally.
143    CommitCancelled,
144}
145
146impl core::error::Error for TransactionError {}
147
148impl fmt::Display for TransactionError {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        match self {
151            Self::AccountInUse
152             => f.write_str("Account in use"),
153            Self::AccountLoadedTwice
154             => f.write_str("Account loaded twice"),
155            Self::AccountNotFound
156             => f.write_str("Attempt to debit an account but found no record of a prior credit."),
157            Self::ProgramAccountNotFound
158             => f.write_str("Attempt to load a program that does not exist"),
159            Self::InsufficientFundsForFee
160             => f.write_str("Insufficient funds for fee"),
161            Self::InvalidAccountForFee
162             => f.write_str("This account may not be used to pay transaction fees"),
163            Self::AlreadyProcessed
164             => f.write_str("This transaction has already been processed"),
165            Self::BlockhashNotFound
166             => f.write_str("Blockhash not found"),
167            Self::InstructionError(idx, err) =>  write!(f, "Error processing Instruction {idx}: {err}"),
168            Self::CallChainTooDeep
169             => f.write_str("Loader call chain is too deep"),
170            Self::MissingSignatureForFee
171             => f.write_str("Transaction requires a fee but has no signature present"),
172            Self::InvalidAccountIndex
173             => f.write_str("Transaction contains an invalid account reference"),
174            Self::SignatureFailure
175             => f.write_str("Transaction did not pass signature verification"),
176            Self::InvalidProgramForExecution
177             => f.write_str("This program may not be used for executing instructions"),
178            Self::SanitizeFailure
179             => f.write_str("Transaction failed to sanitize accounts offsets correctly"),
180            Self::ClusterMaintenance
181             => f.write_str("Transactions are currently disabled due to cluster maintenance"),
182            Self::AccountBorrowOutstanding
183             => f.write_str("Transaction processing left an account with an outstanding borrowed reference"),
184            Self::WouldExceedMaxBlockCostLimit
185             => f.write_str("Transaction would exceed max Block Cost Limit"),
186            Self::UnsupportedVersion
187             => f.write_str("Transaction version is unsupported"),
188            Self::InvalidWritableAccount
189             => f.write_str("Transaction loads a writable account that cannot be written"),
190            Self::WouldExceedMaxAccountCostLimit
191             => f.write_str("Transaction would exceed max account limit within the block"),
192            Self::WouldExceedAccountDataBlockLimit
193             => f.write_str("Transaction would exceed account data limit within the block"),
194            Self::TooManyAccountLocks
195             => f.write_str("Transaction locked too many accounts"),
196            Self::AddressLookupTableNotFound
197             => f.write_str("Transaction loads an address table account that doesn't exist"),
198            Self::InvalidAddressLookupTableOwner
199             => f.write_str("Transaction loads an address table account with an invalid owner"),
200            Self::InvalidAddressLookupTableData
201             => f.write_str("Transaction loads an address table account with invalid data"),
202            Self::InvalidAddressLookupTableIndex
203             => f.write_str("Transaction address table lookup uses an invalid index"),
204            Self::InvalidRentPayingAccount
205             => f.write_str("Transaction leaves an account with a lower balance than rent-exempt minimum"),
206            Self::WouldExceedMaxVoteCostLimit
207             => f.write_str("Transaction would exceed max Vote Cost Limit"),
208            Self::WouldExceedAccountDataTotalLimit
209             => f.write_str("Transaction would exceed total account data limit"),
210            Self::DuplicateInstruction(idx) =>  write!(f, "Transaction contains a duplicate instruction ({idx}) that is not allowed"),
211            Self::InsufficientFundsForRent {
212                account_index
213            } =>  write!(f,"Transaction results in an account ({account_index}) with insufficient funds for rent"),
214            Self::MaxLoadedAccountsDataSizeExceeded
215             => f.write_str("Transaction exceeded max loaded accounts data size cap"),
216            Self::InvalidLoadedAccountsDataSizeLimit
217             => f.write_str("LoadedAccountsDataSizeLimit set for transaction must be greater than 0."),
218            Self::ResanitizationNeeded
219             => f.write_str("ResanitizationNeeded"),
220            Self::ProgramExecutionTemporarilyRestricted {
221                account_index
222            } =>  write!(f,"Execution of the program referenced by account at index {account_index} is temporarily restricted."),
223            Self::UnbalancedTransaction
224             => f.write_str("Sum of account balances before and after transaction do not match"),
225            Self::ProgramCacheHitMaxLimit
226             => f.write_str("Program cache hit max limit"),
227            Self::CommitCancelled
228             => f.write_str("CommitCancelled"),
229        }
230    }
231}
232
233impl From<SanitizeError> for TransactionError {
234    fn from(_: SanitizeError) -> Self {
235        Self::SanitizeFailure
236    }
237}
238
239#[cfg(not(target_os = "solana"))]
240impl From<SanitizeMessageError> for TransactionError {
241    fn from(err: SanitizeMessageError) -> Self {
242        match err {
243            SanitizeMessageError::AddressLoaderError(err) => Self::from(err),
244            _ => Self::SanitizeFailure,
245        }
246    }
247}
248
249#[cfg(not(target_os = "solana"))]
250#[derive(Debug, PartialEq, Eq, Clone)]
251pub enum AddressLoaderError {
252    /// Address loading from lookup tables is disabled
253    Disabled,
254
255    /// Failed to load slot hashes sysvar
256    SlotHashesSysvarNotFound,
257
258    /// Attempted to lookup addresses from a table that does not exist
259    LookupTableAccountNotFound,
260
261    /// Attempted to lookup addresses from an account owned by the wrong program
262    InvalidAccountOwner,
263
264    /// Attempted to lookup addresses from an invalid account
265    InvalidAccountData,
266
267    /// Address lookup contains an invalid index
268    InvalidLookupIndex,
269}
270
271#[cfg(not(target_os = "solana"))]
272impl core::error::Error for AddressLoaderError {}
273
274#[cfg(not(target_os = "solana"))]
275impl fmt::Display for AddressLoaderError {
276    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277        match self {
278            Self::Disabled => f.write_str("Address loading from lookup tables is disabled"),
279            Self::SlotHashesSysvarNotFound => f.write_str("Failed to load slot hashes sysvar"),
280            Self::LookupTableAccountNotFound => {
281                f.write_str("Attempted to lookup addresses from a table that does not exist")
282            }
283            Self::InvalidAccountOwner => f.write_str(
284                "Attempted to lookup addresses from an account owned by the wrong program",
285            ),
286            Self::InvalidAccountData => {
287                f.write_str("Attempted to lookup addresses from an invalid account")
288            }
289            Self::InvalidLookupIndex => f.write_str("Address lookup contains an invalid index"),
290        }
291    }
292}
293
294#[cfg(not(target_os = "solana"))]
295impl From<AddressLoaderError> for TransactionError {
296    fn from(err: AddressLoaderError) -> Self {
297        match err {
298            AddressLoaderError::Disabled => Self::UnsupportedVersion,
299            AddressLoaderError::SlotHashesSysvarNotFound => Self::AccountNotFound,
300            AddressLoaderError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound,
301            AddressLoaderError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner,
302            AddressLoaderError::InvalidAccountData => Self::InvalidAddressLookupTableData,
303            AddressLoaderError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex,
304        }
305    }
306}
307
308#[cfg(not(target_os = "solana"))]
309#[derive(PartialEq, Debug, Eq, Clone)]
310pub enum SanitizeMessageError {
311    IndexOutOfBounds,
312    ValueOutOfBounds,
313    InvalidValue,
314    AddressLoaderError(AddressLoaderError),
315}
316
317#[cfg(not(target_os = "solana"))]
318impl core::error::Error for SanitizeMessageError {
319    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
320        match self {
321            Self::IndexOutOfBounds => None,
322            Self::ValueOutOfBounds => None,
323            Self::InvalidValue => None,
324            Self::AddressLoaderError(e) => Some(e),
325        }
326    }
327}
328
329#[cfg(not(target_os = "solana"))]
330impl fmt::Display for SanitizeMessageError {
331    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
332        match self {
333            Self::IndexOutOfBounds => f.write_str("index out of bounds"),
334            Self::ValueOutOfBounds => f.write_str("value out of bounds"),
335            Self::InvalidValue => f.write_str("invalid value"),
336            Self::AddressLoaderError(e) => {
337                write!(f, "{e}")
338            }
339        }
340    }
341}
342#[cfg(not(target_os = "solana"))]
343impl From<AddressLoaderError> for SanitizeMessageError {
344    fn from(source: AddressLoaderError) -> Self {
345        SanitizeMessageError::AddressLoaderError(source)
346    }
347}
348
349#[cfg(not(target_os = "solana"))]
350impl From<SanitizeError> for SanitizeMessageError {
351    fn from(err: SanitizeError) -> Self {
352        match err {
353            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
354            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
355            SanitizeError::InvalidValue => Self::InvalidValue,
356        }
357    }
358}
359
360#[cfg(not(target_os = "solana"))]
361#[derive(Debug)]
362pub enum TransportError {
363    IoError(std::io::Error),
364    TransactionError(TransactionError),
365    Custom(String),
366}
367
368#[cfg(not(target_os = "solana"))]
369impl core::error::Error for TransportError {
370    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
371        match self {
372            TransportError::IoError(e) => Some(e),
373            TransportError::TransactionError(e) => Some(e),
374            TransportError::Custom(_) => None,
375        }
376    }
377}
378
379#[cfg(not(target_os = "solana"))]
380impl fmt::Display for TransportError {
381    fn fmt(&self, f: &mut fmt::Formatter) -> ::core::fmt::Result {
382        match self {
383            Self::IoError(e) => f.write_fmt(format_args!("transport io error: {e}")),
384            Self::TransactionError(e) => {
385                f.write_fmt(format_args!("transport transaction error: {e}"))
386            }
387            Self::Custom(s) => f.write_fmt(format_args!("transport custom error: {s}")),
388        }
389    }
390}
391
392#[cfg(not(target_os = "solana"))]
393impl From<std::io::Error> for TransportError {
394    fn from(e: std::io::Error) -> Self {
395        TransportError::IoError(e)
396    }
397}
398
399#[cfg(not(target_os = "solana"))]
400impl From<TransactionError> for TransportError {
401    fn from(e: TransactionError) -> Self {
402        TransportError::TransactionError(e)
403    }
404}
405
406#[cfg(not(target_os = "solana"))]
407impl TransportError {
408    pub fn unwrap(&self) -> TransactionError {
409        if let TransportError::TransactionError(err) = self {
410            err.clone()
411        } else {
412            panic!("unexpected transport error")
413        }
414    }
415}
416
417#[cfg(not(target_os = "solana"))]
418pub type TransportResult<T> = std::result::Result<T, TransportError>;