From dd276138c21dc113d219ace2b38991088c155118 Mon Sep 17 00:00:00 2001 From: Jack May Date: Thu, 30 Jan 2020 09:47:22 -0800 Subject: [PATCH] Add support for idiomatic error handling to BPF instruction processors (#7968) --- programs/bpf/Cargo.lock | 22 +++ programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 3 +- programs/bpf/c/src/bench_alu/test_bench_alu.c | 2 +- .../bpf/c/src/dup_accounts/dup_accounts.c | 69 +++++----- .../bpf/c/src/error_handling/error_handling.c | 37 +++++ programs/bpf/c/src/move_funds/move_funds.c | 14 +- programs/bpf/c/src/noop++/noop++.cc | 2 +- programs/bpf/c/src/noop/noop.c | 7 +- programs/bpf/rust/dup_accounts/src/lib.rs | 10 +- programs/bpf/rust/error_handling/Cargo.toml | 29 ++++ programs/bpf/rust/error_handling/Xargo.toml | 2 + programs/bpf/rust/error_handling/src/lib.rs | 60 ++++++++ programs/bpf/rust/external_spend/src/lib.rs | 8 +- programs/bpf/rust/noop/src/lib.rs | 8 +- programs/bpf/rust/sysval/src/lib.rs | 9 +- programs/bpf/tests/programs.rs | 122 ++++++++++++++++- programs/bpf_loader/src/lib.rs | 20 ++- programs/bpf_loader/test_elfs/noop.so | Bin 54200 -> 54488 bytes sdk/bpf/c/inc/solana_sdk.h | 30 ++++ sdk/src/entrypoint.rs | 20 +-- sdk/src/instruction.rs | 5 + sdk/src/instruction_processor_utils.rs | 13 +- sdk/src/lib.rs | 1 + sdk/src/program_error.rs | 129 ++++++++++++++++++ 25 files changed, 515 insertions(+), 108 deletions(-) create mode 100644 programs/bpf/c/src/error_handling/error_handling.c create mode 100644 programs/bpf/rust/error_handling/Cargo.toml create mode 100644 programs/bpf/rust/error_handling/Xargo.toml create mode 100644 programs/bpf/rust/error_handling/src/lib.rs create mode 100644 sdk/src/program_error.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 1d49764ea7..2df278a720 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1198,6 +1198,16 @@ dependencies = [ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-derive" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-derive" version = "0.3.0" @@ -1995,6 +2005,17 @@ dependencies = [ "solana-sdk-bpf-test 0.24.0", ] +[[package]] +name = "solana-bpf-rust-error-handling" +version = "0.24.0" +dependencies = [ + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-sdk 0.24.0", + "solana-sdk-bpf-test 0.24.0", + "thiserror 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "solana-bpf-rust-external-spend" version = "0.24.0" @@ -3156,6 +3177,7 @@ dependencies = [ "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" "checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 06afd12e6f..e6a0c88da4 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -38,6 +38,7 @@ members = [ "rust/alloc", "rust/dep_crate", "rust/dup_accounts", + "rust/error_handling", "rust/external_spend", "rust/iter", "rust/many_args", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 3f90938ad6..8b48fbad58 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -69,9 +69,10 @@ fn main() { "alloc", "dep_crate", "dup_accounts", + "error_handling", + "external_spend", "iter", "many_args", - "external_spend", "noop", "panic", "param_passing", diff --git a/programs/bpf/c/src/bench_alu/test_bench_alu.c b/programs/bpf/c/src/bench_alu/test_bench_alu.c index 7219d977e8..6f36bd27e9 100644 --- a/programs/bpf/c/src/bench_alu/test_bench_alu.c +++ b/programs/bpf/c/src/bench_alu/test_bench_alu.c @@ -4,7 +4,7 @@ Test(bench_alu, sanity) { uint64_t input[] = {500, 0}; - cr_assert_eq(entrypoint((uint8_t *) input), 0); + cr_assert_eq(entrypoint((uint8_t *) input), SUCCESS); cr_assert_eq(input[0], 500); cr_assert_eq(input[1], 5); diff --git a/programs/bpf/c/src/dup_accounts/dup_accounts.c b/programs/bpf/c/src/dup_accounts/dup_accounts.c index cce0a7195b..4fabcb4866 100644 --- a/programs/bpf/c/src/dup_accounts/dup_accounts.c +++ b/programs/bpf/c/src/dup_accounts/dup_accounts.c @@ -9,49 +9,46 @@ */ extern uint32_t entrypoint(const uint8_t *input) { - #define FAILURE 1 - #define INVALID_INPUT 2 - SolKeyedAccount ka[4]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_INPUT; + return INVALID_ARGUMENT; } switch (params.data[0]) { - case(1): - sol_log("modify first account userdata"); - ka[2].userdata[0] = 1; - break; - case(2): - sol_log("modify first account userdata"); - ka[3].userdata[0] = 2; - break; - case(3): - sol_log("modify both account userdata"); - ka[2].userdata[0] += 1; - ka[3].userdata[0] += 2; - break; - case(4): - sol_log("modify first account lamports"); - *ka[1].lamports -= 1; - *ka[2].lamports += 1; - break; - case(5): - sol_log("modify first account lamports"); - *ka[1].lamports -= 2; - *ka[3].lamports += 2; - break; - case(6): - sol_log("modify both account lamports"); - *ka[1].lamports -= 3; - *ka[2].lamports += 1; - *ka[3].lamports += 2; - break; - default: - sol_log("Unrecognized command"); - return FAILURE; + case(1): + sol_log("modify first account userdata"); + ka[2].userdata[0] = 1; + break; + case(2): + sol_log("modify first account userdata"); + ka[3].userdata[0] = 2; + break; + case(3): + sol_log("modify both account userdata"); + ka[2].userdata[0] += 1; + ka[3].userdata[0] += 2; + break; + case(4): + sol_log("modify first account lamports"); + *ka[1].lamports -= 1; + *ka[2].lamports += 1; + break; + case(5): + sol_log("modify first account lamports"); + *ka[1].lamports -= 2; + *ka[3].lamports += 2; + break; + case(6): + sol_log("modify both account lamports"); + *ka[1].lamports -= 3; + *ka[2].lamports += 1; + *ka[3].lamports += 2; + break; + default: + sol_log("Unrecognized command"); + return INVALID_INSTRUCTION_DATA; } return SUCCESS; } diff --git a/programs/bpf/c/src/error_handling/error_handling.c b/programs/bpf/c/src/error_handling/error_handling.c new file mode 100644 index 0000000000..ce131eda88 --- /dev/null +++ b/programs/bpf/c/src/error_handling/error_handling.c @@ -0,0 +1,37 @@ +/** + * @brief Example C-based BPF program that exercises duplicate keyed ka + * passed to it + */ +#include + +/** + * Custom error for when input serialization fails + */ + +extern uint32_t entrypoint(const uint8_t *input) { + SolKeyedAccount ka[4]; + SolParameters params = (SolParameters) { .ka = ka }; + + if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { + return INVALID_ARGUMENT; + } + + switch (params.data[0]) { + case(1): + sol_log("return success"); + return SUCCESS; + case(2): + sol_log("return a builtin"); + return INVALID_ACCOUNT_DATA; + case(3): + sol_log("return custom error"); + return 42; + case(4): + sol_log("return error that conflicts with success"); + return 0x40000000; + default: + sol_log("Unrecognized command"); + return INVALID_INSTRUCTION_DATA; + } + return SUCCESS; +} diff --git a/programs/bpf/c/src/move_funds/move_funds.c b/programs/bpf/c/src/move_funds/move_funds.c index 4201f770b3..d38325ba03 100644 --- a/programs/bpf/c/src/move_funds/move_funds.c +++ b/programs/bpf/c/src/move_funds/move_funds.c @@ -11,27 +11,17 @@ */ #define NUM_KA 3 -/** - * Custom error for when input serialization fails - */ -#define INVALID_INPUT 1 - -/** - * Custom error for when transaction is not signed properly - */ -#define NOT_SIGNED 2 - extern uint32_t entrypoint(const uint8_t *input) { SolKeyedAccount ka[NUM_KA]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_INPUT; + return INVALID_ARGUMENT; } if (!params.ka[0].is_signer) { sol_log("Transaction not signed by key 0"); - return NOT_SIGNED; + return MISSING_REQUIRED_SIGNATURES; } int64_t lamports = *(int64_t *)params.data; diff --git a/programs/bpf/c/src/noop++/noop++.cc b/programs/bpf/c/src/noop++/noop++.cc index 0f62db39da..2ad78b4e49 100644 --- a/programs/bpf/c/src/noop++/noop++.cc +++ b/programs/bpf/c/src/noop++/noop++.cc @@ -16,7 +16,7 @@ extern uint32_t entrypoint(const uint8_t *input) { sol_log(__FILE__); if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_INPUT; + return INVALID_ARGUMENT; } // Log the provided input parameters. In the case of the no-op diff --git a/programs/bpf/c/src/noop/noop.c b/programs/bpf/c/src/noop/noop.c index 1f03047f1f..31985ed4bc 100644 --- a/programs/bpf/c/src/noop/noop.c +++ b/programs/bpf/c/src/noop/noop.c @@ -4,11 +4,6 @@ */ #include -/** - * Custom error for when input serialization fails - */ -#define INVALID_INPUT 1 - extern uint32_t entrypoint(const uint8_t *input) { SolKeyedAccount ka[1]; SolParameters params = (SolParameters) { .ka = ka }; @@ -16,7 +11,7 @@ extern uint32_t entrypoint(const uint8_t *input) { sol_log(__FILE__); if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_INPUT; + return INVALID_ARGUMENT; } // Log the provided input parameters. In the case of the no-op diff --git a/programs/bpf/rust/dup_accounts/src/lib.rs b/programs/bpf/rust/dup_accounts/src/lib.rs index 744a73afbd..c51384e7f8 100644 --- a/programs/bpf/rust/dup_accounts/src/lib.rs +++ b/programs/bpf/rust/dup_accounts/src/lib.rs @@ -2,7 +2,7 @@ extern crate solana_sdk; use solana_sdk::{ - account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, info, pubkey::Pubkey, + account_info::AccountInfo, entrypoint, info, program_error::ProgramError, pubkey::Pubkey, }; entrypoint!(process_instruction); @@ -10,9 +10,7 @@ fn process_instruction( _program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], -) -> u32 { - const FAILURE: u32 = 1; - +) -> Result<(), ProgramError> { match instruction_data[0] { 1 => { info!("modify first account data"); @@ -45,8 +43,8 @@ fn process_instruction( } _ => { info!("Unrecognized command"); - return FAILURE; + return Err(ProgramError::InvalidArgument); } } - SUCCESS + Ok(()) } diff --git a/programs/bpf/rust/error_handling/Cargo.toml b/programs/bpf/rust/error_handling/Cargo.toml new file mode 100644 index 0000000000..8ddbdb4181 --- /dev/null +++ b/programs/bpf/rust/error_handling/Cargo.toml @@ -0,0 +1,29 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-error-handling" +version = "0.24.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +num-derive = "0.2" +num-traits = "0.2" +solana-sdk = { path = "../../../../sdk/", version = "0.24.0", default-features = false } +thiserror = "1.0" + +[dev_dependencies] +solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "0.24.0" } + +[features] +program = ["solana-sdk/program"] +default = ["program"] + +[lib] +name = "solana_bpf_rust_error_handling" +crate-type = ["cdylib"] diff --git a/programs/bpf/rust/error_handling/Xargo.toml b/programs/bpf/rust/error_handling/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/error_handling/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/error_handling/src/lib.rs b/programs/bpf/rust/error_handling/src/lib.rs new file mode 100644 index 0000000000..31ea3e4af6 --- /dev/null +++ b/programs/bpf/rust/error_handling/src/lib.rs @@ -0,0 +1,60 @@ +//! @brief Example Rust-based BPF program that exercises error handling + +extern crate solana_sdk; +use num_derive::FromPrimitive; +use solana_sdk::{ + account_info::AccountInfo, entrypoint, info, program_error::ProgramError, pubkey::Pubkey, +}; +use thiserror::Error; + +/// Custom program errors +#[derive(Error, Debug, Clone, PartialEq, FromPrimitive)] +// Clippy compains about 0x8000_002d, but we don't care about C compatibility here +#[allow(clippy::enum_clike_unportable_variant)] +pub enum MyError { + #[error("The Answer")] + TheAnswer = 42, + #[error("Conflicting with success")] + ConflictingSuccess = 0, + #[error("Conflicting with builtin")] + ConflictingBuiltin = 0x8000_002d, +} +impl From for ProgramError { + fn from(e: MyError) -> Self { + ProgramError::CustomError(e as u32) + } +} + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + match instruction_data[0] { + 1 => { + info!("return success"); + Ok(()) + } + 2 => { + info!("return a builtin"); + Err(ProgramError::AccountBorrowFailed) + } + 3 => { + info!("return custom error"); + Err(MyError::TheAnswer.into()) + } + 4 => { + info!("return error that conflicts with success"); + Err(MyError::ConflictingSuccess.into()) + } + 5 => { + info!("return error that conflicts with builtin"); + Err(MyError::ConflictingBuiltin.into()) + } + _ => { + info!("Unrecognized command"); + Err(ProgramError::InvalidInstructionData) + } + } +} diff --git a/programs/bpf/rust/external_spend/src/lib.rs b/programs/bpf/rust/external_spend/src/lib.rs index 73bbf5d491..a77098aaa5 100644 --- a/programs/bpf/rust/external_spend/src/lib.rs +++ b/programs/bpf/rust/external_spend/src/lib.rs @@ -1,18 +1,20 @@ //! @brief Example Rust-based BPF program that moves a lamport from one account to another extern crate solana_sdk; -use solana_sdk::{account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, pubkey::Pubkey}; +use solana_sdk::{ + account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, +}; entrypoint!(process_instruction); fn process_instruction( _program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], -) -> u32 { +) -> Result<(), ProgramError> { // account 0 is the mint and not owned by this program, any debit of its lamports // should result in a failed program execution. Test to ensure that this debit // is seen by the runtime and fails as expected **accounts[0].lamports.borrow_mut() -= 1; - SUCCESS + Ok(()) } diff --git a/programs/bpf/rust/noop/src/lib.rs b/programs/bpf/rust/noop/src/lib.rs index c5898e80ae..f3bec99608 100644 --- a/programs/bpf/rust/noop/src/lib.rs +++ b/programs/bpf/rust/noop/src/lib.rs @@ -3,8 +3,10 @@ #![allow(unreachable_code)] extern crate solana_sdk; + use solana_sdk::{ - account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, info, log::*, pubkey::Pubkey, + account_info::AccountInfo, entrypoint, info, log::*, program_error::ProgramError, + pubkey::Pubkey, }; #[derive(Debug, PartialEq)] @@ -24,7 +26,7 @@ fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], -) -> u32 { +) -> Result<(), ProgramError> { info!("Program identifier:"); program_id.log(); @@ -58,7 +60,7 @@ fn process_instruction( panic!(); } - SUCCESS + Ok(()) } #[cfg(test)] diff --git a/programs/bpf/rust/sysval/src/lib.rs b/programs/bpf/rust/sysval/src/lib.rs index 57c8802261..d57429b1c7 100644 --- a/programs/bpf/rust/sysval/src/lib.rs +++ b/programs/bpf/rust/sysval/src/lib.rs @@ -4,10 +4,9 @@ extern crate solana_sdk; use solana_sdk::{ account_info::AccountInfo, clock::{get_segment_from_slot, DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT}, - entrypoint, - entrypoint::SUCCESS, - info, + entrypoint, info, log::Log, + program_error::ProgramError, pubkey::Pubkey, rent, sysvar::{ @@ -21,7 +20,7 @@ fn process_instruction( _program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], -) -> u32 { +) -> Result<(), ProgramError> { // Clock info!("Clock identifier:"); sysvar::clock::id().log(); @@ -66,5 +65,5 @@ fn process_instruction( (0, true) ); - SUCCESS + Ok(()) } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 3676f204f8..775b606eae 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -31,9 +31,10 @@ mod bpf { account::Account, bpf_loader, client::SyncClient, - instruction::{AccountMeta, Instruction}, + instruction::{AccountMeta, Instruction, InstructionError}, pubkey::Pubkey, signature::KeypairUtil, + transaction::TransactionError, }; use std::{io::Read, sync::Arc}; @@ -152,6 +153,59 @@ mod bpf { assert!(result.is_ok()); assert_eq!(lamports, 13); } + + #[test] + fn test_program_bpf_c_error_handling() { + solana_logger::setup(); + + let filename = create_bpf_path("error_handling"); + let mut file = File::open(filename).unwrap(); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + + let bank = Bank::new(&genesis_config); + let bank_client = BankClient::new(bank); + let program_id = load_program(&bank_client, &mint_keypair, &bpf_loader::id(), elf); + let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; + + let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidAccountData) + ); + + let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::CustomError(42)) + ); + + let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::ConflictingError(0)) + ); + + let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + ); + } } #[cfg(feature = "bpf_rust")] @@ -162,10 +216,11 @@ mod bpf { bpf_loader, client::SyncClient, clock::DEFAULT_SLOTS_PER_EPOCH, - instruction::{AccountMeta, Instruction}, + instruction::{AccountMeta, Instruction, InstructionError}, pubkey::Pubkey, signature::{Keypair, KeypairUtil}, sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, + transaction::TransactionError, }; use std::io::Read; use std::sync::Arc; @@ -300,5 +355,68 @@ mod bpf { assert!(result.is_ok()); assert_eq!(lamports, 13); } + + #[test] + fn test_program_bpf_rust_error_handling() { + solana_logger::setup(); + + let filename = create_bpf_path("solana_bpf_rust_error_handling"); + let mut file = File::open(filename).unwrap(); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + + let bank = Bank::new(&genesis_config); + let bank_client = BankClient::new(bank); + let program_id = load_program(&bank_client, &mint_keypair, &bpf_loader::id(), elf); + let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; + + let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed) + ); + + let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::CustomError(42)) + ); + + let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::ConflictingError(0)) + ); + + let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::ConflictingError(0x8000_002d) + ) + ); + + let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + ); + } } } diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index d989540298..1196b08ec7 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -15,7 +15,6 @@ use solana_sdk::{ sysvar::rent, }; use std::{ - convert::TryFrom, io::{prelude::*, Error}, mem, }; @@ -145,18 +144,15 @@ pub fn process_instruction( info!("Call BPF program"); match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) { - Ok(status) => match u32::try_from(status) { - Ok(status) => { - if status > 0 { - warn!("BPF program failed: {}", status); - return Err(InstructionError::CustomError(status)); - } + Ok(status) => { + // ignore upper 32bits if any, programs only return lower 32bits + let status = status as u32; + if status != 0 { + let error: InstructionError = status.into(); + warn!("BPF program failed: {:?}", error); + return Err(error); } - Err(e) => { - warn!("BPF VM encountered invalid status: {}", e); - return Err(InstructionError::GenericError); - } - }, + } Err(e) => { warn!("BPF VM failed to run program: {}", e); return Err(InstructionError::GenericError); diff --git a/programs/bpf_loader/test_elfs/noop.so b/programs/bpf_loader/test_elfs/noop.so index 6081fb92521c601a1846b1a3e143eae581f1db0e..6790a77e77d3eecc68bd56985bff92d6b2a0a0cc 100755 GIT binary patch delta 8980 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 delta 8745 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 diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 4b107d8e0a..f4851a6d26 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -56,6 +56,36 @@ static_assert(sizeof(uint64_t) == 8); */ #define SUCCESS 0 +/** + * Builtin program error return values have the 31st bit set. Programs + * may define their own values but their 31st and 30th bit must be unset + * to avoid conflicting with the builtin errors + */ +#define BUILTIN_ERROR_START 0x80000000 + +/** The arguments provided to a program instruction where invalid */ +#define INVALID_ARGUMENT (BUILTIN_ERROR_START + 0) +/** An instruction's data contents was invalid */ +#define INVALID_INSTRUCTION_DATA (BUILTIN_ERROR_START + 1) +/** An account's data contents was invalid */ +#define INVALID_ACCOUNT_DATA (BUILTIN_ERROR_START + 2) +/** An account's data was too small */ +#define ACCOUNT_DATA_TOO_SMALL (BUILTIN_ERROR_START + 3) +/** An account's balance was too small to complete the instruction */ +#define INSUFFICIENT_FUNDS (BUILTIN_ERROR_START + 4) +/** The account did not have the expected program id */ +#define INCORRECT_PROGRAM_ID (BUILTIN_ERROR_START + 5) +/** A signature was required but not found */ +#define MISSING_REQUIRED_SIGNATURES (BUILTIN_ERROR_START + 6) +/** An initialize instruction was sent to an account that has already been initialized */ +#define ACCOUNT_ALREADY_INITIALIZED (BUILTIN_ERROR_START + 7) +/** An attempt to operate on an account that hasn't been initialized */ +#define UNINITIALIZED_ACCOUNT (BUILTIN_ERROR_START + 8) +/** The instruction expected additional account keys */ +#define NOT_ENOUGH_ACCOUNT_KEYS (BUILTIN_ERROR_START + 9) +/** Note: Not applicable to program written in C */ +#define ACCOUNT_BORROW_FAILED (BUILTIN_ERROR_START + 10) + /** * Boolean type */ diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 74e5614115..4b0962cc3f 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -1,9 +1,7 @@ //! @brief Solana Rust-based BPF program entry point and its parameter types -#![cfg(feature = "program")] - extern crate alloc; -use crate::{account_info::AccountInfo, pubkey::Pubkey}; +use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; use alloc::vec::Vec; use std::{ cell::RefCell, @@ -17,8 +15,11 @@ use std::{ /// program_id: Program ID of the currently executing program /// accounts: Accounts passed as part of the instruction /// instruction_data: Instruction data -pub type ProcessInstruction = - fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> u32; +pub type ProcessInstruction = fn( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError>; /// Programs indicate success with a return value of 0 pub const SUCCESS: u32 = 0; @@ -35,10 +36,11 @@ macro_rules! entrypoint { /// # Safety #[no_mangle] pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u32 { - unsafe { - let (program_id, accounts, instruction_data) = - $crate::entrypoint::deserialize(input); - $process_instruction(&program_id, &accounts, &instruction_data) + let (program_id, accounts, instruction_data) = + unsafe { $crate::entrypoint::deserialize(input) }; + match $process_instruction(&program_id, &accounts, &instruction_data) { + Ok(()) => $crate::entrypoint::SUCCESS, + Err(error) => error.into(), } } }; diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index f995e842eb..2b7311016b 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -93,6 +93,11 @@ pub enum InstructionError { /// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and /// so is not used for now. CustomError(u32), + + /// Like CustomError but the return value from the program conflicted with + /// a builtin error. The value held by this variant is the u32 error code + /// returned by the program but with the 30th bit cleared. + ConflictingError(u32), } impl InstructionError { diff --git a/sdk/src/instruction_processor_utils.rs b/sdk/src/instruction_processor_utils.rs index 05ae3d3415..a76af5ea1b 100644 --- a/sdk/src/instruction_processor_utils.rs +++ b/sdk/src/instruction_processor_utils.rs @@ -1,7 +1,7 @@ use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey}; -use num_traits::{FromPrimitive, ToPrimitive}; +use num_traits::FromPrimitive; -// Native program ENTRYPOINT prototype +// Prototype of a native program entry point pub type Entrypoint = unsafe extern "C" fn( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], @@ -99,15 +99,6 @@ macro_rules! declare_program( ) ); -impl From for InstructionError -where - T: ToPrimitive, -{ - fn from(error: T) -> Self { - InstructionError::CustomError(error.to_u32().unwrap_or(0xbad_c0de)) - } -} - /// Return the next KeyedAccount or a NotEnoughAccountKeys instruction error pub fn next_keyed_account(iter: &mut I) -> Result { iter.next().ok_or(InstructionError::NotEnoughAccountKeys) diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 79980b6f9e..88804f4764 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -59,6 +59,7 @@ pub use solana_sdk_macro::declare_id; pub mod account_info; pub mod entrypoint; pub mod log; +pub mod program_error; // Modules not usable by on-chain programs #[cfg(not(feature = "program"))] diff --git a/sdk/src/program_error.rs b/sdk/src/program_error.rs new file mode 100644 index 0000000000..122aeaeb0f --- /dev/null +++ b/sdk/src/program_error.rs @@ -0,0 +1,129 @@ +use crate::instruction::InstructionError; +use num_traits::ToPrimitive; + +/// Reasons the program may fail +pub enum ProgramError { + /// CustomError allows programs to implement program-specific error types and see + /// them returned by the Solana runtime. A CustomError may be any type that is represented + /// as or serialized to a u32 integer. + /// + /// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and + /// so is not used for now. + CustomError(u32), + /// The arguments provided to a program instruction where invalid + InvalidArgument, + /// An instruction's data contents was invalid + InvalidInstructionData, + /// An account's data contents was invalid + InvalidAccountData, + /// An account's data was too small + AccountDataTooSmall, + /// An account's balance was too small to complete the instruction + InsufficientFunds, + /// The account did not have the expected program id + IncorrectProgramId, + /// A signature was required but not found + MissingRequiredSignature, + /// 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. + UninitializedAccount, + /// The instruction expected additional account keys + NotEnoughAccountKeys, + /// Failed to borrow a reference to an account, already borrowed + AccountBorrowFailed, +} + +/// 32bit representations of builtin program errors returned by the entry point +const BUILTIN_ERROR_START: u32 = 0x8000_0000; // 31st bit set +const INVALID_ARGUMENT: u32 = BUILTIN_ERROR_START; +const INVALID_INSTRUCTION_DATA: u32 = BUILTIN_ERROR_START + 1; +const INVALID_ACCOUNT_DATA: u32 = BUILTIN_ERROR_START + 2; +const ACCOUNT_DATA_TOO_SMALL: u32 = BUILTIN_ERROR_START + 3; +const INSUFFICIENT_FUNDS: u32 = BUILTIN_ERROR_START + 4; +const INCORRECT_PROGRAM_ID: u32 = BUILTIN_ERROR_START + 5; +const MISSING_REQUIRED_SIGNATURES: u32 = BUILTIN_ERROR_START + 6; +const ACCOUNT_ALREADY_INITIALIZED: u32 = BUILTIN_ERROR_START + 7; +const UNINITIALIZED_ACCOUNT: u32 = BUILTIN_ERROR_START + 8; +const NOT_ENOUGH_ACCOUNT_KEYS: u32 = BUILTIN_ERROR_START + 9; +const ACCOUNT_BORROW_FAILED: u32 = BUILTIN_ERROR_START + 10; + +/// Is this a builtin error? (is 31th bit set?) +fn is_builtin(error: u32) -> bool { + (error & BUILTIN_ERROR_START) != 0 +} + +/// If a program defined error conflicts with a builtin error +/// its 30th bit is set before returning to distinguish it. +/// The side effect is that the original error's 30th bit +/// value is lost, be aware. +const CONFLICTING_ERROR_MARK: u32 = 0x4000_0000; // 30st bit set + +/// Is this error marked as conflicting? (is 30th bit set?) +fn is_marked_conflicting(error: u32) -> bool { + (error & CONFLICTING_ERROR_MARK) != 0 +} + +/// Mark as a conflicting error +fn mark_conflicting(error: u32) -> u32 { + error | CONFLICTING_ERROR_MARK +} + +/// Unmark as a conflicting error +fn unmark_conflicting(error: u32) -> u32 { + error & !CONFLICTING_ERROR_MARK +} + +impl From for u32 { + fn from(error: ProgramError) -> Self { + match error { + ProgramError::InvalidArgument => INVALID_ARGUMENT, + ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA, + ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA, + ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL, + ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS, + ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID, + ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES, + ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED, + ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT, + ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS, + ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED, + ProgramError::CustomError(error) => { + if error == 0 || is_builtin(error) { + mark_conflicting(error) + } else { + error + } + } + } + } +} + +impl From for InstructionError +where + T: ToPrimitive, +{ + fn from(error: T) -> Self { + let error = error.to_u32().unwrap_or(0xbad_c0de); + match error { + INVALID_ARGUMENT => InstructionError::InvalidArgument, + INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData, + INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData, + ACCOUNT_DATA_TOO_SMALL => InstructionError::AccountDataTooSmall, + INSUFFICIENT_FUNDS => InstructionError::InsufficientFunds, + INCORRECT_PROGRAM_ID => InstructionError::IncorrectProgramId, + MISSING_REQUIRED_SIGNATURES => InstructionError::MissingRequiredSignature, + ACCOUNT_ALREADY_INITIALIZED => InstructionError::AccountAlreadyInitialized, + UNINITIALIZED_ACCOUNT => InstructionError::UninitializedAccount, + NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys, + ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed, + _ => { + if is_marked_conflicting(error) { + InstructionError::ConflictingError(unmark_conflicting(error)) + } else { + InstructionError::CustomError(error) + } + } + } + } +}