From bc74ee7117a8832be6294fd20694f31f2140390e Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 17 May 2019 11:04:29 -0700 Subject: [PATCH] Common Rust-BPF utilities and types (#4325) --- programs/bpf/rust/noop/Cargo.toml | 7 +- programs/bpf/rust/noop/src/lib.rs | 26 +- sdk/bpf/rust-utils/.gitignore | 3 + sdk/bpf/rust-utils/Cargo.toml | 13 + .../bpf/rust-utils/src/lib.rs | 474 ++++++++++-------- 5 files changed, 299 insertions(+), 224 deletions(-) create mode 100644 sdk/bpf/rust-utils/.gitignore create mode 100644 sdk/bpf/rust-utils/Cargo.toml rename programs/bpf/rust/noop/src/solana_sdk.rs => sdk/bpf/rust-utils/src/lib.rs (52%) diff --git a/programs/bpf/rust/noop/Cargo.toml b/programs/bpf/rust/noop/Cargo.toml index 7a6473c5c8..46798f0045 100644 --- a/programs/bpf/rust/noop/Cargo.toml +++ b/programs/bpf/rust/noop/Cargo.toml @@ -9,11 +9,10 @@ authors = ["Solana Maintainers "] repository = "https://github.com/solana-labs/solana" license = "Apache-2.0" homepage = "https://solana.com/" +edition = "2018" [dependencies] -# byteorder = { version = "1.3.1", default-features = false } -# heapless = { version = "0.4.0", default-features = false } -# byte = { version = "0.2", default-features = false } +solana-sdk-bpf-utils = { path = "../../../../sdk/bpf/rust-utils", version = "0.15.0" } [workspace] members = [] @@ -21,5 +20,3 @@ members = [] [lib] name = "solana_bpf_rust_noop" crate-type = ["cdylib"] - - diff --git a/programs/bpf/rust/noop/src/lib.rs b/programs/bpf/rust/noop/src/lib.rs index 4fa2a549c1..354ae4db3d 100644 --- a/programs/bpf/rust/noop/src/lib.rs +++ b/programs/bpf/rust/noop/src/lib.rs @@ -1,11 +1,10 @@ //! @brief Example Rust-based BPF program that prints out the parameters passed to it -#![cfg(not(test))] #![no_std] -mod solana_sdk; +extern crate solana_sdk_bpf_utils; -use solana_sdk::*; +use solana_sdk_bpf_utils::*; struct SStruct { x: u64, @@ -18,7 +17,12 @@ fn return_sstruct() -> SStruct { SStruct { x: 1, y: 2, z: 3 } } -fn process(ka: &mut [SolKeyedAccount], data: &[u8], info: &SolClusterInfo) -> bool { +solana_entrypoint!(process_instruction); +fn process_instruction( + ka: &mut [Option; MAX_ACCOUNTS], + info: &SolClusterInfo, + data: &[u8], +) -> bool { sol_log("Tick height:"); sol_log_64(info.tick_height, 0, 0, 0, 0); sol_log("Program identifier:"); @@ -30,23 +34,27 @@ fn process(ka: &mut [SolKeyedAccount], data: &[u8], info: &SolClusterInfo) -> bo sol_log("Account keys and instruction input data:"); sol_log_params(ka, data); + { + // Test - arch config + #[cfg(not(target_arch = "bpf"))] + assert!(false); + } + { // Test - use core methods, unwrap // valid bytes, in a stack-allocated array let sparkle_heart = [240, 159, 146, 150]; - let result_str = core::str::from_utf8(&sparkle_heart).unwrap(); - - sol_log_64(0, 0, 0, 0, result_str.len() as u64); - sol_log(result_str); + assert_eq!(4, result_str.len()); assert_eq!("💖", result_str); + sol_log(result_str); } { // Test - struct return + let s = return_sstruct(); - sol_log_64(0, 0, s.x, s.y, s.z); assert_eq!(s.x + s.y + s.z, 6); } diff --git a/sdk/bpf/rust-utils/.gitignore b/sdk/bpf/rust-utils/.gitignore new file mode 100644 index 0000000000..e13de17f65 --- /dev/null +++ b/sdk/bpf/rust-utils/.gitignore @@ -0,0 +1,3 @@ +/target/ + +Cargo.lock diff --git a/sdk/bpf/rust-utils/Cargo.toml b/sdk/bpf/rust-utils/Cargo.toml new file mode 100644 index 0000000000..12dfd21a46 --- /dev/null +++ b/sdk/bpf/rust-utils/Cargo.toml @@ -0,0 +1,13 @@ + +[package] +name = "solana-sdk-bpf-utils" +version = "0.15.0" +description = "Solana BPF SDK Rust Utils" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[workspace] +members = [] diff --git a/programs/bpf/rust/noop/src/solana_sdk.rs b/sdk/bpf/rust-utils/src/lib.rs similarity index 52% rename from programs/bpf/rust/noop/src/solana_sdk.rs rename to sdk/bpf/rust-utils/src/lib.rs index 248a63a7cb..2604d3ccc7 100644 --- a/programs/bpf/rust/noop/src/solana_sdk.rs +++ b/sdk/bpf/rust-utils/src/lib.rs @@ -1,142 +1,21 @@ //! @brief Solana Rust-based BPF program utility functions and types -// extern crate heapless; +#![no_std] -// use self::heapless::consts::*; -// use self::heapless::String; // fixed capacity `std::Vec` // type level integer used to specify capacity -#[cfg(test)] -use self::tests::process; use core::mem::size_of; -use core::panic::PanicInfo; -use core::slice::from_raw_parts; - #[cfg(not(test))] -use process; +use core::panic::PanicInfo; +use core::slice::{from_raw_parts, from_raw_parts_mut}; -// Panic handling -extern "C" { - pub fn sol_panic_() -> !; -} -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - sol_log("Panic!"); - // TODO rashes! sol_log(_info.payload().downcast_ref::<&str>().unwrap()); - if let Some(location) = _info.location() { - if !location.file().is_empty() { - // TODO location.file() returns empty str, if we get here its been fixed - sol_log(location.file()); - sol_log("location.file() is fixed!!"); - unsafe { - sol_panic_(); - } - } - sol_log_64(0, 0, 0, location.line() as u64, location.column() as u64); - } else { - sol_log("Panic! but could not get location information"); - } - unsafe { - sol_panic_(); - } -} - -extern "C" { - fn sol_log_(message: *const u8); -} -/// Helper function that prints a string to stdout -#[inline(never)] // stack intensive, block inline so everyone does not incur -pub fn sol_log(message: &str) { - // TODO This is extremely slow, do something better - let mut buf: [u8; 128] = [0; 128]; - for (i, b) in message.as_bytes().iter().enumerate() { - if i >= 126 { - break; - } - buf[i] = *b; - } - unsafe { - sol_log_(buf.as_ptr()); - } - - // let mut c_string: String = String::new(); - // if message.len() < 256 { - // if c_string.push_str(message).is_err() { - // c_string - // .push_str("Attempted to log a malformed string\0") - // .is_ok(); - // } - // if c_string.push('\0').is_err() { - // c_string.push_str("Failed to log string\0").is_ok(); - // }; - // } else { - // c_string - // .push_str("Attempted to log a string that is too long\0") - // .is_ok(); - // } - // unsafe { - // sol_log_(message.as_ptr()); - // } -} - -extern "C" { - fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64); -} -/// Helper function that prints a 64 bit values represented in hexadecimal -/// to stdout -pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { - unsafe { - sol_log_64_(arg1, arg2, arg3, arg4, arg5); - } -} - -/// Prints the hexadecimal representation of a public key -/// -/// @param key The public key to print -#[allow(dead_code)] -pub fn sol_log_key(key: &SolPubkey) { - for (i, k) in key.key.iter().enumerate() { - sol_log_64(0, 0, 0, i as u64, u64::from(*k)); - } -} - -/// Prints the hexadecimal representation of a slice -/// -/// @param slice The array to print -#[allow(dead_code)] -pub fn sol_log_slice(slice: &[u8]) { - for (i, s) in slice.iter().enumerate() { - sol_log_64(0, 0, 0, i as u64, u64::from(*s)); - } -} - -/// Prints the hexadecimal representation of the program's input parameters -/// -/// @param ka A pointer to an array of SolKeyedAccount to print -/// @param data A pointer to the instruction data to print -#[allow(dead_code)] -pub fn sol_log_params(ka: &[SolKeyedAccount], data: &[u8]) { - sol_log("- Number of KeyedAccounts"); - sol_log_64(0, 0, 0, 0, ka.len() as u64); - for k in ka.iter() { - sol_log("- Is signer"); - sol_log_64(0, 0, 0, 0, k.is_signer as u64); - sol_log("- Key"); - sol_log_key(&k.key); - sol_log("- Lamports"); - sol_log_64(0, 0, 0, 0, k.lamports); - sol_log("- AccountData"); - sol_log_slice(k.data); - sol_log("- Owner"); - sol_log_key(&k.owner); - } - sol_log("- Instruction data"); - sol_log_slice(data); -} +/// Max number of accounts supported +pub const MAX_ACCOUNTS: usize = 10; +/// Size in bytes of a public key pub const SIZE_PUBKEY: usize = 32; /// Public key pub struct SolPubkey<'a> { - pub key: &'a [u8], + pub key: &'a [u8; SIZE_PUBKEY], } /// Keyed Account @@ -144,11 +23,11 @@ pub struct SolKeyedAccount<'a> { /// Public key of the account pub key: SolPubkey<'a>, /// Public key of the account - pub is_signer: u64, + pub is_signer: bool, /// Number of lamports owned by this account pub lamports: u64, /// On-chain data within this account - pub data: &'a [u8], + pub data: &'a mut [u8], /// Program that owns this account pub owner: SolPubkey<'a>, } @@ -162,9 +41,126 @@ pub struct SolClusterInfo<'a> { pub program_id: SolPubkey<'a>, } -#[no_mangle] -pub extern "C" fn entrypoint(input: *mut u8) -> bool { - const NUM_KA: usize = 1; // Number of KeyedAccounts expected +/// Declare entrypoint of the program. +/// +/// Deserialize the program input parameters and call +/// a user defined entrypoint. Users must call +/// this function otherwise an entrypoint for +/// their program will not be created. +#[macro_export] +macro_rules! solana_entrypoint { + ($process_instruction:ident) => { + #[no_mangle] + pub extern "C" fn entrypoint(input: *mut u8) -> bool { + if let Ok((mut ka, info, data)) = $crate::deserialize(input) { + // Call use function + $process_instruction(&mut ka, &info, &data) + } else { + false + } + } + }; +} + +/// Checks if a public key is default, default implies not set and is +/// represented as all zeros +/// +/// @param key - Public key to check +pub fn sol_key_default(key_bytes: &[u8]) -> bool { + for (_, k) in key_bytes.iter().enumerate() { + if *k != 0 { + return false; + } + } + true +} + +/// Prints a string to stdout +#[inline(never)] // stack intensive, prevent inline so everyone does not incur cost +pub fn sol_log(message: &str) { + // TODO This is extremely slow, do something better + let mut buf: [u8; 128] = [0; 128]; + for (i, b) in message.as_bytes().iter().enumerate() { + if i >= 126 { + break; + } + buf[i] = *b; + } + unsafe { + sol_log_(buf.as_ptr()); + } +} +extern "C" { + fn sol_log_(message: *const u8); +} + +/// Prints 64 bit values represented as hexadecimal to stdout +pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { + unsafe { + sol_log_64_(arg1, arg2, arg3, arg4, arg5); + } +} +extern "C" { + fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64); +} + +/// Prints the hexadecimal representation of a public key +/// +/// @param - key The public key to print +#[allow(dead_code)] +pub fn sol_log_key(key: &SolPubkey) { + for (i, k) in key.key.iter().enumerate() { + sol_log_64(0, 0, 0, i as u64, u64::from(*k)); + } +} + +/// Prints the hexadecimal representation of a slice +/// +/// @param slice - The array to print +#[allow(dead_code)] +pub fn sol_log_slice(slice: &[u8]) { + for (i, s) in slice.iter().enumerate() { + sol_log_64(0, 0, 0, i as u64, u64::from(*s)); + } +} + +/// Prints the hexadecimal representation of the program's input parameters +/// +/// @param ka - A pointer to an array of SolKeyedAccounts to print +/// @param data - A pointer to the instruction data to print +#[allow(dead_code)] +pub fn sol_log_params(ka: &[Option], data: &[u8]) { + for (i, k) in ka.iter().enumerate() { + if let Some(k) = k { + sol_log("SolKeyedAccount"); + sol_log_64(0, 0, 0, 0, i as u64); + sol_log("- Is signer"); + sol_log_64(0, 0, 0, 0, k.is_signer as u64); + sol_log("- Key"); + sol_log_key(&k.key); + sol_log("- Lamports"); + sol_log_64(0, 0, 0, 0, k.lamports); + sol_log("- AccountData"); + sol_log_slice(k.data); + sol_log("- Owner"); + sol_log_key(&k.owner); + } + } + sol_log("Instruction data"); + sol_log_slice(data); +} + +/// Deserialize the input parameters +pub fn deserialize<'a>( + input: *mut u8, +) -> Result< + ( + [Option>; MAX_ACCOUNTS], + SolClusterInfo<'a>, + &'a [u8], + ), + (), +> { let mut offset: usize = 0; // Number of KeyedAccounts present @@ -172,55 +168,76 @@ pub extern "C" fn entrypoint(input: *mut u8) -> bool { let num_ka = unsafe { #[allow(clippy::cast_ptr_alignment)] let num_ka_ptr: *const u64 = input.add(offset) as *const u64; - *num_ka_ptr + *num_ka_ptr as usize }; offset += 8; - if num_ka != NUM_KA as u64 { - return false; - } - // KeyedAccounts - let is_signer = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let is_signer_ptr: *const u64 = input.add(offset) as *const u64; - *is_signer_ptr - }; - offset += size_of::(); + if num_ka > MAX_ACCOUNTS { + sol_log("Error: Too many accounts"); + return Err(()); + } - let key_slice = unsafe { from_raw_parts(input.add(offset), SIZE_PUBKEY) }; - let key = SolPubkey { key: &key_slice }; - offset += SIZE_PUBKEY; + let mut ka: [Option; MAX_ACCOUNTS] = + [None, None, None, None, None, None, None, None, None, None]; + let iter = 0..num_ka; // This weirdness due to #issue $#4271 + for (i, _) in iter.enumerate() { + let is_signer = unsafe { + #[allow(clippy::cast_ptr_alignment)] + let is_signer_ptr: *const u64 = input.add(offset) as *const u64; + if *is_signer_ptr == 0 { + false + } else { + true + } + }; + offset += size_of::(); - let lamports = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let lamports_ptr: *const u64 = input.add(offset) as *const u64; - *lamports_ptr - }; - offset += size_of::(); + // let key_slice = unsafe { from_raw_parts(input.add(offset), SIZE_PUBKEY) }; + // let key = SolPubkey { + // key: key_slice.try_into().unwrap(), + // }; + let key = unsafe { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } + }; + offset += SIZE_PUBKEY; - let data_length = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let data_length_ptr: *const u64 = input.add(offset) as *const u64; - *data_length_ptr - } as usize; - offset += size_of::(); + let lamports = unsafe { + #[allow(clippy::cast_ptr_alignment)] + let lamports_ptr: *const u64 = input.add(offset) as *const u64; + *lamports_ptr + }; + offset += size_of::(); - let data = unsafe { from_raw_parts(input.add(offset), data_length) }; - offset += data_length; + let data_length = unsafe { + #[allow(clippy::cast_ptr_alignment)] + let data_length_ptr: *const u64 = input.add(offset) as *const u64; + *data_length_ptr + } as usize; + offset += size_of::(); - let owner_slice = unsafe { from_raw_parts(input.add(offset), SIZE_PUBKEY) }; - let owner = SolPubkey { key: &owner_slice }; - offset += SIZE_PUBKEY; + let data = unsafe { from_raw_parts_mut(input.add(offset), data_length) }; + offset += data_length; - let mut ka = [SolKeyedAccount { - key, - is_signer, - lamports, - data, - owner, - }]; + // let owner_slice = unsafe { input.add(offset) as *mut [u8; SIZE_PUBKEY] }; + let owner = unsafe { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } + }; + offset += SIZE_PUBKEY; + + ka[i] = Some(SolKeyedAccount { + key, + is_signer, + lamports, + data, + owner, + }); + } // Instruction data @@ -245,9 +262,10 @@ pub extern "C" fn entrypoint(input: *mut u8) -> bool { // Id - let program_id_slice = unsafe { from_raw_parts(input.add(offset), SIZE_PUBKEY) }; - let program_id: SolPubkey = SolPubkey { - key: &program_id_slice, + let program_id = unsafe { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } }; let info = SolClusterInfo { @@ -255,8 +273,34 @@ pub extern "C" fn entrypoint(input: *mut u8) -> bool { program_id, }; - // Call user implementable function - process(&mut ka, &data, &info) + Ok((ka, info, data)) +} + +// Panic handling +extern "C" { + pub fn sol_panic_() -> !; +} +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + sol_log("Panic!"); + // TODO crashes! sol_log(_info.payload().downcast_ref::<&str>().unwrap()); + if let Some(location) = _info.location() { + if !location.file().is_empty() { + // TODO location.file() returns empty str, if we get here its been fixed + sol_log(location.file()); + sol_log("location.file() is fixed!!"); + unsafe { + sol_panic_(); + } + } + sol_log_64(0, 0, 0, location.line() as u64, location.column() as u64); + } else { + sol_log("Panic! but could not get location information"); + } + unsafe { + sol_panic_(); + } } #[cfg(test)] @@ -267,8 +311,9 @@ mod tests { use self::std::println; use self::std::string::String; use super::*; + use core::mem; - static mut _LOG_SCENARIO: u64 = 0; + static mut _LOG_SCENARIO: u64 = 4; fn get_log_scenario() -> u64 { unsafe { _LOG_SCENARIO } } @@ -285,15 +330,15 @@ mod tests { 1 => assert_eq!(string, "This is a test message"), 2 => assert_eq!(string, "Attempted to log a string that is too long"), 3 => { - let s: String = ['a'; 255].iter().collect(); + let s: String = ['a'; 126].iter().collect(); assert_eq!(string, s); } 4 => println!("{:?}", string), - _ => panic!("Unkown sol_log test"), + _ => panic!("Unknown sol_log test"), } } - static mut _LOG_64_SCENARIO: u64 = 0; + static mut _LOG_64_SCENARIO: u64 = 4; fn get_log_64_scenario() -> u64 { unsafe { _LOG_64_SCENARIO } } @@ -337,7 +382,7 @@ mod tests { #[test] fn test_sol_log_long() { - set_log_scenario(2); + set_log_scenario(3); let s: String = ['a'; 256].iter().collect(); sol_log(&s); } @@ -345,7 +390,7 @@ mod tests { #[test] fn test_sol_log_max_length() { set_log_scenario(3); - let s: String = ['a'; 255].iter().collect(); + let s: String = ['a'; 126].iter().collect(); sol_log(&s); } @@ -376,33 +421,6 @@ mod tests { sol_log_slice(&array); } - pub fn process(ka: &mut [SolKeyedAccount], data: &[u8], info: &SolClusterInfo) -> bool { - assert_eq!(1, ka.len()); - assert_eq!(1, ka[0].is_signer); - let key = [ - 151, 116, 3, 85, 181, 39, 151, 99, 155, 29, 208, 191, 255, 191, 11, 161, 4, 43, 104, - 189, 202, 240, 231, 111, 146, 255, 199, 71, 67, 34, 254, 68, - ]; - assert_eq!(SIZE_PUBKEY, ka[0].key.key.len()); - assert_eq!(key, ka[0].key.key); - assert_eq!(48, ka[0].lamports); - assert_eq!(1, ka[0].data.len()); - let owner = [0; 32]; - assert_eq!(SIZE_PUBKEY, ka[0].owner.key.len()); - assert_eq!(owner, ka[0].owner.key); - let d = [1, 0, 0, 0, 0, 0, 0, 0, 1]; - assert_eq!(9, data.len()); - assert_eq!(d, data); - assert_eq!(1, info.tick_height); - let program_id = [ - 190, 103, 191, 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, 120, - 177, 90, 212, 54, 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, - ]; - assert_eq!(program_id, info.program_id.key); - - true - } - #[test] fn test_entrypoint() { set_log_scenario(4); @@ -417,6 +435,42 @@ mod tests { 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, ]; - entrypoint(&mut input[0] as *mut u8); + if let Ok((mut ka, info, data)) = deserialize(&mut input[0] as *mut u8) { + let account0 = match mem::replace(&mut ka[0], None) { + Some(mut account0) => account0, + None => { + panic!("Error: account not found"); + } + }; + + for k in ka[1..].iter() { + if let Some(_) = k { + panic!("Too many keyed accounts found"); + } + } + assert_eq!(true, account0.is_signer); + let key: &[u8; SIZE_PUBKEY] = &[ + 151, 116, 3, 85, 181, 39, 151, 99, 155, 29, 208, 191, 255, 191, 11, 161, 4, 43, + 104, 189, 202, 240, 231, 111, 146, 255, 199, 71, 67, 34, 254, 68, + ]; + assert_eq!(SIZE_PUBKEY, account0.key.key.len()); + assert_eq!(key, account0.key.key); + assert_eq!(48, account0.lamports); + assert_eq!(1, account0.data.len()); + let owner = &[0; SIZE_PUBKEY]; + assert_eq!(SIZE_PUBKEY, account0.owner.key.len()); + assert_eq!(owner, account0.owner.key); + let d = [1, 0, 0, 0, 0, 0, 0, 0, 1]; + assert_eq!(9, data.len()); + assert_eq!(d, data); + assert_eq!(1, info.tick_height); + let program_id: &[u8; SIZE_PUBKEY] = &[ + 190, 103, 191, 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, + 120, 177, 90, 212, 54, 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, + ]; + assert_eq!(program_id, info.program_id.key); + } else { + panic!("Failed to deserialize"); + } } }