Add support for BPF program custom errors (#5743)

* Add support for BPF program custom errors

* Rename SOL_SUCCESS -> SUCCESS
This commit is contained in:
Justin Starry 2019-09-06 16:05:01 -07:00 committed by GitHub
parent d3052d094c
commit 81c36699c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 108 additions and 67 deletions

View File

@ -80,7 +80,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
println!("Interpreted:"); println!("Interpreted:");
assert_eq!( assert_eq!(
1, /*true*/ 0, /*success*/
vm.execute_program(&mut inner_iter, &[], &[]).unwrap() vm.execute_program(&mut inner_iter, &[], &[]).unwrap()
); );
assert_eq!(ARMSTRONG_LIMIT, LittleEndian::read_u64(&inner_iter)); assert_eq!(ARMSTRONG_LIMIT, LittleEndian::read_u64(&inner_iter));
@ -106,7 +106,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
// vm.jit_compile().unwrap(); // vm.jit_compile().unwrap();
// unsafe { // unsafe {
// assert_eq!( // assert_eq!(
// 1, /*true*/ // 0, /*success*/
// vm.execute_program_jit(&mut inner_iter).unwrap() // vm.execute_program_jit(&mut inner_iter).unwrap()
// ); // );
// } // }

View File

@ -6,7 +6,7 @@
#include <solana_sdk.h> #include <solana_sdk.h>
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
uint64_t x = *(uint64_t *) input; uint64_t x = *(uint64_t *) input;
uint64_t *result = (uint64_t *) input + 1; uint64_t *result = (uint64_t *) input + 1;
uint64_t count = 0; uint64_t count = 0;
@ -26,5 +26,5 @@ extern bool entrypoint(const uint8_t *input) {
// sol_log_64(x, count, 0, 0, 0); // sol_log_64(x, count, 0, 0, 0);
*result = count; *result = count;
return true; return SUCCESS;
} }

View File

@ -4,7 +4,7 @@
Test(bench_alu, sanity) { Test(bench_alu, sanity) {
uint64_t input[] = {500, 0}; uint64_t input[] = {500, 0};
cr_assert(entrypoint((uint8_t *) input)); cr_assert_eq(entrypoint((uint8_t *) input), 0);
cr_assert_eq(input[0], 500); cr_assert_eq(input[0], 500);
cr_assert_eq(input[1], 5); cr_assert_eq(input[1], 5);

View File

@ -6,9 +6,9 @@
#include "helper.h" #include "helper.h"
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
sol_log(__FILE__); sol_log(__FILE__);
helper_function(); helper_function();
sol_log(__FILE__); sol_log(__FILE__);
return true; return SUCCESS;
} }

View File

@ -11,17 +11,27 @@
*/ */
#define NUM_KA 3 #define NUM_KA 3
extern bool entrypoint(const uint8_t *input) { /**
* 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]; SolKeyedAccount ka[NUM_KA];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return false; return INVALID_INPUT;
} }
if (!params.ka[0].is_signer) { if (!params.ka[0].is_signer) {
sol_log("Transaction not signed by key 0"); sol_log("Transaction not signed by key 0");
return false; return NOT_SIGNED;
} }
int64_t lamports = *(int64_t *)params.data; int64_t lamports = *(int64_t *)params.data;
@ -32,5 +42,5 @@ extern bool entrypoint(const uint8_t *input) {
} else { } else {
// sol_log_64(0, 0, 0xFF, *ka[0].lamports, lamports); // sol_log_64(0, 0, 0xFF, *ka[0].lamports, lamports);
} }
return true; return SUCCESS;
} }

View File

@ -3,8 +3,8 @@
static const char msg[] = "This is a message"; static const char msg[] = "This is a message";
static const char msg2[] = "This is a different message"; static const char msg2[] = "This is a different message";
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
sol_log((char*)msg); sol_log((char*)msg);
sol_log((char*)msg2); sol_log((char*)msg2);
return true; return SUCCESS;
} }

View File

@ -4,19 +4,24 @@
*/ */
#include <solana_sdk.h> #include <solana_sdk.h>
extern bool entrypoint(const uint8_t *input) { /**
* Custom error for when input serialization fails
*/
#define INVALID_INPUT 1
extern uint32_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[1]; SolKeyedAccount ka[1];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
sol_log(__FILE__); sol_log(__FILE__);
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return false; return INVALID_INPUT;
} }
// Log the provided input parameters. In the case of the no-op // Log the provided input parameters. In the case of the no-op
// program, no account keys or input data are expected but real // program, no account keys or input data are expected but real
// programs will have specific requirements so they can do their work. // programs will have specific requirements so they can do their work.
sol_log_params(&params); sol_log_params(&params);
return true; return SUCCESS;
} }

