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#[cfg_attr(
19 feature = "frozen-abi",
20 derive(AbiExample, AbiEnumVisitor, StableAbi, StableAbiSample),
21 frozen_abi(
22 abi_digest = "2W49i91iPDXeXCEet6LbbpvoDJAm1HEDBfGskAmocXuu",
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#[cfg_attr(test, derive(strum_macros::EnumIter))]
30#[derive(Debug, PartialEq, Eq, Clone)]
31#[non_exhaustive]
32pub enum TransactionError {
33 AccountInUse,
36
37 AccountLoadedTwice,
40
41 AccountNotFound,
43
44 ProgramAccountNotFound,
46
47 InsufficientFundsForFee,
49
50 InvalidAccountForFee,
52
53 AlreadyProcessed,
57
58 BlockhashNotFound,
61
62 InstructionError(u8, InstructionError),
65
66 CallChainTooDeep,
68
69 MissingSignatureForFee,
71
72 InvalidAccountIndex,
74
75 SignatureFailure,
77
78 InvalidProgramForExecution,
80
81 SanitizeFailure,
85
86 ClusterMaintenance,
87
88 AccountBorrowOutstanding,
90
91 WouldExceedMaxBlockCostLimit,
93
94 UnsupportedVersion,
96
97 InvalidWritableAccount,
99
100 WouldExceedMaxAccountCostLimit,
102
103 WouldExceedAccountDataBlockLimit,
105
106 TooManyAccountLocks,
108
109 AddressLookupTableNotFound,
111
112 InvalidAddressLookupTableOwner,
114
115 InvalidAddressLookupTableData,
117
118 InvalidAddressLookupTableIndex,
120
121 InvalidRentPayingAccount,
123
124 WouldExceedMaxVoteCostLimit,
126
127 WouldExceedAccountDataTotalLimit,
129
130 DuplicateInstruction(u8),
132
133 InsufficientFundsForRent {
135 account_index: u8,
136 },
137
138 MaxLoadedAccountsDataSizeExceeded,
140
141 InvalidLoadedAccountsDataSizeLimit,
143
144 ResanitizationNeeded,
146
147 ProgramExecutionTemporarilyRestricted {
149 account_index: u8,
150 },
151
152 UnbalancedTransaction,
154
155 ProgramCacheHitMaxLimit,
157
158 CommitCancelled,
160
161 BailOut,
165}
166
167impl TransactionError {
168 pub const VARIANTS: [Self; 40] = [
169 Self::AccountInUse,
170 Self::AccountLoadedTwice,
171 Self::AccountNotFound,
172 Self::ProgramAccountNotFound,
173 Self::InsufficientFundsForFee,
174 Self::InvalidAccountForFee,
175 Self::AlreadyProcessed,
176 Self::BlockhashNotFound,
177 Self::InstructionError(0, InstructionError::Custom(0)),
178 Self::CallChainTooDeep,
179 Self::MissingSignatureForFee,
180 Self::InvalidAccountIndex,
181 Self::SignatureFailure,
182 Self::InvalidProgramForExecution,
183 Self::SanitizeFailure,
184 Self::ClusterMaintenance,
185 Self::AccountBorrowOutstanding,
186 Self::WouldExceedMaxBlockCostLimit,
187 Self::UnsupportedVersion,
188 Self::InvalidWritableAccount,
189 Self::WouldExceedMaxAccountCostLimit,
190 Self::WouldExceedAccountDataBlockLimit,
191 Self::TooManyAccountLocks,
192 Self::AddressLookupTableNotFound,
193 Self::InvalidAddressLookupTableOwner,
194 Self::InvalidAddressLookupTableData,
195 Self::InvalidAddressLookupTableIndex,
196 Self::InvalidRentPayingAccount,
197 Self::WouldExceedMaxVoteCostLimit,
198 Self::WouldExceedAccountDataTotalLimit,
199 Self::DuplicateInstruction(0),
200 Self::InsufficientFundsForRent { account_index: 0 },
201 Self::MaxLoadedAccountsDataSizeExceeded,
202 Self::InvalidLoadedAccountsDataSizeLimit,
203 Self::ResanitizationNeeded,
204 Self::ProgramExecutionTemporarilyRestricted { account_index: 0 },
205 Self::UnbalancedTransaction,
206 Self::ProgramCacheHitMaxLimit,
207 Self::CommitCancelled,
208 Self::BailOut,
209 ];
210}
211
212impl core::error::Error for TransactionError {}
213
214impl fmt::Display for TransactionError {
215 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216 match self {
217 Self::AccountInUse
218 => f.write_str("Account in use"),
219 Self::AccountLoadedTwice
220 => f.write_str("Account loaded twice"),
221 Self::AccountNotFound
222 => f.write_str("Attempt to debit an account but found no record of a prior credit."),
223 Self::ProgramAccountNotFound
224 => f.write_str("Attempt to load a program that does not exist"),
225 Self::InsufficientFundsForFee
226 => f.write_str("Insufficient funds for fee"),
227 Self::InvalidAccountForFee
228 => f.write_str("This account may not be used to pay transaction fees"),
229 Self::AlreadyProcessed
230 => f.write_str("This transaction has already been processed"),
231 Self::BlockhashNotFound
232 => f.write_str("Blockhash not found"),
233 Self::InstructionError(idx, err) => write!(f, "Error processing Instruction {idx}: {err}"),
234 Self::CallChainTooDeep
235 => f.write_str("Loader call chain is too deep"),
236 Self::MissingSignatureForFee
237 => f.write_str("Transaction requires a fee but has no signature present"),
238 Self::InvalidAccountIndex
239 => f.write_str("Transaction contains an invalid account reference"),
240 Self::SignatureFailure
241 => f.write_str("Transaction did not pass signature verification"),
242 Self::InvalidProgramForExecution
243 => f.write_str("This program may not be used for executing instructions"),
244 Self::SanitizeFailure
245 => f.write_str("Transaction failed to sanitize accounts offsets correctly"),
246 Self::ClusterMaintenance
247 => f.write_str("Transactions are currently disabled due to cluster maintenance"),
248 Self::AccountBorrowOutstanding
249 => f.write_str("Transaction processing left an account with an outstanding borrowed reference"),
250 Self::WouldExceedMaxBlockCostLimit
251 => f.write_str("Transaction would exceed max Block Cost Limit"),
252 Self::UnsupportedVersion
253 => f.write_str("Transaction version is unsupported"),
254 Self::InvalidWritableAccount
255 => f.write_str("Transaction loads a writable account that cannot be written"),
256 Self::WouldExceedMaxAccountCostLimit
257 => f.write_str("Transaction would exceed max account limit within the block"),
258 Self::WouldExceedAccountDataBlockLimit
259 => f.write_str("Transaction would exceed account data limit within the block"),
260 Self::TooManyAccountLocks
261 => f.write_str("Transaction locked too many accounts"),
262 Self::AddressLookupTableNotFound
263 => f.write_str("Transaction loads an address table account that doesn't exist"),
264 Self::InvalidAddressLookupTableOwner
265 => f.write_str("Transaction loads an address table account with an invalid owner"),
266 Self::InvalidAddressLookupTableData
267 => f.write_str("Transaction loads an address table account with invalid data"),
268 Self::InvalidAddressLookupTableIndex
269 => f.write_str("Transaction address table lookup uses an invalid index"),
270 Self::InvalidRentPayingAccount
271 => f.write_str("Transaction leaves an account with a lower balance than rent-exempt minimum"),
272 Self::WouldExceedMaxVoteCostLimit
273 => f.write_str("Transaction would exceed max Vote Cost Limit"),
274 Self::WouldExceedAccountDataTotalLimit
275 => f.write_str("Transaction would exceed total account data limit"),
276 Self::DuplicateInstruction(idx) => write!(f, "Transaction contains a duplicate instruction ({idx}) that is not allowed"),
277 Self::InsufficientFundsForRent {
278 account_index
279 } => write!(f,"Transaction results in an account ({account_index}) with insufficient funds for rent"),
280 Self::MaxLoadedAccountsDataSizeExceeded
281 => f.write_str("Transaction exceeded max loaded accounts data size cap"),
282 Self::InvalidLoadedAccountsDataSizeLimit
283 => f.write_str("LoadedAccountsDataSizeLimit set for transaction must be greater than 0."),
284 Self::ResanitizationNeeded
285 => f.write_str("ResanitizationNeeded"),
286 Self::ProgramExecutionTemporarilyRestricted {
287 account_index
288 } => write!(f,"Execution of the program referenced by account at index {account_index} is temporarily restricted."),
289 Self::UnbalancedTransaction
290 => f.write_str("Sum of account balances before and after transaction do not match"),
291 Self::ProgramCacheHitMaxLimit
292 => f.write_str("Program cache hit max limit"),
293 Self::CommitCancelled
294 => f.write_str("CommitCancelled"),
295 Self::BailOut
296 => f.write_str("BailOut"),
297 }
298 }
299}
300
301impl From<SanitizeError> for TransactionError {
302 fn from(_: SanitizeError) -> Self {
303 Self::SanitizeFailure
304 }
305}
306
307#[cfg(not(target_os = "solana"))]
308impl From<SanitizeMessageError> for TransactionError {
309 fn from(err: SanitizeMessageError) -> Self {
310 match err {
311 SanitizeMessageError::AddressLoaderError(err) => Self::from(err),
312 _ => Self::SanitizeFailure,
313 }
314 }
315}
316
317#[cfg(not(target_os = "solana"))]
318#[derive(Debug, PartialEq, Eq, Clone)]
319#[cfg_attr(test, derive(strum_macros::EnumIter, Default))]
320#[non_exhaustive]
321pub enum AddressLoaderError {
322 #[cfg_attr(test, default)]
324 Disabled,
325
326 SlotHashesSysvarNotFound,
328
329 LookupTableAccountNotFound,
331
332 InvalidAccountOwner,
334
335 InvalidAccountData,
337
338 InvalidLookupIndex,
340}
341
342#[cfg(not(target_os = "solana"))]
343impl AddressLoaderError {
344 pub const VARIANTS: [Self; 6] = [
345 Self::Disabled,
346 Self::SlotHashesSysvarNotFound,
347 Self::LookupTableAccountNotFound,
348 Self::InvalidAccountOwner,
349 Self::InvalidAccountData,
350 Self::InvalidLookupIndex,
351 ];
352}
353
354#[cfg(not(target_os = "solana"))]
355impl core::error::Error for AddressLoaderError {}
356
357#[cfg(not(target_os = "solana"))]
358impl fmt::Display for AddressLoaderError {
359 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360 match self {
361 Self::Disabled => f.write_str("Address loading from lookup tables is disabled"),
362 Self::SlotHashesSysvarNotFound => f.write_str("Failed to load slot hashes sysvar"),
363 Self::LookupTableAccountNotFound => {
364 f.write_str("Attempted to lookup addresses from a table that does not exist")
365 }
366 Self::InvalidAccountOwner => f.write_str(
367 "Attempted to lookup addresses from an account owned by the wrong program",
368 ),
369 Self::InvalidAccountData => {
370 f.write_str("Attempted to lookup addresses from an invalid account")
371 }
372 Self::InvalidLookupIndex => f.write_str("Address lookup contains an invalid index"),
373 }
374 }
375}
376
377#[cfg(not(target_os = "solana"))]
378impl From<AddressLoaderError> for TransactionError {
379 fn from(err: AddressLoaderError) -> Self {
380 match err {
381 AddressLoaderError::Disabled => Self::UnsupportedVersion,
382 AddressLoaderError::SlotHashesSysvarNotFound => Self::AccountNotFound,
383 AddressLoaderError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound,
384 AddressLoaderError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner,
385 AddressLoaderError::InvalidAccountData => Self::InvalidAddressLookupTableData,
386 AddressLoaderError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex,
387 }
388 }
389}
390
391#[cfg(not(target_os = "solana"))]
392#[derive(PartialEq, Debug, Eq, Clone)]
393#[cfg_attr(test, derive(strum_macros::EnumIter))]
394#[non_exhaustive]
395pub enum SanitizeMessageError {
396 IndexOutOfBounds,
397 ValueOutOfBounds,
398 InvalidValue,
399 AddressLoaderError(AddressLoaderError),
400}
401
402#[cfg(not(target_os = "solana"))]
403impl SanitizeMessageError {
404 pub const VARIANTS: [Self; 4] = [
405 Self::IndexOutOfBounds,
406 Self::ValueOutOfBounds,
407 Self::InvalidValue,
408 Self::AddressLoaderError(AddressLoaderError::Disabled),
409 ];
410}
411
412#[cfg(not(target_os = "solana"))]
413impl core::error::Error for SanitizeMessageError {
414 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
415 match self {
416 Self::IndexOutOfBounds => None,
417 Self::ValueOutOfBounds => None,
418 Self::InvalidValue => None,
419 Self::AddressLoaderError(e) => Some(e),
420 }
421 }
422}
423
424#[cfg(not(target_os = "solana"))]
425impl fmt::Display for SanitizeMessageError {
426 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
427 match self {
428 Self::IndexOutOfBounds => f.write_str("index out of bounds"),
429 Self::ValueOutOfBounds => f.write_str("value out of bounds"),
430 Self::InvalidValue => f.write_str("invalid value"),
431 Self::AddressLoaderError(e) => {
432 write!(f, "{e}")
433 }
434 }
435 }
436}
437#[cfg(not(target_os = "solana"))]
438impl From<AddressLoaderError> for SanitizeMessageError {
439 fn from(source: AddressLoaderError) -> Self {
440 SanitizeMessageError::AddressLoaderError(source)
441 }
442}
443
444#[cfg(not(target_os = "solana"))]
445impl From<SanitizeError> for SanitizeMessageError {
446 fn from(err: SanitizeError) -> Self {
447 match err {
448 SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
449 SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
450 SanitizeError::InvalidValue => Self::InvalidValue,
451 }
452 }
453}
454
455#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
456#[derive(Debug)]
457pub enum TransportError {
458 IoError(std::io::Error),
459 TransactionError(TransactionError),
460 Custom(std::string::String),
461}
462
463#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
464impl core::error::Error for TransportError {
465 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
466 match self {
467 TransportError::IoError(e) => Some(e),
468 TransportError::TransactionError(e) => Some(e),
469 TransportError::Custom(_) => None,
470 }
471 }
472}
473
474#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
475impl fmt::Display for TransportError {
476 fn fmt(&self, f: &mut fmt::Formatter) -> ::core::fmt::Result {
477 match self {
478 Self::IoError(e) => f.write_fmt(format_args!("transport io error: {e}")),
479 Self::TransactionError(e) => {
480 f.write_fmt(format_args!("transport transaction error: {e}"))
481 }
482 Self::Custom(s) => f.write_fmt(format_args!("transport custom error: {s}")),
483 }
484 }
485}
486
487#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
488impl From<std::io::Error> for TransportError {
489 fn from(e: std::io::Error) -> Self {
490 TransportError::IoError(e)
491 }
492}
493
494#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
495impl From<TransactionError> for TransportError {
496 fn from(e: TransactionError) -> Self {
497 TransportError::TransactionError(e)
498 }
499}
500
501#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
502impl TransportError {
503 pub fn unwrap(&self) -> TransactionError {
504 if let TransportError::TransactionError(err) = self {
505 err.clone()
506 } else {
507 panic!("unexpected transport error")
508 }
509 }
510}
511
512#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
513pub type TransportResult<T> = std::result::Result<T, TransportError>;
514
515#[cfg(test)]
516mod tests {
517 use {
518 super::{AddressLoaderError, SanitizeMessageError, TransactionError},
519 strum::IntoEnumIterator,
520 };
521
522 #[test]
523 fn test_transaction_error_variants_exhaustive() {
524 for variant in TransactionError::iter() {
525 assert!(TransactionError::VARIANTS.contains(&variant));
526 }
527 }
528
529 #[test]
530 fn test_address_loader_error_variants_exhaustive() {
531 for variant in AddressLoaderError::iter() {
532 assert!(AddressLoaderError::VARIANTS.contains(&variant));
533 }
534 }
535
536 #[test]
537 fn test_sanitize_message_error_variants_exhaustive() {
538 for variant in SanitizeMessageError::iter() {
539 assert!(SanitizeMessageError::VARIANTS.contains(&variant));
540 }
541 }
542}