From 4e595e8e3c32b9eb73ed7da2e81a98c9bbcf74d8 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 4 Feb 2020 09:03:45 -0800 Subject: [PATCH] Facilitate printing program errors from BPF programs (#8109) --- programs/bpf/Cargo.lock | 10 +-- programs/bpf/rust/error_handling/src/lib.rs | 26 +++++++- sdk/src/program_error.rs | 68 +++++++++++++++++---- 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 2df278a720..65170cce8e 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2155,7 +2155,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-sdk 0.24.0", - "sys-info 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sys-info 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2163,7 +2163,7 @@ name = "solana-rayon-threadlimit" version = "0.24.0" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sys-info 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sys-info 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2194,7 +2194,7 @@ dependencies = [ "solana-stake-program 0.24.0", "solana-storage-program 0.24.0", "solana-vote-program 0.24.0", - "sys-info 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sys-info 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2398,7 +2398,7 @@ dependencies = [ [[package]] name = "sys-info" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3271,7 +3271,7 @@ dependencies = [ "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" -"checksum sys-info 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0079fe39cec2c8215e21b0bc4ccec9031004c160b88358f531b601e96b77f0df" +"checksum sys-info 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d4eff5474e55653c77b4470bdc2278c7e22f942d59658d0e8767d1c97e02a7b9" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" diff --git a/programs/bpf/rust/error_handling/src/lib.rs b/programs/bpf/rust/error_handling/src/lib.rs index 516ecc9c71..303b27d478 100644 --- a/programs/bpf/rust/error_handling/src/lib.rs +++ b/programs/bpf/rust/error_handling/src/lib.rs @@ -2,8 +2,14 @@ extern crate solana_sdk; use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + use solana_sdk::{ - account_info::AccountInfo, entrypoint, info, program_error::ProgramError, pubkey::Pubkey, + account_info::AccountInfo, + entrypoint, info, + instruction_processor_utils::DecodeError, + program_error::{PrintProgramError, ProgramError}, + pubkey::Pubkey, }; use thiserror::Error; @@ -20,6 +26,22 @@ impl From for ProgramError { ProgramError::CustomError(e as u32) } } +impl DecodeError for MyError { + fn type_of() -> &'static str { + "MyError" + } +} +impl PrintProgramError for MyError { + fn print(&self) + where + E: 'static + std::error::Error + DecodeError + PrintProgramError + FromPrimitive, + { + match self { + MyError::DefaultEnumStart => info!("Error: Default enum start"), + MyError::TheAnswer => info!("Error: The Answer"), + } + } +} entrypoint!(process_instruction); fn process_instruction( @@ -27,6 +49,8 @@ fn process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], ) -> Result<(), ProgramError> { + ProgramError::CustomError(42).print::(); + match instruction_data[0] { 1 => { info!("return success"); diff --git a/sdk/src/program_error.rs b/sdk/src/program_error.rs index 9891c22960..06b0281010 100644 --- a/sdk/src/program_error.rs +++ b/sdk/src/program_error.rs @@ -1,36 +1,78 @@ -use crate::instruction::InstructionError; -use num_traits::ToPrimitive; +use crate::{instruction::InstructionError, instruction_processor_utils::DecodeError}; +use num_traits::{FromPrimitive, ToPrimitive}; +use thiserror::Error; + +#[cfg(feature = "program")] +use crate::info; +#[cfg(not(feature = "program"))] +use log::info; /// Reasons the program may fail +#[derive(Clone, Debug, Deserialize, Eq, Error, PartialEq, Serialize)] pub enum ProgramError { /// Allows on-chain programs to implement program-specific error types and see them returned /// by the Solana runtime. A program-specific error may be any type that is represented as /// or serialized to a u32 integer. + #[error("Custom program error: {0}")] CustomError(u32), - /// The arguments provided to a program instruction where invalid + #[error("The arguments provided to a program instruction where invalid")] InvalidArgument, - /// An instruction's data contents was invalid + #[error("An instruction's data contents was invalid")] InvalidInstructionData, - /// An account's data contents was invalid + #[error("An account's data contents was invalid")] InvalidAccountData, - /// An account's data was too small + #[error("An account's data was too small")] AccountDataTooSmall, - /// An account's balance was too small to complete the instruction + #[error("An account's balance was too small to complete the instruction")] InsufficientFunds, - /// The account did not have the expected program id + #[error("The account did not have the expected program id")] IncorrectProgramId, - /// A signature was required but not found + #[error("A signature was required but not found")] MissingRequiredSignature, - /// An initialize instruction was sent to an account that has already been initialized. + #[error("An initialize instruction was sent to an account that has already been initialized")] AccountAlreadyInitialized, - /// An attempt to operate on an account that hasn't been initialized. + #[error("An attempt to operate on an account that hasn't been initialized")] UninitializedAccount, - /// The instruction expected additional account keys + #[error("The instruction expected additional account keys")] NotEnoughAccountKeys, - /// Failed to borrow a reference to account data, already borrowed + #[error("Failed to borrow a reference to account data, already borrowed")] AccountBorrowFailed, } +pub trait PrintProgramError { + fn print(&self) + where + E: 'static + std::error::Error + DecodeError + PrintProgramError + FromPrimitive; +} + +impl PrintProgramError for ProgramError { + fn print(&self) + where + E: 'static + std::error::Error + DecodeError + PrintProgramError + FromPrimitive, + { + match self { + ProgramError::CustomError(error) => { + if let Some(custom_error) = E::decode_custom_error_to_enum(*error) { + custom_error.print::(); + } else { + info!("Error: Unknown"); + } + } + ProgramError::InvalidArgument => info!("Error: InvalidArgument"), + ProgramError::InvalidInstructionData => info!("Error: InvalidInstructionData"), + ProgramError::InvalidAccountData => info!("Error: InvalidAccountData"), + ProgramError::AccountDataTooSmall => info!("Error: AccountDataTooSmall"), + ProgramError::InsufficientFunds => info!("Error: InsufficientFunds"), + ProgramError::IncorrectProgramId => info!("Error: IncorrectProgramId"), + ProgramError::MissingRequiredSignature => info!("Error: MissingRequiredSignature"), + ProgramError::AccountAlreadyInitialized => info!("Error: AccountAlreadyInitialized"), + ProgramError::UninitializedAccount => info!("Error: UninitializedAccount"), + ProgramError::NotEnoughAccountKeys => info!("Error: NotEnoughAccountKeys"), + ProgramError::AccountBorrowFailed => info!("Error: AccountBorrowFailed"), + } + } +} + /// Builtin return values occupy the upper 32 bits const BUILTIN_BIT_SHIFT: usize = 32; macro_rules! to_builtin {