View File

@ -4,20 +4,24 @@
*/ */
#include <solana_sdk.h> #include <solana_sdk.h>
extern bool entrypoint(const uint8_t *input) { /**
* Custom error for when input serialization fails
*/
#define INVALID_INPUT 1
extern uint32_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[1]; SolKeyedAccount ka[1];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
sol_log(__FILE__); sol_log(__FILE__);
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return false; return INVALID_INPUT;
} }
// Log the provided input parameters. In the case of the no-op // Log the provided input parameters. In the case of the no-op
// program, no account keys or input data are expected but real // program, no account keys or input data are expected but real
// programs will have specific requirements so they can do their work. // programs will have specific requirements so they can do their work.
sol_log_params(&params); sol_log_params(&params);
return true; return SUCCESS;
} }

View File

@ -4,8 +4,7 @@
*/ */
#include <solana_sdk.h> #include <solana_sdk.h>
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
sol_panic(); sol_panic();
return true; return SUCCESS;
} }

View File

@ -8,9 +8,8 @@ void __attribute__ ((noinline)) helper() {
sol_log(__func__); sol_log(__func__);
} }
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
sol_log(__func__); sol_log(__func__);
helper(); helper();
return true; return SUCCESS;
} }

View File

@ -3,15 +3,14 @@
struct foo {const uint8_t *input;}; struct foo {const uint8_t *input;};
void foo(const uint8_t *input, struct foo foo) ; void foo(const uint8_t *input, struct foo foo) ;
extern bool entrypoint(const uint8_t *input) { extern uint32_t entrypoint(const uint8_t *input) {
struct foo f; struct foo f;
f.input = input; f.input = input;
foo(input, f); foo(input, f);
return true; return SUCCESS;
} }
void foo(const uint8_t *input, struct foo foo) { void foo(const uint8_t *input, struct foo foo) {
sol_log_64(0, 0, 0, (uint64_t)input, (uint64_t)foo.input); sol_log_64(0, 0, 0, (uint64_t)input, (uint64_t)foo.input);
sol_assert(input == foo.input); sol_assert(input == foo.input);
} }

View File

