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