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