From ae0fd3043a7a4aef23adf52e63caa9866a351680 Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 31 Aug 2020 14:06:58 -0700 Subject: [PATCH] Add support for deprecated loader (#11946) --- programs/bpf/Cargo.lock | 7 + programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 1 + .../src/deprecated_loader/deprecated_loader.c | 23 +++ .../bpf/rust/deprecated_loader/Cargo.toml | 26 ++++ .../bpf/rust/deprecated_loader/Xargo.toml | 2 + .../bpf/rust/deprecated_loader/src/lib.rs | 77 ++++++++++ programs/bpf/tests/programs.rs | 65 ++++++++- sdk/bpf/c/inc/deserialize_deprecated.h | 115 +++++++++++++++ sdk/src/bpf_loader.rs | 16 ++ sdk/src/bpf_loader_deprecated.rs | 13 ++ sdk/src/entrypoint.rs | 3 +- sdk/src/entrypoint_deprecated.rs | 138 ++++++++++++++++++ sdk/src/lib.rs | 1 + 14 files changed, 479 insertions(+), 9 deletions(-) create mode 100644 programs/bpf/c/src/deprecated_loader/deprecated_loader.c create mode 100644 programs/bpf/rust/deprecated_loader/Cargo.toml create mode 100644 programs/bpf/rust/deprecated_loader/Xargo.toml create mode 100644 programs/bpf/rust/deprecated_loader/src/lib.rs create mode 100644 sdk/bpf/c/inc/deserialize_deprecated.h create mode 100644 sdk/src/entrypoint_deprecated.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index bd23b8cbd0..9f709b0577 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1669,6 +1669,13 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-bpf-rust-deprecated_loader" +version = "1.4.0" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-bpf-rust-dup-accounts" version = "1.4.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 45bef9ed94..ca055e7c5b 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -37,6 +37,7 @@ members = [ "rust/128bit_dep", "rust/alloc", "rust/dep_crate", + "rust/deprecated_loader", "rust/dup_accounts", "rust/error_handling", "rust/external_spend", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 78036080a2..24b0c0cfcc 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -68,6 +68,7 @@ fn main() { "128bit", "alloc", "dep_crate", + "deprecated_loader", "dup_accounts", "error_handling", "external_spend", diff --git a/programs/bpf/c/src/deprecated_loader/deprecated_loader.c b/programs/bpf/c/src/deprecated_loader/deprecated_loader.c new file mode 100644 index 0000000000..d3a312837e --- /dev/null +++ b/programs/bpf/c/src/deprecated_loader/deprecated_loader.c @@ -0,0 +1,23 @@ +/** + * @brief Example C-based BPF program that prints out the parameters + * passed to it + */ +#include +#include + +extern uint64_t entrypoint(const uint8_t *input) { + SolAccountInfo ka[1]; + SolParameters params = (SolParameters) { .ka = ka }; + + sol_log(__FILE__); + + if (!sol_deserialize_deprecated(input, ¶ms, SOL_ARRAY_SIZE(ka))) { + return ERROR_INVALID_ARGUMENT; + } + + // Log the provided input parameters. In the case of the no-op + // program, no account keys or input data are expected but real + // programs will have specific requirements so they can do their work. + sol_log_params(¶ms); + return SUCCESS; +} diff --git a/programs/bpf/rust/deprecated_loader/Cargo.toml b/programs/bpf/rust/deprecated_loader/Cargo.toml new file mode 100644 index 0000000000..94a7097674 --- /dev/null +++ b/programs/bpf/rust/deprecated_loader/Cargo.toml @@ -0,0 +1,26 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-deprecated_loader" +version = "1.4.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] +solana-sdk = { path = "../../../../sdk/", version = "1.4.0", default-features = false } + +[features] +program = ["solana-sdk/program"] +default = ["program", "solana-sdk/default"] + +[lib] +name = "solana_bpf_rust_deprecated_loader" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/deprecated_loader/Xargo.toml b/programs/bpf/rust/deprecated_loader/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/deprecated_loader/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/deprecated_loader/src/lib.rs b/programs/bpf/rust/deprecated_loader/src/lib.rs new file mode 100644 index 0000000000..d71b967523 --- /dev/null +++ b/programs/bpf/rust/deprecated_loader/src/lib.rs @@ -0,0 +1,77 @@ +//! @brief Example Rust-based BPF program that supports the deprecated loader + +#![allow(unreachable_code)] + +extern crate solana_sdk; +use solana_sdk::{ + account_info::AccountInfo, bpf_loader, entrypoint_deprecated, + entrypoint_deprecated::ProgramResult, info, log::*, pubkey::Pubkey, +}; + +#[derive(Debug, PartialEq)] +struct SStruct { + x: u64, + y: u64, + z: u64, +} + +#[inline(never)] +fn return_sstruct() -> SStruct { + SStruct { x: 1, y: 2, z: 3 } +} + +entrypoint_deprecated!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + info!("Program identifier:"); + program_id.log(); + + assert!(!bpf_loader::check_id(program_id)); + + // Log the provided account keys and instruction input data. In the case of + // the no-op program, no account keys or input data are expected but real + // programs will have specific requirements so they can do their work. + info!("Account keys and instruction input data:"); + sol_log_params(accounts, instruction_data); + + { + // Test - use std methods, unwrap + + // valid bytes, in a stack-allocated array + let sparkle_heart = [240, 159, 146, 150]; + let result_str = std::str::from_utf8(&sparkle_heart).unwrap(); + assert_eq!(4, result_str.len()); + assert_eq!("💖", result_str); + info!(result_str); + } + + { + // Test - struct return + + let s = return_sstruct(); + assert_eq!(s.x + s.y + s.z, 6); + } + + { + // Test - arch config + #[cfg(not(target_arch = "bpf"))] + panic!(); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + // Pull in syscall stubs when building for non-BPF targets + solana_sdk::program_stubs!(); + + #[test] + fn test_return_sstruct() { + assert_eq!(SStruct { x: 1, y: 2, z: 3 }, return_sstruct()); + } +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index eae89781e3..4a98990849 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -11,7 +11,7 @@ use solana_runtime::{ }; use solana_sdk::{ account::Account, - bpf_loader, + bpf_loader, bpf_loader_deprecated, client::SyncClient, clock::DEFAULT_SLOTS_PER_EPOCH, entrypoint::MAX_PERMITTED_DATA_INCREASE, @@ -40,12 +40,17 @@ fn create_bpf_path(name: &str) -> PathBuf { pathbuf } -fn load_bpf_program(bank_client: &BankClient, payer_keypair: &Keypair, name: &str) -> Pubkey { +fn load_bpf_program( + bank_client: &BankClient, + loader_id: &Pubkey, + payer_keypair: &Keypair, + name: &str, +) -> Pubkey { let path = create_bpf_path(name); let mut file = File::open(path).unwrap(); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - load_program(bank_client, payer_keypair, &bpf_loader::id(), elf) + load_program(bank_client, payer_keypair, loader_id, elf) } #[test] @@ -101,7 +106,8 @@ fn test_program_bpf_sanity() { let bank_client = BankClient::new(bank); // Call user program - let program_id = load_bpf_program(&bank_client, &mint_keypair, program.0); + let program_id = + load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.0); let account_metas = vec![ AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(Keypair::new().pubkey(), false), @@ -122,6 +128,47 @@ fn test_program_bpf_sanity() { } } +#[test] +#[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] +fn test_program_bpf_loader_deprecated() { + solana_logger::setup(); + + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[("deprecated_loader")]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[("solana_bpf_rust_deprecated_loader")]); + } + + for program in programs.iter() { + println!("Test program: {:?}", program); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!(); + bank.add_builtin_loader(&name, id, entrypoint); + let bank_client = BankClient::new(bank); + + let program_id = load_bpf_program( + &bank_client, + &bpf_loader_deprecated::id(), + &mint_keypair, + program, + ); + let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; + let instruction = Instruction::new(program_id, &1u8, account_metas); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + } +} + #[test] fn test_program_bpf_duplicate_accounts() { solana_logger::setup(); @@ -149,7 +196,7 @@ fn test_program_bpf_duplicate_accounts() { bank.add_builtin_loader(&name, id, entrypoint); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); - let program_id = load_bpf_program(&bank_client, &mint_keypair, program); + let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let payee_account = Account::new(10, 1, &program_id); let payee_pubkey = Pubkey::new_rand(); bank.store_account(&payee_pubkey, &payee_account); @@ -233,7 +280,7 @@ fn test_program_bpf_error_handling() { let (name, id, entrypoint) = solana_bpf_loader_program!(); bank.add_builtin_loader(&name, id, entrypoint); let bank_client = BankClient::new(bank); - let program_id = load_bpf_program(&bank_client, &mint_keypair, program); + let program_id = load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); @@ -342,8 +389,10 @@ fn test_program_bpf_invoke() { let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); - let invoke_program_id = load_bpf_program(&bank_client, &mint_keypair, program.0); - let invoked_program_id = load_bpf_program(&bank_client, &mint_keypair, program.1); + let invoke_program_id = + load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.0); + let invoked_program_id = + load_bpf_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.1); let argument_keypair = Keypair::new(); let account = Account::new(42, 100, &invoke_program_id); diff --git a/sdk/bpf/c/inc/deserialize_deprecated.h b/sdk/bpf/c/inc/deserialize_deprecated.h new file mode 100644 index 0000000000..679be23040 --- /dev/null +++ b/sdk/bpf/c/inc/deserialize_deprecated.h @@ -0,0 +1,115 @@ +#pragma once +/** + * @brief Solana bpf_loader_deprecated deserializer to used when deploying + * a program with `BPFLoader1111111111111111111111111111111111` + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * De-serializes the input parameters into usable types + * + * Use this function to deserialize the buffer passed to the program entrypoint + * into usable types. This function does not perform copy deserialization, + * instead it populates the pointers and lengths in SolAccountInfo and data so + * that any modification to lamports or account data take place on the original + * buffer. Doing so also eliminates the need to serialize back into the buffer + * at the end of the program. + * + * @param input Source buffer containing serialized input parameters + * @param params Pointer to a SolParameters structure + * @return Boolean true if successful. + */ +static bool sol_deserialize_deprecated( + const uint8_t *input, + SolParameters *params, + uint64_t ka_num +) { + if (NULL == input || NULL == params) { + return false; + } + params->ka_num = *(uint64_t *) input; + input += sizeof(uint64_t); + + for (int i = 0; i < params->ka_num; i++) { + uint8_t dup_info = input[0]; + input += sizeof(uint8_t); + + if (i >= ka_num) { + if (dup_info == UINT8_MAX) { + input += sizeof(uint8_t); + input += sizeof(uint8_t); + input += sizeof(SolPubkey); + input += sizeof(uint64_t); + input += *(uint64_t *) input; + input += sizeof(uint64_t); + input += sizeof(SolPubkey); + input += sizeof(uint8_t); + input += sizeof(uint64_t); + } + continue; + } + if (dup_info == UINT8_MAX) { + // is signer? + params->ka[i].is_signer = *(uint8_t *) input != 0; + input += sizeof(uint8_t); + + // is writable? + params->ka[i].is_writable = *(uint8_t *) input != 0; + input += sizeof(uint8_t); + + // key + params->ka[i].key = (SolPubkey *) input; + input += sizeof(SolPubkey); + + // lamports + params->ka[i].lamports = (uint64_t *) input; + input += sizeof(uint64_t); + + // account data + params->ka[i].data_len = *(uint64_t *) input; + input += sizeof(uint64_t); + params->ka[i].data = (uint8_t *) input; + input += params->ka[i].data_len; + + // owner + params->ka[i].owner = (SolPubkey *) input; + input += sizeof(SolPubkey); + + // executable? + params->ka[i].executable = *(uint8_t *) input; + input += sizeof(uint8_t); + + // rent epoch + params->ka[i].rent_epoch = *(uint64_t *) input; + input += sizeof(uint64_t); + } else { + params->ka[i].is_signer = params->ka[dup_info].is_signer; + params->ka[i].key = params->ka[dup_info].key; + params->ka[i].lamports = params->ka[dup_info].lamports; + params->ka[i].data_len = params->ka[dup_info].data_len; + params->ka[i].data = params->ka[dup_info].data; + params->ka[i].owner = params->ka[dup_info].owner; + params->ka[i].executable = params->ka[dup_info].executable; + params->ka[i].rent_epoch = params->ka[dup_info].rent_epoch; + } + } + + params->data_len = *(uint64_t *) input; + input += sizeof(uint64_t); + params->data = input; + input += params->data_len; + + params->program_id = (SolPubkey *) input; + input += sizeof(SolPubkey); + + return true; +} + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/src/bpf_loader.rs b/sdk/src/bpf_loader.rs index cf6fa767b9..1bee67849b 100644 --- a/sdk/src/bpf_loader.rs +++ b/sdk/src/bpf_loader.rs @@ -1 +1,17 @@ +//! @brief The latest Solana BPF loader. +//! +//! The BPF loader is responsible for loading, finalizing, and executing BPF +//! programs. Not all networks may support the latest loader. You can use the +//! command-line tools to check if this version of the loader is supported by +//! requesting the account info for the public key below. +//! +//! The program format may change between loaders, and it is crucial to build +//! your program against the proper entrypoint semantics. All programs being +//! deployed to this BPF loader must build against the latest entrypoint version +//! located in `entrypoint.rs`. +//! +//! Note: Programs built for older loaders must use a matching entrypoint +//! version. An example is `bpf_loader_deprecated.rs` which requires +//! `entrypoint_deprecated.rs`. + crate::declare_id!("BPFLoader2111111111111111111111111111111111"); diff --git a/sdk/src/bpf_loader_deprecated.rs b/sdk/src/bpf_loader_deprecated.rs index 33461caa0a..55e58dbc2e 100644 --- a/sdk/src/bpf_loader_deprecated.rs +++ b/sdk/src/bpf_loader_deprecated.rs @@ -1 +1,14 @@ +//! @brief The original and now deprecated Solana BPF loader. +//! +//! The BPF loader is responsible for loading, finalizing, and executing BPF +//! programs. +//! +//! This loader is deprecated, and it is strongly encouraged to build for and +//! deploy to the latest BPF loader. For more information see `bpf_loader.rs` +//! +//! The program format may change between loaders, and it is crucial to build +//! your program against the proper entrypoint semantics. All programs being +//! deployed to this BPF loader must build against the deprecated entrypoint +//! version located in `entrypoint_deprecated.rs`. + crate::declare_id!("BPFLoader1111111111111111111111111111111111"); diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 2917bcda77..0c3bc0a362 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -1,4 +1,5 @@ -//! @brief Solana Rust-based BPF program entry point +//! @brief Solana Rust-based BPF program entry point supported by the latest +//! BPFLoader. For more information see './bpf_loader.rs' extern crate alloc; use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; diff --git a/sdk/src/entrypoint_deprecated.rs b/sdk/src/entrypoint_deprecated.rs new file mode 100644 index 0000000000..ac403ac8fb --- /dev/null +++ b/sdk/src/entrypoint_deprecated.rs @@ -0,0 +1,138 @@ +//! @brief Solana Rust-based BPF program entry point supported by the original +//! and now deprecated BPFLoader. For more information see +//! './bpf_loader_deprecated.rs' + +extern crate alloc; +use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; +use alloc::vec::Vec; +use std::{ + cell::RefCell, + mem::size_of, + rc::Rc, + // Hide Result from bindgen gets confused about generics in non-generic type declarations + result::Result as ResultGeneric, + slice::{from_raw_parts, from_raw_parts_mut}, +}; + +pub type ProgramResult = ResultGeneric<(), ProgramError>; + +/// User implemented function to process an instruction +/// +/// 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]) -> ProgramResult; + +/// Programs indicate success with a return value of 0 +pub const SUCCESS: u64 = 0; + +/// Declare the entry point of the program. +/// +/// Deserialize the program input arguments and call +/// the user defined `process_instruction` function. +/// Users must call this macro otherwise an entry point for +/// their program will not be created. +#[macro_export] +macro_rules! entrypoint_deprecated { + ($process_instruction:ident) => { + /// # Safety + #[cfg(not(feature = "skip-no-mangle"))] + #[no_mangle] + pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { + let (program_id, accounts, instruction_data) = + unsafe { $crate::entrypoint_deprecated::deserialize(input) }; + match $process_instruction(&program_id, &accounts, &instruction_data) { + Ok(()) => $crate::entrypoint_deprecated::SUCCESS, + Err(error) => error.into(), + } + } + }; +} + +/// Deserialize the input arguments +/// +/// # Safety +#[allow(clippy::type_complexity)] +pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec>, &'a [u8]) { + let mut offset: usize = 0; + + // Number of accounts present + + #[allow(clippy::cast_ptr_alignment)] + let num_accounts = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + // Account Infos + + let mut accounts = Vec::with_capacity(num_accounts); + for _ in 0..num_accounts { + let dup_info = *(input.add(offset) as *const u8); + offset += size_of::(); + if dup_info == std::u8::MAX { + #[allow(clippy::cast_ptr_alignment)] + let is_signer = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let is_writable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + let key: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64))); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let data = Rc::new(RefCell::new({ + from_raw_parts_mut(input.add(offset), data_len) + })); + offset += data_len; + + let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let executable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let rent_epoch = *(input.add(offset) as *const u64); + offset += size_of::(); + + accounts.push(AccountInfo { + is_signer, + is_writable, + key, + lamports, + data, + owner, + executable, + rent_epoch, + }); + } else { + // Duplicate account, clone the original + accounts.push(accounts[dup_info as usize].clone()); + } + } + + // Instruction data + + #[allow(clippy::cast_ptr_alignment)] + let instruction_data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) }; + offset += instruction_data_len; + + // Program Id + + let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey); + + (program_id, accounts, instruction_data) +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 6ff135d84e..2812b35471 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -75,6 +75,7 @@ pub use solana_sdk_macro::respan; // On-chain program specific modules pub mod account_info; pub mod entrypoint; +pub mod entrypoint_deprecated; pub mod log; pub mod program; pub mod program_error;