diff --git a/Cargo.lock b/Cargo.lock index 7fba057cdd..273fb65ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3943,6 +3943,7 @@ dependencies = [ "serde_json 1.0.49 (registry+https://github.com/rust-lang/crates.io-index)", "serial_test 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "serial_test_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-bpf-loader-program 1.2.0", "solana-budget-program 1.2.0", "solana-chacha-cuda 1.2.0", "solana-clap-utils 1.2.0", @@ -4495,7 +4496,6 @@ dependencies = [ "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", - "solana-bpf-loader-program 1.2.0", "solana-logger 1.2.0", "solana-measure 1.2.0", "solana-metrics 1.2.0", diff --git a/core/Cargo.toml b/core/Cargo.toml index 62d04080f2..5176615cef 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -41,6 +41,7 @@ regex = "1.3.6" serde = "1.0.105" serde_derive = "1.0.103" serde_json = "1.0.49" +solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.0" } solana-budget-program = { path = "../programs/budget", version = "1.2.0" } solana-clap-utils = { path = "../clap-utils", version = "1.2.0" } solana-client = { path = "../client", version = "1.2.0" } diff --git a/core/src/lib.rs b/core/src/lib.rs index 3bf1d06a6f..c594787dbd 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,6 +58,9 @@ pub mod verified_vote_packets; pub mod weighted_shuffle; pub mod window_service; +#[macro_use] +extern crate solana_bpf_loader_program; + #[macro_use] extern crate solana_budget_program; diff --git a/core/src/validator.rs b/core/src/validator.rs index 413c199e02..eebcb3fe46 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -714,6 +714,9 @@ impl TestValidator { genesis_config .native_instruction_processors .push(solana_budget_program!()); + genesis_config + .native_instruction_processors + .push(solana_bpf_loader_program!()); genesis_config.rent.lamports_per_byte_year = 1; genesis_config.rent.exemption_threshold = 1.0; diff --git a/genesis-programs/src/lib.rs b/genesis-programs/src/lib.rs index 543e870bd7..24654ec9a8 100644 --- a/genesis-programs/src/lib.rs +++ b/genesis-programs/src/lib.rs @@ -1,6 +1,7 @@ use solana_sdk::{ clock::Epoch, genesis_config::OperatingMode, inflation::Inflation, - move_loader::solana_move_loader_program, pubkey::Pubkey, system_program::solana_system_program, + move_loader::solana_move_loader_program, native_loader, pubkey::Pubkey, + system_program::solana_system_program, }; #[macro_use] @@ -49,7 +50,10 @@ pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option Option> { +pub fn get_programs( + operating_mode: OperatingMode, + epoch: Epoch, +) -> Option> { match operating_mode { OperatingMode::Development => { if epoch == 0 { @@ -122,9 +126,9 @@ pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpoch bank.set_inflation(inflation); } if let Some(new_programs) = get_programs(operating_mode, bank.epoch()) { - for (name, program_id) in new_programs.iter() { - info!("Registering {} at {}", name, program_id); - bank.register_native_instruction_processor(name, program_id); + for (info, program_id) in new_programs.iter() { + info!("Registering {:?} at {}", info, program_id); + bank.register_native_instruction_processor(info, program_id); } } }) diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 5935142495..7f8f3c8dcb 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -20,6 +20,7 @@ use solana_sdk::{ epoch_schedule::EpochSchedule, genesis_config::{GenesisConfig, OperatingMode}, message::Message, + native_loader, poh_config::PohConfig, pubkey::Pubkey, signature::{Keypair, Signer}, @@ -81,7 +82,7 @@ pub struct ClusterConfig { pub slots_per_epoch: u64, pub slots_per_segment: u64, pub stakers_slot_offset: u64, - pub native_instruction_processors: Vec<(String, Pubkey)>, + pub native_instruction_processors: Vec<(native_loader::Info, Pubkey)>, pub operating_mode: OperatingMode, pub poh_config: PohConfig, } @@ -179,14 +180,13 @@ impl LocalCluster { .push(solana_storage_program!()); } } - - genesis_config.inflation = - solana_genesis_programs::get_inflation(genesis_config.operating_mode, 0).unwrap(); - genesis_config .native_instruction_processors .extend_from_slice(&config.native_instruction_processors); + genesis_config.inflation = + solana_genesis_programs::get_inflation(genesis_config.operating_mode, 0).unwrap(); + let storage_keypair = Keypair::new(); genesis_config.add_account( storage_keypair.pubkey(), diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index fda5c0faf5..ec230846c9 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1980,7 +1980,6 @@ dependencies = [ "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", - "solana-bpf-loader-program 1.2.0", "solana-logger 1.2.0", "solana-measure 1.2.0", "solana-metrics 1.2.0", diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 3be6f7976a..6216e2bbb2 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -18,6 +18,7 @@ mod bpf { sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, transaction::TransactionError, }; + use solana_bpf_loader_program::solana_bpf_loader_program; use std::{env, fs::File, io::Read, path::PathBuf, sync::Arc}; /// BPF program file extension @@ -83,10 +84,11 @@ mod bpf { println!("Test program: {:?}", program.0); let GenesisConfigInfo { - genesis_config, + mut genesis_config, mint_keypair, .. } = create_genesis_config(50); + genesis_config.add_native_instruction_processor(solana_bpf_loader_program!()); let bank = Arc::new(Bank::new(&genesis_config)); // Create bank with specific slot, used by solana_bpf_rust_sysvar test let bank = @@ -133,10 +135,11 @@ mod bpf { println!("Test program: {:?}", program); let GenesisConfigInfo { - genesis_config, + mut genesis_config, mint_keypair, .. } = create_genesis_config(50); + genesis_config.add_native_instruction_processor(solana_bpf_loader_program!()); let bank = Arc::new(Bank::new(&genesis_config)); let bank_client = BankClient::new_shared(&bank); let program_id = load_bpf_program(&bank_client, &mint_keypair, program); @@ -215,10 +218,11 @@ mod bpf { println!("Test program: {:?}", program); let GenesisConfigInfo { - genesis_config, + mut genesis_config, mint_keypair, .. } = create_genesis_config(50); + genesis_config.add_native_instruction_processor(solana_bpf_loader_program!()); let bank = Bank::new(&genesis_config); let bank_client = BankClient::new(bank); let program_id = load_bpf_program(&bank_client, &mint_keypair, program); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 226e727520..77486e2c2b 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -25,7 +25,7 @@ use solana_sdk::{ use std::{io::prelude::*, mem}; use thiserror::Error; -solana_sdk::declare_program!( +solana_sdk::declare_loader!( solana_sdk::bpf_loader::ID, solana_bpf_loader_program, process_instruction diff --git a/programs/failure/tests/failure.rs b/programs/failure/tests/failure.rs index 0741f26708..b001aff425 100644 --- a/programs/failure/tests/failure.rs +++ b/programs/failure/tests/failure.rs @@ -4,6 +4,7 @@ use solana_runtime::loader_utils::create_invoke_instruction; use solana_sdk::client::SyncClient; use solana_sdk::genesis_config::create_genesis_config; use solana_sdk::instruction::InstructionError; +use solana_sdk::native_program_info; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signer; use solana_sdk::transaction::TransactionError; @@ -13,7 +14,10 @@ fn test_program_native_failure() { let (genesis_config, alice_keypair) = create_genesis_config(50); let program_id = Pubkey::new_rand(); let bank = Bank::new(&genesis_config); - bank.register_native_instruction_processor("solana_failure_program", &program_id); + bank.register_native_instruction_processor( + &native_program_info!("solana_failure_program"), + &program_id, + ); // Call user program let instruction = create_invoke_instruction(alice_keypair.pubkey(), program_id, &1u8); diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index b8817b0654..0bc0eb50a0 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -27,7 +27,6 @@ rand = "0.6.5" rayon = "1.3.0" serde = { version = "1.0.105", features = ["rc"] } serde_derive = "1.0.103" -solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.0" } solana-logger = { path = "../logger", version = "1.2.0" } solana-measure = { path = "../measure", version = "1.2.0" } solana-metrics = { path = "../metrics", version = "1.2.0" } diff --git a/runtime/benches/bank.rs b/runtime/benches/bank.rs index 1c5f82879b..56a6234267 100644 --- a/runtime/benches/bank.rs +++ b/runtime/benches/bank.rs @@ -11,6 +11,7 @@ use solana_sdk::{ clock::MAX_RECENT_BLOCKHASHES, genesis_config::create_genesis_config, instruction::InstructionError, + native_program_info, pubkey::Pubkey, signature::{Keypair, Signer}, transaction::Transaction, @@ -124,7 +125,7 @@ fn do_bench_transactions( let mut bank = Bank::new(&genesis_config); bank.add_instruction_processor(Pubkey::new(&BUILTIN_PROGRAM_ID), process_instruction); bank.register_native_instruction_processor( - "solana_noop_program", + &native_program_info!("solana_noop_program"), &Pubkey::new(&NOOP_PROGRAM_ID), ); let bank = Arc::new(bank); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 1babbb3500..604a006c0c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -39,7 +39,8 @@ use solana_sdk::{ hard_forks::HardForks, hash::{extend_and_hash, hashv, Hash}, inflation::Inflation, - native_loader, nonce, + native_loader::{self, create_loadable_account}, + native_program_info, nonce, pubkey::Pubkey, signature::{Keypair, Signature}, slot_hashes::SlotHashes, @@ -898,14 +899,18 @@ impl Bank { ); // Add additional native programs specified in the genesis config - for (name, program_id) in &genesis_config.native_instruction_processors { - self.register_native_instruction_processor(name, program_id); + for (info, program_id) in &genesis_config.native_instruction_processors { + self.register_native_instruction_processor(&info, program_id); } } - pub fn register_native_instruction_processor(&self, name: &str, program_id: &Pubkey) { - debug!("Adding native program {} under {:?}", name, program_id); - let account = native_loader::create_loadable_account(name); + pub fn register_native_instruction_processor( + &self, + info: &native_loader::Info, + program_id: &Pubkey, + ) { + debug!("Adding {:?} under {:?}", info, program_id); + let account = create_loadable_account(info); self.store_account(program_id, &account); } @@ -2155,7 +2160,7 @@ impl Bank { assert_eq!(program_account.owner, solana_sdk::native_loader::id()); } else { // Register a bogus executable account, which will be loaded and ignored. - self.register_native_instruction_processor("", &program_id); + self.register_native_instruction_processor(&native_program_info!(""), &program_id); } } diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 2accac24b6..51ce8c1a41 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -136,7 +136,6 @@ pub fn create_genesis_config_with_leader_ex( // Bare minimum program set let native_instruction_processors = vec![ solana_system_program(), - solana_bpf_loader_program!(), solana_vote_program!(), solana_stake_program!(), ]; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index bec77dc00f..c32ce2e923 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -30,9 +30,6 @@ extern crate solana_vote_program; #[macro_use] extern crate solana_stake_program; -#[macro_use] -extern crate solana_bpf_loader_program; - #[macro_use] extern crate serde_derive; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 8b739f1046..8b72a5dcae 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -1,22 +1,18 @@ -use crate::{native_loader, rent_collector::RentCollector, system_instruction_processor}; +use crate::{ + native_loader::NativeLoader, rent_collector::RentCollector, system_instruction_processor, +}; use serde::{Deserialize, Serialize}; use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, clock::Epoch, - entrypoint_native, instruction::{CompiledInstruction, InstructionError}, message::Message, - native_loader::id as native_loader_id, + native_loader, pubkey::Pubkey, system_program, transaction::TransactionError, }; -use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::RwLock}; - -#[cfg(unix)] -use libloading::os::unix::*; -#[cfg(windows)] -use libloading::os::windows::*; +use std::{cell::RefCell, rc::Rc}; // The relevant state of an account before an Instruction executes, used // to verify account integrity after the Instruction completes @@ -159,14 +155,13 @@ impl PreAccount { } pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>; -pub type SymbolCache = RwLock, Symbol>>; #[derive(Serialize, Deserialize)] pub struct MessageProcessor { #[serde(skip)] instruction_processors: Vec<(Pubkey, ProcessInstruction)>, #[serde(skip)] - symbol_cache: SymbolCache, + native_loader: NativeLoader, } impl Default for MessageProcessor { fn default() -> Self { @@ -177,7 +172,7 @@ impl Default for MessageProcessor { Self { instruction_processors, - symbol_cache: RwLock::new(HashMap::new()), + native_loader: NativeLoader::default(), } } } @@ -218,10 +213,10 @@ impl MessageProcessor { }) .collect(); keyed_accounts.append(&mut keyed_accounts2); - assert!(keyed_accounts[0].executable()?, "account not executable"); - let root_program_id = keyed_accounts[0].unsigned_key(); + for (id, process_instruction) in &self.instruction_processors { + let root_program_id = keyed_accounts[0].unsigned_key(); if id == root_program_id { return process_instruction( &root_program_id, @@ -231,12 +226,15 @@ impl MessageProcessor { } } - native_loader::invoke_entrypoint( - &native_loader_id(), - &keyed_accounts, - &instruction.data, - &self.symbol_cache, - ) + if native_loader::check_id(&keyed_accounts[0].owner()?) { + self.native_loader.process_instruction( + &native_loader::id(), + &keyed_accounts, + &instruction.data, + ) + } else { + Err(InstructionError::UnsupportedProgramId) + } } /// Record the initial state of the accounts so that they can be compared @@ -372,6 +370,7 @@ mod tests { instruction::{AccountMeta, Instruction, InstructionError}, message::Message, native_loader::create_loadable_account, + native_program_info, }; #[test] @@ -917,7 +916,9 @@ mod tests { accounts.push(account); let mut loaders: Vec)>> = Vec::new(); - let account = RefCell::new(create_loadable_account("mock_system_program")); + let account = RefCell::new(create_loadable_account(&native_program_info!( + "mock_system_program" + ))); loaders.push(vec![(mock_system_program_id, account)]); let from_pubkey = Pubkey::new_rand(); @@ -1040,7 +1041,9 @@ mod tests { accounts.push(account); let mut loaders: Vec)>> = Vec::new(); - let account = RefCell::new(create_loadable_account("mock_system_program")); + let account = RefCell::new(create_loadable_account(&native_program_info!( + "mock_system_program" + ))); loaders.push(vec![(mock_program_id, account)]); let from_pubkey = Pubkey::new_rand(); diff --git a/runtime/src/native_loader.rs b/runtime/src/native_loader.rs index 443902abf4..8074b857c1 100644 --- a/runtime/src/native_loader.rs +++ b/runtime/src/native_loader.rs @@ -1,29 +1,30 @@ //! Native loader -use crate::message_processor::SymbolCache; #[cfg(unix)] use libloading::os::unix::*; #[cfg(windows)] use libloading::os::windows::*; use log::*; use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::FromPrimitive; use solana_sdk::{ account::KeyedAccount, - entrypoint_native, + entrypoint_native::{LoaderEntrypoint, ProgramEntrypoint}, instruction::InstructionError, + native_loader::Kind, program_utils::{next_keyed_account, DecodeError}, pubkey::Pubkey, }; -use std::{env, path::PathBuf, str}; +use std::{collections::HashMap, env, path::PathBuf, str, sync::RwLock}; use thiserror::Error; #[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)] pub enum NativeLoaderError { #[error("Entrypoint name in the account data is not a valid UTF-8 string")] - InvalidEntrypointName, + InvalidEntrypointName = 0x0aaa_0001, #[error("Entrypoint was not found in the module")] - EntrypointNotFound, + EntrypointNotFound = 0x0aaa_0002, #[error("Failed to load the module")] - FailedToLoad, + FailedToLoad = 0x0aaa_0003, } impl DecodeError for NativeLoaderError { fn type_of() -> &'static str { @@ -33,103 +34,130 @@ impl DecodeError for NativeLoaderError { /// Dynamic link library prefixes #[cfg(unix)] -const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib"; +const PLATFORM_FILE_PREFIX: &str = "lib"; #[cfg(windows)] -const PLATFORM_FILE_PREFIX_NATIVE: &str = ""; +const PLATFORM_FILE_PREFIX: &str = ""; /// Dynamic link library file extension specific to the platform #[cfg(any(target_os = "macos", target_os = "ios"))] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib"; +const PLATFORM_FILE_EXTENSION: &str = "dylib"; /// Dynamic link library file extension specific to the platform #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so"; +const PLATFORM_FILE_EXTENSION: &str = "so"; /// Dynamic link library file extension specific to the platform #[cfg(windows)] -const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll"; +const PLATFORM_FILE_EXTENSION: &str = "dll"; -fn create_path(name: &str) -> PathBuf { - let current_exe = env::current_exe() - .unwrap_or_else(|e| panic!("create_path(\"{}\"): current exe not found: {:?}", name, e)); - let current_exe_directory = PathBuf::from(current_exe.parent().unwrap_or_else(|| { - panic!( - "create_path(\"{}\"): no parent directory of {:?}", - name, current_exe, - ) - })); +pub type ProgramSymbolCache = RwLock>>; +pub type LoaderSymbolCache = RwLock>>; - let library_file_name = PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name) - .with_extension(PLATFORM_FILE_EXTENSION_NATIVE); - - // Check the current_exe directory for the library as `cargo tests` are run - // from the deps/ subdirectory - let file_path = current_exe_directory.join(&library_file_name); - if file_path.exists() { - file_path - } else { - // `cargo build` places dependent libraries in the deps/ subdirectory - current_exe_directory.join("deps").join(library_file_name) - } +#[derive(Debug, Default)] +pub struct NativeLoader { + program_symbol_cache: ProgramSymbolCache, + loader_symbol_cache: LoaderSymbolCache, } +impl NativeLoader { + fn create_path(name: &str) -> PathBuf { + let current_exe = env::current_exe().unwrap_or_else(|e| { + panic!("create_path(\"{}\"): current exe not found: {:?}", name, e) + }); + let current_exe_directory = PathBuf::from(current_exe.parent().unwrap_or_else(|| { + panic!( + "create_path(\"{}\"): no parent directory of {:?}", + name, current_exe, + ) + })); -#[cfg(windows)] -fn library_open(path: &PathBuf) -> std::io::Result { - Library::new(path) -} + let library_file_name = PathBuf::from(PLATFORM_FILE_PREFIX.to_string() + name) + .with_extension(PLATFORM_FILE_EXTENSION); -#[cfg(not(windows))] -fn library_open(path: &PathBuf) -> std::io::Result { - // Linux tls bug can cause crash on dlclose(), workaround by never unloading - Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW) -} - -pub fn invoke_entrypoint( - _program_id: &Pubkey, - keyed_accounts: &[KeyedAccount], - instruction_data: &[u8], - symbol_cache: &SymbolCache, -) -> Result<(), InstructionError> { - let mut keyed_accounts_iter = keyed_accounts.iter(); - let program = next_keyed_account(&mut keyed_accounts_iter)?; - let params = keyed_accounts_iter.as_slice(); - let name_vec = &program.try_account_ref()?.data; - if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) { - unsafe { - return entrypoint(program.unsigned_key(), params, instruction_data); + // Check the current_exe directory for the library as `cargo tests` are run + // from the deps/ subdirectory + let file_path = current_exe_directory.join(&library_file_name); + if file_path.exists() { + file_path + } else { + // `cargo build` places dependent libraries in the deps/ subdirectory + current_exe_directory.join("deps").join(library_file_name) } } - let name = match str::from_utf8(name_vec) { - Ok(v) => v, - Err(e) => { - warn!("Invalid UTF-8 sequence: {}", e); - return Err(NativeLoaderError::InvalidEntrypointName.into()); - } - }; - trace!("Call native {:?}", name); - let path = create_path(&name); - match library_open(&path) { - Ok(library) => unsafe { - let entrypoint: Symbol = - match library.get(name.as_bytes()) { - Ok(s) => s, - Err(e) => { - warn!( - "Unable to find entrypoint {:?} (error: {:?})", - name.as_bytes(), - e - ); - return Err(NativeLoaderError::EntrypointNotFound.into()); + + #[cfg(windows)] + fn library_open(path: &PathBuf) -> std::io::Result { + Library::new(path) + } + + #[cfg(not(windows))] + fn library_open(path: &PathBuf) -> std::io::Result { + // Linux tls bug can cause crash on dlclose(), workaround by never unloading + Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW) + } + + fn invoke_entrypoint( + name: &str, + cache: &RwLock>>, + ) -> Result, InstructionError> { + let mut cache = cache.write().unwrap(); + if let Some(entrypoint) = cache.get(name) { + Ok(entrypoint.clone()) + } else { + match Self::library_open(&Self::create_path(&name)) { + Ok(library) => { + let result = unsafe { library.get::(name.as_bytes()) }; + match result { + Ok(entrypoint) => { + cache.insert(name.to_string(), entrypoint.clone()); + Ok(entrypoint) + } + Err(e) => { + warn!("Unable to find program entrypoint in {:?}: {:?})", name, e); + Err(NativeLoaderError::EntrypointNotFound.into()) + } } - }; - let ret = entrypoint(program.unsigned_key(), params, instruction_data); - symbol_cache - .write() - .unwrap() - .insert(name_vec.to_vec(), entrypoint); - ret - }, - Err(e) => { - warn!("Failed to load: {:?}", e); - Err(NativeLoaderError::FailedToLoad.into()) + } + Err(e) => { + warn!("Failed to load: {:?}", e); + Err(NativeLoaderError::FailedToLoad.into()) + } + } + } + } + + pub fn process_instruction( + &self, + _program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + ) -> Result<(), InstructionError> { + let mut keyed_accounts_iter = keyed_accounts.iter(); + let program = next_keyed_account(&mut keyed_accounts_iter)?; + let params = keyed_accounts_iter.as_slice(); + let data = &program.try_account_ref()?.data; + let program_kind = FromPrimitive::from_u8(data[0]); + let name = match str::from_utf8(&data[1..]) { + Ok(v) => v, + Err(e) => { + warn!("Invalid UTF-8 sequence: {}", e); + return Err(NativeLoaderError::InvalidEntrypointName.into()); + } + }; + + trace!("Call native {:?}: {:?}", program_kind, name); + match program_kind { + Some(Kind::Program) => { + let entrypoint = + Self::invoke_entrypoint::(name, &self.program_symbol_cache)?; + unsafe { entrypoint(program.unsigned_key(), params, instruction_data) } + } + Some(Kind::Loader) => { + let entrypoint = + Self::invoke_entrypoint::(name, &self.loader_symbol_cache)?; + unsafe { entrypoint(program.unsigned_key(), params, instruction_data) } + } + None => { + warn!("Invalid native type: {:?}", data[0]); + Err(NativeLoaderError::FailedToLoad.into()) + } } } } diff --git a/runtime/tests/noop.rs b/runtime/tests/noop.rs index eae9c0704d..ca80d5789f 100644 --- a/runtime/tests/noop.rs +++ b/runtime/tests/noop.rs @@ -2,7 +2,8 @@ use solana_runtime::{ bank::Bank, bank_client::BankClient, loader_utils::create_invoke_instruction, }; use solana_sdk::{ - client::SyncClient, genesis_config::create_genesis_config, pubkey::Pubkey, signature::Signer, + client::SyncClient, genesis_config::create_genesis_config, native_program_info, pubkey::Pubkey, + signature::Signer, }; #[test] @@ -12,7 +13,10 @@ fn test_program_native_noop() { let (genesis_config, alice_keypair) = create_genesis_config(50); let program_id = Pubkey::new_rand(); let bank = Bank::new(&genesis_config); - bank.register_native_instruction_processor("solana_noop_program", &program_id); + bank.register_native_instruction_processor( + &native_program_info!("solana_noop_program"), + &program_id, + ); // Call user program let instruction = create_invoke_instruction(alice_keypair.pubkey(), program_id, &1u8); diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index 4ad5bb3342..585d7c0231 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -7,7 +7,19 @@ use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey /// program_id: Program ID of the currently executing program /// keyed_accounts: Accounts passed as part of the instruction /// instruction_data: Instruction data -pub type Entrypoint = unsafe extern "C" fn( +pub type ProgramEntrypoint = unsafe extern "C" fn( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], +) -> Result<(), InstructionError>; + +// Prototype of a native loader entry point +/// +/// program_id: Program ID of the currently executing program +/// keyed_accounts: Accounts passed as part of the instruction +/// instruction_data: Instruction data +/// invoke_context: Invocation context +pub type LoaderEntrypoint = unsafe extern "C" fn( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], instruction_data: &[u8], @@ -89,7 +101,32 @@ macro_rules! declare_program( #[macro_export] macro_rules! $name { () => { - (stringify!($name).to_string(), $crate::id()) + (solana_sdk::native_program_info!(stringify!($name).to_string()), $crate::id()) + }; + } + + #[no_mangle] + pub extern "C" fn $name( + program_id: &$crate::pubkey::Pubkey, + keyed_accounts: &[$crate::account::KeyedAccount], + instruction_data: &[u8], + ) -> Result<(), $crate::instruction::InstructionError> { + $entrypoint(program_id, keyed_accounts, instruction_data) + } + ) +); + +/// Same as declare_program but for native loaders +#[macro_export] +macro_rules! declare_loader( + ($bs58_string:expr, $name:ident, $entrypoint:expr) => ( + $crate::declare_id!($bs58_string); + + #[macro_export] + macro_rules! $name { + () => { + (solana_sdk::native_loader_info!(stringify!($name).to_string()), $crate::id()) + }; } diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 09e3737679..bebd77ae3f 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -7,6 +7,7 @@ use crate::{ fee_calculator::FeeRateGovernor, hash::{hash, Hash}, inflation::Inflation, + native_loader, native_token::lamports_to_sol, poh_config::PohConfig, pubkey::Pubkey, @@ -41,7 +42,7 @@ pub struct GenesisConfig { /// initial accounts pub accounts: BTreeMap, /// built-in programs - pub native_instruction_processors: Vec<(String, Pubkey)>, + pub native_instruction_processors: Vec<(native_loader::Info, Pubkey)>, /// accounts for network rewards, these do not count towards capitalization pub rewards_pools: BTreeMap, pub ticks_per_slot: u64, @@ -104,7 +105,7 @@ impl Default for GenesisConfig { impl GenesisConfig { pub fn new( accounts: &[(Pubkey, Account)], - native_instruction_processors: &[(String, Pubkey)], + native_instruction_processors: &[(native_loader::Info, Pubkey)], ) -> Self { Self { accounts: accounts @@ -172,8 +173,8 @@ impl GenesisConfig { self.accounts.insert(pubkey, account); } - pub fn add_native_instruction_processor(&mut self, name: String, program_id: Pubkey) { - self.native_instruction_processors.push((name, program_id)); + pub fn add_native_instruction_processor(&mut self, processor: (native_loader::Info, Pubkey)) { + self.native_instruction_processors.push(processor); } pub fn add_rewards_pool(&mut self, pubkey: Pubkey, account: Account) { @@ -230,6 +231,7 @@ impl fmt::Display for GenesisConfig { mod tests { use super::*; use crate::signature::{Keypair, Signer}; + use solana_sdk::native_program_info; use std::path::PathBuf; fn make_tmp_path(name: &str) -> PathBuf { @@ -261,7 +263,10 @@ mod tests { Account::new(10_000, 0, &Pubkey::default()), ); config.add_account(Pubkey::new_rand(), Account::new(1, 0, &Pubkey::default())); - config.add_native_instruction_processor("hi".to_string(), Pubkey::new_rand()); + config.add_native_instruction_processor(( + native_program_info!("hi".to_string()), + Pubkey::new_rand(), + )); assert_eq!(config.accounts.len(), 2); assert!(config diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index 6d1cd3b3a4..ea3862f133 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -134,6 +134,10 @@ pub enum InstructionError { /// Executable accounts must be rent exempt #[error("executable accounts must be rent exempt")] ExecutableAccountNotRentExempt, + + /// Unsupported program id + #[error("Unsupported program id")] + UnsupportedProgramId, } impl InstructionError { diff --git a/sdk/src/move_loader.rs b/sdk/src/move_loader.rs index d89c077d62..7d871177c1 100644 --- a/sdk/src/move_loader.rs +++ b/sdk/src/move_loader.rs @@ -1,5 +1,10 @@ +use crate::{native_loader, native_program_info, pubkey::Pubkey}; + crate::declare_id!("MoveLdr111111111111111111111111111111111111"); -pub fn solana_move_loader_program() -> (String, crate::pubkey::Pubkey) { - ("solana_move_loader_program".to_string(), id()) +pub fn solana_move_loader_program() -> (native_loader::Info, Pubkey) { + ( + native_program_info!("solana_move_loader_program".to_string()), + id(), + ) } diff --git a/sdk/src/native_loader.rs b/sdk/src/native_loader.rs index e7bdff6d4b..f2386c7e76 100644 --- a/sdk/src/native_loader.rs +++ b/sdk/src/native_loader.rs @@ -1,13 +1,46 @@ use crate::{account::Account, hash::Hash}; +use num_derive::FromPrimitive; crate::declare_id!("NativeLoader1111111111111111111111111111111"); +#[derive(Debug, Clone, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Info { + pub kind: Kind, + pub name: String, +} +#[derive(Debug, Clone, Copy, Deserialize, Eq, FromPrimitive, Hash, PartialEq, Serialize)] +pub enum Kind { + Program = 1, + Loader = 2, +} + +#[macro_export] +macro_rules! native_program_info( + ($name:expr) => ( + $crate::native_loader::Info { + kind: $crate::native_loader::Kind::Program, + name: $name.to_string(), + } + ) +); +#[macro_export] +macro_rules! native_loader_info( + ($name:expr) => ( + $crate::native_loader::Info { + kind: $crate::native_loader::Kind::Loader, + name: $name.to_string(), + } + ) +); + /// Create an executable account with the given shared object name. -pub fn create_loadable_account(name: &str) -> Account { +pub fn create_loadable_account(info: &Info) -> Account { + let mut data = vec![info.kind as u8]; + data.extend_from_slice(info.name.as_bytes()); Account { lamports: 1, owner: id(), - data: name.as_bytes().to_vec(), + data, executable: true, rent_epoch: 0, hash: Hash::default(), diff --git a/sdk/src/system_program.rs b/sdk/src/system_program.rs index 8b1ce46811..81624779fc 100644 --- a/sdk/src/system_program.rs +++ b/sdk/src/system_program.rs @@ -1,5 +1,10 @@ +use crate::{native_loader, native_program_info, pubkey::Pubkey}; + crate::declare_id!("11111111111111111111111111111111"); -pub fn solana_system_program() -> (String, crate::pubkey::Pubkey) { - ("solana_system_program".to_string(), id()) +pub fn solana_system_program() -> (native_loader::Info, Pubkey) { + ( + native_program_info!("solana_system_program".to_string()), + id(), + ) }