@ -1,5 +1,10 @@
#include <solana_sdk.h> #include <solana_sdk.h>
/**
* Custom error for when struct doesn't add to 12
*/
#define INCORRECT_SUM 1
struct test_struct { uint64_t x; uint64_t y; uint64_t z;}; struct test_struct { uint64_t x; uint64_t y; uint64_t z;};
static struct test_struct __attribute__ ((noinline)) test_function(void) { static struct test_struct __attribute__ ((noinline)) test_function(void) {
@ -10,12 +15,11 @@ static struct test_struct __attribute__ ((noinline)) test_function(void) {
return s; return s;
} }
extern bool entrypoint(const uint8_t* input) { extern uint32_t entrypoint(const uint8_t* input) {
struct test_struct s = test_function(); struct test_struct s = test_function();
sol_log("foobar"); sol_log("foobar");
if (s.x + s.y + s.z == 12 ) { if (s.x + s.y + s.z == 12 ) {
return true; return SUCCESS;
} }
return false; return INCORRECT_SUM;
} }

View File

@ -1,10 +1,11 @@
//! @brief Example Rust-based BPF program tests loop iteration //! @brief Example Rust-based BPF program tests loop iteration
extern crate solana_sdk; extern crate solana_sdk;
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
let x: u128 = 1; let x: u128 = 1;
let y = x.rotate_right(1); let y = x.rotate_right(1);
assert_eq!(y, 170_141_183_460_469_231_731_687_303_715_884_105_728); assert_eq!(y, 170_141_183_460_469_231_731_687_303_715_884_105_728);
@ -48,5 +49,5 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
assert_eq!(x, 0x0001_ffff_ffff_ffff_fffe); assert_eq!(x, 0x0001_ffff_ffff_ffff_fffe);
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -3,12 +3,13 @@
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;
extern crate solana_sdk; extern crate solana_sdk;
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
use std::alloc::Layout; use std::alloc::Layout;
use std::mem; use std::mem;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
unsafe { unsafe {
// Confirm large allocation fails // Confirm large allocation fails
@ -100,5 +101,5 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
} }
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -2,10 +2,11 @@
extern crate solana_sdk; extern crate solana_sdk;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
let mut buf = [0; 4]; let mut buf = [0; 4];
LittleEndian::write_u32(&mut buf, 1_000_000); LittleEndian::write_u32(&mut buf, 1_000_000);
assert_eq!(1_000_000, LittleEndian::read_u32(&buf)); assert_eq!(1_000_000, LittleEndian::read_u32(&buf));
@ -15,5 +16,5 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
assert_eq!(-5_000, LittleEndian::read_i16(&buf)); assert_eq!(-5_000, LittleEndian::read_i16(&buf));
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -6,11 +6,10 @@ use solana_sdk::entrypoint::*;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
entrypoint!(process_instruction); entrypoint!(process_instruction);
fn process_instruction(_program_id: &Pubkey, ka: &mut [SolKeyedAccount], _data: &[u8]) -> bool { fn process_instruction(_program_id: &Pubkey, ka: &mut [SolKeyedAccount], _data: &[u8]) -> u32 {
// account 0 is the mint and not owned by this program, any debit of its lamports // 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 // should result in a failed program execution. Test to ensure that this debit
// is seen by the runtime and fails as expected // is seen by the runtime and fails as expected
*ka[0].lamports -= 1; *ka[0].lamports -= 1;
SUCCESS
true
} }

View File

@ -1,10 +1,11 @@
//! @brief Example Rust-based BPF program tests loop iteration //! @brief Example Rust-based BPF program tests loop iteration
extern crate solana_sdk; extern crate solana_sdk;
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
const ITERS: usize = 100; const ITERS: usize = 100;
let ones = [1_u64; ITERS]; let ones = [1_u64; ITERS];
let mut sum: u64 = 0; let mut sum: u64 = 0;
@ -16,5 +17,5 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
assert_eq!(sum, ITERS as u64); assert_eq!(sum, ITERS as u64);
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -2,10 +2,11 @@
mod helper; mod helper;
extern crate solana_sdk; extern crate solana_sdk;
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
info!("Call same package"); info!("Call same package");
assert_eq!(crate::helper::many_args(1, 2, 3, 4, 5, 6, 7, 8, 9), 45); assert_eq!(crate::helper::many_args(1, 2, 3, 4, 5, 6, 7, 8, 9), 45);
@ -24,5 +25,5 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
); );
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -21,7 +21,7 @@ fn return_sstruct() -> SStruct {
} }
entrypoint!(process_instruction); entrypoint!(process_instruction);
fn process_instruction(program_id: &Pubkey, ka: &mut [SolKeyedAccount], data: &[u8]) -> bool { fn process_instruction(program_id: &Pubkey, ka: &mut [SolKeyedAccount], data: &[u8]) -> u32 {
info!("Program identifier:"); info!("Program identifier:");
program_id.log(); program_id.log();
@ -56,7 +56,7 @@ fn process_instruction(program_id: &Pubkey, ka: &mut [SolKeyedAccount], data: &[
} }
info!("Success"); info!("Success");
true SUCCESS
} }
#[cfg(test)] #[cfg(test)]

View File

@ -3,6 +3,6 @@
extern crate solana_sdk; extern crate solana_sdk;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
panic!(); panic!();
} }

View File

@ -2,10 +2,11 @@
extern crate solana_sdk; extern crate solana_sdk;
use solana_bpf_rust_param_passing_dep::{Data, TestDep}; use solana_bpf_rust_param_passing_dep::{Data, TestDep};
use solana_sdk::entrypoint::SUCCESS;
use solana_sdk::info; use solana_sdk::info;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> bool { pub extern "C" fn entrypoint(_input: *mut u8) -> u32 {
let array = [0xA, 0xB, 0xC, 0xD, 0xE, 0xF]; let array = [0xA, 0xB, 0xC, 0xD, 0xE, 0xF];
let data = Data { let data = Data {
twentyone: 21u64, twentyone: 21u64,
@ -18,5 +19,6 @@ pub extern "C" fn entrypoint(_input: *mut u8) -> bool {
let test_dep = TestDep::new(&data, 1, 2, 3, 4, 5); let test_dep = TestDep::new(&data, 1, 2, 3, 4, 5);
info!(0, 0, 0, 0, test_dep.thirty); info!(0, 0, 0, 0, test_dep.thirty);
test_dep.thirty == 30 assert!(test_dep.thirty == 30);
SUCCESS
} }

View File

@ -7,10 +7,10 @@ use solana_sdk::pubkey::Pubkey;
use solana_sdk::{entrypoint, info}; use solana_sdk::{entrypoint, info};
entrypoint!(process_instruction); entrypoint!(process_instruction);
fn process_instruction(_program_id: &Pubkey, ka: &mut [SolKeyedAccount], _data: &[u8]) -> bool { fn process_instruction(_program_id: &Pubkey, ka: &mut [SolKeyedAccount], _data: &[u8]) -> u32 {
let tick_height = LittleEndian::read_u64(ka[2].data); let tick_height = LittleEndian::read_u64(ka[2].data);
assert_eq!(10u64, tick_height); assert_eq!(10u64, tick_height);
info!("Success"); info!("Success");
true SUCCESS
} }

View File

@ -20,6 +20,7 @@ use solana_sdk::account::KeyedAccount;
use solana_sdk::instruction::InstructionError; use solana_sdk::instruction::InstructionError;
use solana_sdk::loader_instruction::LoaderInstruction; use solana_sdk::loader_instruction::LoaderInstruction;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use std::convert::TryFrom;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::Error; use std::io::Error;
use std::mem; use std::mem;
@ -136,12 +137,18 @@ pub fn process_instruction(
let mut v = serialize_parameters(program_id, params, &data); let mut v = serialize_parameters(program_id, params, &data);
match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) { match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) {
Ok(status) => { Ok(status) => match u32::try_from(status) {
if 0 == status { Ok(status) => {
warn!("BPF program failed: {}", status); if status > 0 {
warn!("BPF program failed: {}", status);
return Err(InstructionError::CustomError(status));
}
}
Err(e) => {
warn!("BPF VM encountered invalid status: {}", e);
return Err(InstructionError::GenericError); return Err(InstructionError::GenericError);
} }
} },
Err(e) => { Err(e) => {
warn!("BPF VM failed to run program: {}", e); warn!("BPF VM failed to run program: {}", e);
return Err(InstructionError::GenericError); return Err(InstructionError::GenericError);

View File

@ -51,6 +51,11 @@ static_assert(sizeof(uint64_t) == 8);
*/ */
#define NULL 0 #define NULL 0
/**
* SUCCESS return value
*/
#define SUCCESS 0
/** /**
* Boolean type * Boolean type
*/ */
@ -319,9 +324,9 @@ SOL_FN_PREFIX void sol_log_params(const SolParameters *params) {
* Program instruction entrypoint * Program instruction entrypoint
* *
* @param input Buffer of serialized input parameters. Use sol_deserialize() to decode * @param input Buffer of serialized input parameters. Use sol_deserialize() to decode
* @return true if the instruction executed successfully * @return 0 if the instruction executed successfully
*/ */
bool entrypoint(const uint8_t *input); uint32_t entrypoint(const uint8_t *input);
#ifdef SOL_TEST #ifdef SOL_TEST

View File

@ -28,6 +28,9 @@ pub struct SolKeyedAccount<'a> {
pub type ProcessInstruction = pub type ProcessInstruction =
fn(program_id: &Pubkey, accounts: &mut [SolKeyedAccount], data: &[u8]) -> bool; fn(program_id: &Pubkey, accounts: &mut [SolKeyedAccount], data: &[u8]) -> bool;
/// Programs indicate success with a return value of 0
pub const SUCCESS: u32 = 0;
/// Declare entrypoint of the program. /// Declare entrypoint of the program.
/// ///
/// Deserialize the program input parameters and call /// Deserialize the program input parameters and call
@ -38,13 +41,10 @@ pub type ProcessInstruction =
macro_rules! entrypoint { macro_rules! entrypoint {
($process_instruction:ident) => { ($process_instruction:ident) => {
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> bool { pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u32 {
unsafe { unsafe {
if let Ok((program_id, mut kas, data)) = $crate::entrypoint::deserialize(input) { let (program_id, mut kas, data) = $crate::entrypoint::deserialize(input);
$process_instruction(&program_id, &mut kas, &data) $process_instruction(&program_id, &mut kas, &data)
} else {
false
}
} }
} }
}; };
@ -54,7 +54,7 @@ macro_rules! entrypoint {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub unsafe fn deserialize<'a>( pub unsafe fn deserialize<'a>(
input: *mut u8, input: *mut u8,
) -> Result<(&'a Pubkey, Vec<SolKeyedAccount<'a>>, &'a [u8]), ()> { ) -> (&'a Pubkey, Vec<SolKeyedAccount<'a>>, &'a [u8]) {
let mut offset: usize = 0; let mut offset: usize = 0;
// Number of KeyedAccounts present // Number of KeyedAccounts present
@ -113,5 +113,5 @@ pub unsafe fn deserialize<'a>(
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey); let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
Ok((program_id, kas, data)) (program_id, kas, data)
} }

View File

@ -70,6 +70,9 @@ pub enum InstructionError {
/// CustomError allows on-chain programs to implement program-specific error types and see /// CustomError allows on-chain programs to implement program-specific error types and see
/// them returned by the Solana runtime. A CustomError may be any type that is represented /// them returned by the Solana runtime. A CustomError may be any type that is represented
/// as or serialized to a u32 integer. /// 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), CustomError(u32),
} }