From 7c736f71fe08ef3bdb26b6b7a9e9766b01d53f3b Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 14 Aug 2020 12:32:45 -0700 Subject: [PATCH] Make BPF Loader static (#11516) --- Cargo.lock | 2 - core/Cargo.toml | 1 - core/src/lib.rs | 3 - core/src/validator.rs | 7 - genesis-programs/src/lib.rs | 106 +++- genesis/src/main.rs | 2 +- ledger/src/blockstore_processor.rs | 10 +- local-cluster/src/local_cluster.rs | 2 +- programs/bpf/Cargo.lock | 1 - programs/bpf/tests/programs.rs | 815 +++++++++++++------------- programs/bpf_loader/Cargo.toml | 3 +- programs/bpf_loader/src/deprecated.rs | 7 +- programs/bpf_loader/src/lib.rs | 14 +- runtime/src/bank.rs | 74 ++- runtime/src/builtin_programs.rs | 54 -- runtime/src/builtins.rs | 39 ++ runtime/src/lib.rs | 2 +- runtime/src/message_processor.rs | 20 +- sdk/src/builtins.rs | 140 +++++ sdk/src/entrypoint_native.rs | 30 +- sdk/src/lib.rs | 1 + 21 files changed, 756 insertions(+), 577 deletions(-) delete mode 100644 runtime/src/builtin_programs.rs create mode 100644 runtime/src/builtins.rs create mode 100644 sdk/src/builtins.rs diff --git a/Cargo.lock b/Cargo.lock index 9ab33ced9..97f3184b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3378,7 +3378,6 @@ version = "1.4.0" dependencies = [ "bincode", "byteorder", - "jemalloc-sys", "num-derive 0.3.0", "num-traits", "rand 0.7.3", @@ -3556,7 +3555,6 @@ dependencies = [ "serial_test_derive", "solana-account-decoder", "solana-banks-server", - "solana-bpf-loader-program", "solana-clap-utils", "solana-client", "solana-faucet", diff --git a/core/Cargo.toml b/core/Cargo.toml index 045bc8f41..044f8d273 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -45,7 +45,6 @@ serde_derive = "1.0.103" serde_json = "1.0.56" solana-account-decoder = { path = "../account-decoder", version = "1.4.0" } solana-banks-server = { path = "../banks-server", version = "1.4.0" } -solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.4.0" } solana-clap-utils = { path = "../clap-utils", version = "1.4.0" } solana-client = { path = "../client", version = "1.4.0" } solana-faucet = { path = "../faucet", version = "1.4.0" } diff --git a/core/src/lib.rs b/core/src/lib.rs index ddaaaae07..9649c622d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -72,9 +72,6 @@ pub mod vote_stake_tracker; pub mod weighted_shuffle; pub mod window_service; -#[macro_use] -extern crate solana_bpf_loader_program; - #[macro_use] extern crate log; diff --git a/core/src/validator.rs b/core/src/validator.rs index e458ba00f..a889d960a 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -868,13 +868,6 @@ impl TestValidator { 42, bootstrap_validator_lamports, ); - genesis_config - .native_instruction_processors - .push(solana_bpf_loader_program!()); - genesis_config - .native_instruction_processors - .push(solana_bpf_loader_deprecated_program!()); - genesis_config.rent.lamports_per_byte_year = 1; genesis_config.rent.exemption_threshold = 1.0; genesis_config.fee_rate_governor = FeeRateGovernor::new(fees, 0); diff --git a/genesis-programs/src/lib.rs b/genesis-programs/src/lib.rs index 86d2935d3..575f4d34e 100644 --- a/genesis-programs/src/lib.rs +++ b/genesis-programs/src/lib.rs @@ -1,7 +1,3 @@ -use solana_sdk::{ - clock::Epoch, genesis_config::OperatingMode, inflation::Inflation, pubkey::Pubkey, -}; - #[macro_use] extern crate solana_bpf_loader_program; #[macro_use] @@ -13,6 +9,10 @@ extern crate solana_vest_program; use log::*; use solana_runtime::bank::{Bank, EnteredEpochCallback}; +use solana_sdk::{ + clock::Epoch, entrypoint_native::ProcessInstructionWithContext, genesis_config::OperatingMode, + inflation::Inflation, pubkey::Pubkey, +}; pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option { match operating_mode { @@ -44,22 +44,27 @@ pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option Option> { +enum Program { + Native((String, Pubkey)), + BuiltinLoader((String, Pubkey, ProcessInstructionWithContext)), +} + +fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option> { match operating_mode { OperatingMode::Development => { if epoch == 0 { // Programs used for testing Some(vec![ - solana_bpf_loader_program!(), - solana_bpf_loader_deprecated_program!(), - solana_vest_program!(), - solana_budget_program!(), - solana_exchange_program!(), + Program::BuiltinLoader(solana_bpf_loader_program!()), + Program::BuiltinLoader(solana_bpf_loader_deprecated_program!()), + Program::Native(solana_vest_program!()), + Program::Native(solana_budget_program!()), + Program::Native(solana_exchange_program!()), ]) } else if epoch == std::u64::MAX { // The epoch of std::u64::MAX is a placeholder and is expected // to be reduced in a future network update. - Some(vec![solana_bpf_loader_program!()]) + Some(vec![Program::BuiltinLoader(solana_bpf_loader_program!())]) } else { None } @@ -68,7 +73,10 @@ pub fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option Option Option Option> { + match get_programs(operating_mode, epoch) { + Some(programs) => { + let mut native_programs = vec![]; + for program in programs { + if let Program::Native((string, key)) = program { + native_programs.push((string, key)); + } + } + Some(native_programs) + } + None => None, + } +} + pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpochCallback { Box::new(move |bank: &mut Bank| { - info!( - "Entering epoch {} with operating_mode {:?}", - bank.epoch(), - operating_mode - ); if let Some(inflation) = get_inflation(operating_mode, bank.epoch()) { info!("Entering new epoch with inflation {:?}", inflation); 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.add_native_program(name, program_id); + if let Some(programs) = get_programs(operating_mode, bank.epoch()) { + for program in programs { + match program { + Program::Native((name, program_id)) => { + bank.add_native_program(&name, &program_id); + } + Program::BuiltinLoader(( + name, + program_id, + process_instruction_with_context, + )) => { + bank.add_builtin_loader( + &name, + program_id, + process_instruction_with_context, + ); + } + } } } if OperatingMode::Stable == operating_mode { @@ -118,8 +156,13 @@ mod tests { #[test] fn test_id_uniqueness() { let mut unique = HashSet::new(); - let ids = get_programs(OperatingMode::Development, 0).unwrap(); - assert!(ids.into_iter().all(move |id| unique.insert(id))); + let programs = get_programs(OperatingMode::Development, 0).unwrap(); + for program in programs { + match program { + Program::Native((name, id)) => assert!(unique.insert((name, id))), + Program::BuiltinLoader((name, id, _)) => assert!(unique.insert((name, id))), + } + } } #[test] @@ -137,7 +180,18 @@ mod tests { get_programs(OperatingMode::Development, 0).unwrap().len(), 5 ); - assert_eq!(get_programs(OperatingMode::Development, 1), None); + assert!(get_programs(OperatingMode::Development, 1).is_none()); + } + + #[test] + fn test_native_development_programs() { + assert_eq!( + get_native_programs(OperatingMode::Development, 0) + .unwrap() + .len(), + 3 + ); + assert!(get_native_programs(OperatingMode::Development, 1).is_none()); } #[test] @@ -155,7 +209,7 @@ mod tests { #[test] fn test_softlaunch_programs() { - assert_eq!(get_programs(OperatingMode::Stable, 1), None); + assert!(get_programs(OperatingMode::Stable, 1).is_none()); assert!(get_programs(OperatingMode::Stable, std::u64::MAX).is_some()); } } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 093ec3f95..57dc3d556 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -468,7 +468,7 @@ fn main() -> Result<(), Box> { ); let native_instruction_processors = - solana_genesis_programs::get_programs(operating_mode, 0).unwrap_or_else(Vec::new); + solana_genesis_programs::get_native_programs(operating_mode, 0).unwrap_or_else(Vec::new); let inflation = solana_genesis_programs::get_inflation(operating_mode, 0).unwrap(); let mut genesis_config = GenesisConfig { diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index a482adfa8..f982d03f6 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -323,11 +323,11 @@ pub fn process_blockstore( } // Setup bank for slot 0 - let bank0 = Arc::new(Bank::new_with_paths( - &genesis_config, - account_paths, - &opts.frozen_accounts, - )); + let mut bank0 = Bank::new_with_paths(&genesis_config, account_paths, &opts.frozen_accounts); + let callback = + solana_genesis_programs::get_entered_epoch_callback(genesis_config.operating_mode); + callback(&mut bank0); + let bank0 = Arc::new(bank0); info!("processing ledger for slot 0..."); let recyclers = VerifyRecyclers::default(); process_bank_0(&bank0, blockstore, &opts, &recyclers)?; diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index e2797cb15..437e109db 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -171,7 +171,7 @@ impl LocalCluster { match genesis_config.operating_mode { OperatingMode::Stable | OperatingMode::Preview => { genesis_config.native_instruction_processors = - solana_genesis_programs::get_programs(genesis_config.operating_mode, 0) + solana_genesis_programs::get_native_programs(genesis_config.operating_mode, 0) .unwrap_or_default() } _ => (), diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 4c99002d9..05cbdc40d 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1616,7 +1616,6 @@ version = "1.4.0" dependencies = [ "bincode", "byteorder 1.3.4", - "jemalloc-sys", "num-derive 0.3.0", "num-traits", "solana-runtime", diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index e3242f1c4..1cd90b7f0 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -1,394 +1,413 @@ +#![cfg(any(feature = "bpf_c", feature = "bpf_rust"))] + +#[macro_use] +extern crate solana_bpf_loader_program; + +use solana_runtime::{ + bank::Bank, + bank_client::BankClient, + genesis_utils::{create_genesis_config, GenesisConfigInfo}, + loader_utils::load_program, +}; +use solana_sdk::{ + account::Account, + bpf_loader, + client::SyncClient, + clock::DEFAULT_SLOTS_PER_EPOCH, + instruction::{AccountMeta, Instruction, InstructionError}, + message::Message, + pubkey::Pubkey, + signature::Keypair, + signature::Signer, + sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, + transaction::TransactionError, +}; +use std::{env, fs::File, io::Read, path::PathBuf, sync::Arc}; + +/// BPF program file extension +const PLATFORM_FILE_EXTENSION_BPF: &str = "so"; + +/// Create a BPF program file name +fn create_bpf_path(name: &str) -> PathBuf { + let mut pathbuf = { + let current_exe = env::current_exe().unwrap(); + PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) + }; + pathbuf.push("bpf/"); + pathbuf.push(name); + pathbuf.set_extension(PLATFORM_FILE_EXTENSION_BPF); + pathbuf +} + +fn load_bpf_program(bank_client: &BankClient, 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) +} + +#[test] #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] -mod bpf { - use solana_bpf_loader_program::solana_bpf_loader_program; - use solana_runtime::{ - bank::Bank, - bank_client::BankClient, - genesis_utils::{create_genesis_config, GenesisConfigInfo}, - loader_utils::load_program, - }; - use solana_sdk::{ - account::Account, - bpf_loader, - client::SyncClient, - clock::DEFAULT_SLOTS_PER_EPOCH, - instruction::{AccountMeta, Instruction, InstructionError}, - message::Message, - pubkey::Pubkey, - signature::Keypair, - signature::Signer, - sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, - transaction::TransactionError, - }; - use std::{env, fs::File, io::Read, path::PathBuf, sync::Arc}; +fn test_program_bpf_sanity() { + solana_logger::setup(); - /// BPF program file extension - const PLATFORM_FILE_EXTENSION_BPF: &str = "so"; - - /// Create a BPF program file name - fn create_bpf_path(name: &str) -> PathBuf { - let mut pathbuf = { - let current_exe = env::current_exe().unwrap(); - PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) - }; - pathbuf.push("bpf/"); - pathbuf.push(name); - pathbuf.set_extension(PLATFORM_FILE_EXTENSION_BPF); - pathbuf + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[ + ("bpf_to_bpf", true), + ("multiple_static", true), + ("noop", true), + ("noop++", true), + ("panic", false), + ("relative_call", true), + ("struct_pass", true), + ("struct_ret", true), + ]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[ + ("solana_bpf_rust_128bit", true), + ("solana_bpf_rust_alloc", true), + ("solana_bpf_rust_dep_crate", true), + ("solana_bpf_rust_external_spend", false), + ("solana_bpf_rust_iter", true), + ("solana_bpf_rust_many_args", true), + ("solana_bpf_rust_noop", true), + ("solana_bpf_rust_panic", false), + ("solana_bpf_rust_param_passing", true), + ("solana_bpf_rust_sysval", true), + ]); } - fn load_bpf_program(bank_client: &BankClient, 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) - } + for program in programs.iter() { + println!("Test program: {:?}", program.0); - #[test] - #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] - fn test_program_bpf_sanity() { - solana_logger::setup(); + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin_loader(&name, id, entrypoint); + let bank = Arc::new(bank); - let mut programs = Vec::new(); - #[cfg(feature = "bpf_c")] - { - programs.extend_from_slice(&[ - ("bpf_to_bpf", true), - ("multiple_static", true), - ("noop", true), - ("noop++", true), - ("panic", false), - ("relative_call", true), - ("struct_pass", true), - ("struct_ret", true), - ]); - } - #[cfg(feature = "bpf_rust")] - { - programs.extend_from_slice(&[ - ("solana_bpf_rust_128bit", true), - ("solana_bpf_rust_alloc", true), - ("solana_bpf_rust_dep_crate", true), - ("solana_bpf_rust_external_spend", false), - ("solana_bpf_rust_iter", true), - ("solana_bpf_rust_many_args", true), - ("solana_bpf_rust_noop", true), - ("solana_bpf_rust_panic", false), - ("solana_bpf_rust_param_passing", true), - ("solana_bpf_rust_sysval", true), - ]); - } + // Create bank with a specific slot, used by solana_bpf_rust_sysvar test + let bank = Bank::new_from_parent(&bank, &Pubkey::default(), DEFAULT_SLOTS_PER_EPOCH + 1); + let bank_client = BankClient::new(bank); - for program in programs.iter() { - println!("Test program: {:?}", program.0); - - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - genesis_config - .native_instruction_processors - .push(solana_bpf_loader_program!()); - let bank = Arc::new(Bank::new(&genesis_config)); - // Create bank with a specific slot, used by solana_bpf_rust_sysvar test - let bank = - Bank::new_from_parent(&bank, &Pubkey::default(), DEFAULT_SLOTS_PER_EPOCH + 1); - let bank_client = BankClient::new(bank); - - // Call user program - let program_id = load_bpf_program(&bank_client, &mint_keypair, program.0); - let account_metas = vec![ - AccountMeta::new(mint_keypair.pubkey(), true), - AccountMeta::new(Keypair::new().pubkey(), false), - AccountMeta::new(clock::id(), false), - AccountMeta::new(fees::id(), false), - AccountMeta::new(rewards::id(), false), - AccountMeta::new(slot_hashes::id(), false), - AccountMeta::new(stake_history::id(), false), - AccountMeta::new(rent::id(), false), - ]; - let instruction = Instruction::new(program_id, &1u8, account_metas); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - if program.1 { - assert!(result.is_ok()); - } else { - assert!(result.is_err()); - } + // Call user program + let program_id = load_bpf_program(&bank_client, &mint_keypair, program.0); + let account_metas = vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new(Keypair::new().pubkey(), false), + AccountMeta::new(clock::id(), false), + AccountMeta::new(fees::id(), false), + AccountMeta::new(rewards::id(), false), + AccountMeta::new(slot_hashes::id(), false), + AccountMeta::new(stake_history::id(), false), + AccountMeta::new(rent::id(), false), + ]; + let instruction = Instruction::new(program_id, &1u8, account_metas); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + if program.1 { + assert!(result.is_ok()); + } else { + assert!(result.is_err()); } } +} - #[test] - fn test_program_bpf_duplicate_accounts() { - solana_logger::setup(); +#[test] +fn test_program_bpf_duplicate_accounts() { + solana_logger::setup(); - let mut programs = Vec::new(); - #[cfg(feature = "bpf_c")] - { - programs.extend_from_slice(&[("dup_accounts")]); - } - #[cfg(feature = "bpf_rust")] - { - programs.extend_from_slice(&[("solana_bpf_rust_dup_accounts")]); - } - - for program in programs.iter() { - println!("Test program: {:?}", program); - - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - genesis_config - .native_instruction_processors - .push(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); - let payee_account = Account::new(10, 1, &program_id); - let payee_pubkey = Pubkey::new_rand(); - bank.store_account(&payee_pubkey, &payee_account); - - let account = Account::new(10, 1, &program_id); - let pubkey = Pubkey::new_rand(); - let account_metas = vec![ - AccountMeta::new(mint_keypair.pubkey(), true), - AccountMeta::new(payee_pubkey, false), - AccountMeta::new(pubkey, false), - AccountMeta::new(pubkey, false), - ]; - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); - assert!(result.is_ok()); - assert_eq!(data[0], 1); - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); - assert!(result.is_ok()); - assert_eq!(data[0], 2); - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); - assert!(result.is_ok()); - assert_eq!(data[0], 3); - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let lamports = bank_client.get_balance(&pubkey).unwrap(); - assert!(result.is_ok()); - assert_eq!(lamports, 11); - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let lamports = bank_client.get_balance(&pubkey).unwrap(); - assert!(result.is_ok()); - assert_eq!(lamports, 12); - - bank.store_account(&pubkey, &account); - let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let lamports = bank_client.get_balance(&pubkey).unwrap(); - assert!(result.is_ok()); - assert_eq!(lamports, 13); - } + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[("dup_accounts")]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[("solana_bpf_rust_dup_accounts")]); } - #[test] - fn test_program_bpf_error_handling() { - solana_logger::setup(); + for program in programs.iter() { + println!("Test program: {:?}", program); - let mut programs = Vec::new(); - #[cfg(feature = "bpf_c")] - { - programs.extend_from_slice(&[("error_handling")]); - } - #[cfg(feature = "bpf_rust")] - { - programs.extend_from_slice(&[("solana_bpf_rust_error_handling")]); - } + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + 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 payee_account = Account::new(10, 1, &program_id); + let payee_pubkey = Pubkey::new_rand(); + bank.store_account(&payee_pubkey, &payee_account); - for program in programs.iter() { - println!("Test program: {:?}", program); + let account = Account::new(10, 1, &program_id); + let pubkey = Pubkey::new_rand(); + let account_metas = vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new(payee_pubkey, false), + AccountMeta::new(pubkey, false), + AccountMeta::new(pubkey, false), + ]; - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - genesis_config - .native_instruction_processors - .push(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); - let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 1); - let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert!(result.is_ok()); + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 2); - let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::InvalidAccountData) - ); + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 3); - let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::Custom(0)) - ); + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 11); - let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::Custom(42)) - ); + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 12); - let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let result = result.unwrap_err().unwrap(); - if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) - != result - { - assert_eq!( - result, - TransactionError::InstructionError(0, InstructionError::InvalidError) - ); - } + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 13); + } +} - let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let result = result.unwrap_err().unwrap(); - if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) - != result - { - assert_eq!( - result, - TransactionError::InstructionError(0, InstructionError::InvalidError) - ); - } +#[test] +fn test_program_bpf_error_handling() { + solana_logger::setup(); - let instruction = Instruction::new(program_id, &7u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - let result = result.unwrap_err().unwrap(); - if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) - != result - { - assert_eq!( - result, - TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed) - ); - } - - let instruction = Instruction::new(program_id, &8u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) - ); - - let instruction = Instruction::new(program_id, &9u8, account_metas.clone()); - let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded) - ); - } + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[("error_handling")]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[("solana_bpf_rust_error_handling")]); } - #[test] - fn test_program_bpf_invoke() { - solana_logger::setup(); + for program in programs.iter() { + println!("Test program: {:?}", program); - const TEST_SUCCESS: u8 = 1; - const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; - const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + 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 account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; - let mut programs = Vec::new(); - #[cfg(feature = "bpf_c")] + let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidAccountData) + ); + + let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(0)) + ); + + let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(42)) + ); + + let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let result = result.unwrap_err().unwrap(); + if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result { - programs.extend_from_slice(&[("invoke", "invoked")]); - } - #[cfg(feature = "bpf_rust")] - { - programs.extend_from_slice(&[("solana_bpf_rust_invoke", "solana_bpf_rust_invoked")]); - } - - for program in programs.iter() { - println!("Test program: {:?}", program); - - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - genesis_config - .native_instruction_processors - .push(solana_bpf_loader_program!()); - let bank = Arc::new(Bank::new(&genesis_config)); - 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 argument_keypair = Keypair::new(); - let account = Account::new(41, 100, &invoke_program_id); - bank.store_account(&argument_keypair.pubkey(), &account); - - let invoked_argument_keypair = Keypair::new(); - let account = Account::new(10, 10, &invoked_program_id); - bank.store_account(&invoked_argument_keypair.pubkey(), &account); - - let from_keypair = Keypair::new(); - let account = Account::new(43, 0, &solana_sdk::system_program::id()); - bank.store_account(&from_keypair.pubkey(), &account); - - let (derived_key1, nonce1) = - Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id); - let (derived_key2, nonce2) = - Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id); - let (derived_key3, nonce3) = - Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id); - - let mint_pubkey = mint_keypair.pubkey(); - let account_metas = vec![ - AccountMeta::new(mint_pubkey, true), - AccountMeta::new(argument_keypair.pubkey(), true), - AccountMeta::new_readonly(invoked_program_id, false), - AccountMeta::new(invoked_argument_keypair.pubkey(), true), - AccountMeta::new_readonly(invoked_program_id, false), - AccountMeta::new(argument_keypair.pubkey(), true), - AccountMeta::new(derived_key1, false), - AccountMeta::new(derived_key2, false), - AccountMeta::new_readonly(derived_key3, false), - AccountMeta::new_readonly(solana_sdk::system_program::id(), false), - AccountMeta::new(from_keypair.pubkey(), true), - ]; - - // success cases - - let instruction = Instruction::new( - invoke_program_id, - &[TEST_SUCCESS, nonce1, nonce2, nonce3], - account_metas.clone(), + assert_eq!( + result, + TransactionError::InstructionError(0, InstructionError::InvalidError) ); - let message = Message::new(&[instruction], Some(&mint_pubkey)); - assert!(bank_client + } + + let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let result = result.unwrap_err().unwrap(); + if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result + { + assert_eq!( + result, + TransactionError::InstructionError(0, InstructionError::InvalidError) + ); + } + + let instruction = Instruction::new(program_id, &7u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + let result = result.unwrap_err().unwrap(); + if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result + { + assert_eq!( + result, + TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed) + ); + } + + let instruction = Instruction::new(program_id, &8u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + ); + + let instruction = Instruction::new(program_id, &9u8, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded) + ); + } +} + +#[test] +fn test_program_bpf_invoke() { + solana_logger::setup(); + + const TEST_SUCCESS: u8 = 1; + const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; + const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; + + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[("invoke", "invoked")]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[("solana_bpf_rust_invoke", "solana_bpf_rust_invoked")]); + } + + 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_program!(); + bank.add_builtin_loader(&name, id, entrypoint); + 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 argument_keypair = Keypair::new(); + let account = Account::new(41, 100, &invoke_program_id); + bank.store_account(&argument_keypair.pubkey(), &account); + + let invoked_argument_keypair = Keypair::new(); + let account = Account::new(10, 10, &invoked_program_id); + bank.store_account(&invoked_argument_keypair.pubkey(), &account); + + let from_keypair = Keypair::new(); + let account = Account::new(43, 0, &solana_sdk::system_program::id()); + bank.store_account(&from_keypair.pubkey(), &account); + + let (derived_key1, nonce1) = + Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id); + let (derived_key2, nonce2) = + Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id); + let (derived_key3, nonce3) = + Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id); + + let mint_pubkey = mint_keypair.pubkey(); + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(argument_keypair.pubkey(), true), + AccountMeta::new_readonly(invoked_program_id, false), + AccountMeta::new(invoked_argument_keypair.pubkey(), true), + AccountMeta::new_readonly(invoked_program_id, false), + AccountMeta::new(argument_keypair.pubkey(), true), + AccountMeta::new(derived_key1, false), + AccountMeta::new(derived_key2, false), + AccountMeta::new_readonly(derived_key3, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new(from_keypair.pubkey(), true), + ]; + + // success cases + + let instruction = Instruction::new( + invoke_program_id, + &[TEST_SUCCESS, nonce1, nonce2, nonce3], + account_metas.clone(), + ); + let message = Message::new(&[instruction], Some(&mint_pubkey)); + assert!(bank_client + .send_and_confirm_message( + &[ + &mint_keypair, + &argument_keypair, + &invoked_argument_keypair, + &from_keypair + ], + message, + ) + .is_ok()); + + // failure cases + + let instruction = Instruction::new( + invoke_program_id, + &TEST_PRIVILEGE_ESCALATION_SIGNER, + account_metas.clone(), + ); + let message = Message::new(&[instruction], Some(&mint_pubkey)); + assert_eq!( + bank_client .send_and_confirm_message( &[ &mint_keypair, @@ -398,53 +417,31 @@ mod bpf { ], message, ) - .is_ok()); + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(194969602)) + ); - // failure cases - - let instruction = Instruction::new( - invoke_program_id, - &TEST_PRIVILEGE_ESCALATION_SIGNER, - account_metas.clone(), - ); - let message = Message::new(&[instruction], Some(&mint_pubkey)); - assert_eq!( - bank_client - .send_and_confirm_message( - &[ - &mint_keypair, - &argument_keypair, - &invoked_argument_keypair, - &from_keypair - ], - message, - ) - .unwrap_err() - .unwrap(), - TransactionError::InstructionError(0, InstructionError::Custom(194969602)) - ); - - let instruction = Instruction::new( - invoke_program_id, - &TEST_PRIVILEGE_ESCALATION_WRITABLE, - account_metas.clone(), - ); - let message = Message::new(&[instruction], Some(&mint_pubkey)); - assert_eq!( - bank_client - .send_and_confirm_message( - &[ - &mint_keypair, - &argument_keypair, - &invoked_argument_keypair, - &from_keypair - ], - message, - ) - .unwrap_err() - .unwrap(), - TransactionError::InstructionError(0, InstructionError::Custom(194969602)) - ); - } + let instruction = Instruction::new( + invoke_program_id, + &TEST_PRIVILEGE_ESCALATION_WRITABLE, + account_metas.clone(), + ); + let message = Message::new(&[instruction], Some(&mint_pubkey)); + assert_eq!( + bank_client + .send_and_confirm_message( + &[ + &mint_keypair, + &argument_keypair, + &invoked_argument_keypair, + &from_keypair + ], + message, + ) + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(194969602)) + ); } } diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index c0da39310..7f6585a89 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -11,7 +11,6 @@ edition = "2018" [dependencies] bincode = "1.3.1" byteorder = "1.3.4" -jemalloc-sys = { version = "0.3.2", features = ["disable_initial_exec_tls"] } num-derive = { version = "0.3" } num-traits = { version = "0.2" } solana-runtime = { path = "../../runtime", version = "1.4.0" } @@ -24,7 +23,7 @@ rand = "0.7.3" rustversion = "1.0.3" [lib] -crate-type = ["lib", "cdylib"] +crate-type = ["lib"] name = "solana_bpf_loader_program" [package.metadata.docs.rs] diff --git a/programs/bpf_loader/src/deprecated.rs b/programs/bpf_loader/src/deprecated.rs index b2564c9ad..3c1ee2660 100644 --- a/programs/bpf_loader/src/deprecated.rs +++ b/programs/bpf_loader/src/deprecated.rs @@ -1,9 +1,6 @@ -use crate::process_instruction; - -solana_sdk::declare_loader!( +solana_sdk::declare_builtin!( solana_sdk::bpf_loader_deprecated::ID, solana_bpf_loader_deprecated_program, - process_instruction, - solana_bpf_loader_program, + solana_bpf_loader_program::process_instruction, deprecated::id ); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index c9cefa140..ca3cc9550 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -29,11 +29,10 @@ use solana_sdk::{ }; use thiserror::Error; -solana_sdk::declare_loader!( +solana_sdk::declare_builtin!( solana_sdk::bpf_loader::ID, solana_bpf_loader_program, - process_instruction, - solana_bpf_loader_program + solana_bpf_loader_program::process_instruction ); #[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] @@ -280,15 +279,6 @@ mod tests { } } - #[rustversion::since(1.46.0)] - #[test] - fn test_bpf_loader_same_crate() { - // Ensure that we can invoke this macro from the same crate - // where it is defined. - solana_bpf_loader_program!(); - solana_bpf_loader_deprecated_program!(); - } - #[test] #[should_panic(expected = "ExceededMaxInstructions(10)")] fn test_bpf_loader_non_terminating_program() { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5c10667d5..45db8e65c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -10,7 +10,7 @@ use crate::{ accounts_db::{ErrorCounters, SnapshotStorages}, accounts_index::Ancestors, blockhash_queue::BlockhashQueue, - builtin_programs::{get_builtin_programs, get_epoch_activated_builtin_programs}, + builtins::{get_builtins, get_epoch_activated_builtins}, epoch_stakes::{EpochStakes, NodeVoteAccounts}, log_collector::LogCollector, message_processor::MessageProcessor, @@ -35,7 +35,7 @@ use solana_sdk::{ Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, SECONDS_PER_DAY, }, - entrypoint_native::ProcessInstruction, + entrypoint_native::{ProcessInstruction, ProcessInstructionWithContext}, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, @@ -98,6 +98,26 @@ type RentCollectionCycleParams = ( type EpochCount = u64; +#[derive(Copy, Clone)] +pub enum Entrypoint { + Program(ProcessInstruction), + Loader(ProcessInstructionWithContext), +} +pub struct Builtin { + pub name: String, + pub id: Pubkey, + pub entrypoint: Entrypoint, +} +impl Builtin { + pub fn new(name: &str, id: Pubkey, entrypoint: Entrypoint) -> Self { + Self { + name: name.to_string(), + id, + entrypoint, + } + } +} + #[derive(Default)] pub struct BankRc { /// where all the Accounts are stored @@ -544,11 +564,9 @@ impl Bank { entered_epoch_callback(&mut new) } - if let Some(builtin_programs) = - get_epoch_activated_builtin_programs(new.operating_mode(), new.epoch) - { - for program in builtin_programs.iter() { - new.add_builtin_program(&program.name, program.id, program.process_instruction); + if let Some(builtins) = get_epoch_activated_builtins(new.operating_mode(), new.epoch) { + for program in builtins.iter() { + new.add_builtin(&program.name, program.id, program.entrypoint); } } } @@ -2575,9 +2593,9 @@ impl Bank { } pub fn finish_init(&mut self) { - let builtin_programs = get_builtin_programs(self.operating_mode(), self.epoch); - for program in builtin_programs.iter() { - self.add_builtin_program(&program.name, program.id, program.process_instruction); + let builtins = get_builtins(); + for program in builtins.iter() { + self.add_builtin(&program.name, program.id, program.entrypoint); } } @@ -3021,19 +3039,36 @@ impl Bank { !self.is_delta.load(Ordering::Relaxed) } - /// Add an instruction processor to intercept instructions before the dynamic loader. pub fn add_builtin_program( &mut self, name: &str, program_id: Pubkey, process_instruction: ProcessInstruction, ) { + self.add_builtin(name, program_id, Entrypoint::Program(process_instruction)); + } + + pub fn add_builtin_loader( + &mut self, + name: &str, + program_id: Pubkey, + process_instruction_with_context: ProcessInstructionWithContext, + ) { + self.add_builtin( + name, + program_id, + Entrypoint::Loader(process_instruction_with_context), + ); + } + + /// Add an instruction processor to intercept instructions before the dynamic loader. + pub fn add_builtin(&mut self, name: &str, program_id: Pubkey, entrypoint: Entrypoint) { match self.get_account(&program_id) { Some(account) => { assert_eq!( account.owner, native_loader::id(), - "Cannot overwrite non-native loader account" + "Cannot overwrite non-native account" ); } None => { @@ -3042,9 +3077,18 @@ impl Bank { self.store_account(&program_id, &account); } } - self.message_processor - .add_program(program_id, process_instruction); - debug!("Added static program {} under {:?}", name, program_id); + match entrypoint { + Entrypoint::Program(process_instruction) => { + self.message_processor + .add_program(program_id, process_instruction); + debug!("Added builtin program {} under {:?}", name, program_id); + } + Entrypoint::Loader(process_instruction_with_context) => { + self.message_processor + .add_loader(program_id, process_instruction_with_context); + debug!("Added builtin loader {} under {:?}", name, program_id); + } + } } pub fn compare_bank(&self, dbank: &Bank) { diff --git a/runtime/src/builtin_programs.rs b/runtime/src/builtin_programs.rs deleted file mode 100644 index 5bfb4e91c..000000000 --- a/runtime/src/builtin_programs.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::system_instruction_processor; -use solana_sdk::{ - clock::Epoch, entrypoint_native::ProcessInstruction, genesis_config::OperatingMode, - pubkey::Pubkey, system_program, -}; - -pub struct BuiltinProgram { - pub name: String, - pub id: Pubkey, - pub process_instruction: ProcessInstruction, -} -impl BuiltinProgram { - pub fn new(name: &str, id: Pubkey, process_instruction: ProcessInstruction) -> Self { - Self { - name: name.to_string(), - id, - process_instruction, - } - } -} - -/// All builtin programs that should be active at the given (operating_mode, epoch) -pub fn get_builtin_programs(_operating_mode: OperatingMode, _epoch: Epoch) -> Vec { - vec![ - BuiltinProgram::new( - "system_program", - system_program::id(), - system_instruction_processor::process_instruction, - ), - BuiltinProgram::new( - "config_program", - solana_config_program::id(), - solana_config_program::config_processor::process_instruction, - ), - BuiltinProgram::new( - "stake_program", - solana_stake_program::id(), - solana_stake_program::stake_instruction::process_instruction, - ), - BuiltinProgram::new( - "vote_program", - solana_vote_program::id(), - solana_vote_program::vote_instruction::process_instruction, - ), - ] -} - -/// Builtin programs that activate at the given (operating_mode, epoch) -pub fn get_epoch_activated_builtin_programs( - _operating_mode: OperatingMode, - _epoch: Epoch, -) -> Option> { - None -} diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs new file mode 100644 index 000000000..50534d79d --- /dev/null +++ b/runtime/src/builtins.rs @@ -0,0 +1,39 @@ +use crate::{ + bank::{Builtin, Entrypoint}, + system_instruction_processor, +}; +use solana_sdk::{clock::Epoch, genesis_config::OperatingMode, system_program}; + +/// All builtin programs that should be active at the given (operating_mode, epoch) +pub fn get_builtins() -> Vec { + vec![ + Builtin::new( + "system_program", + system_program::id(), + Entrypoint::Program(system_instruction_processor::process_instruction), + ), + Builtin::new( + "config_program", + solana_config_program::id(), + Entrypoint::Program(solana_config_program::config_processor::process_instruction), + ), + Builtin::new( + "stake_program", + solana_stake_program::id(), + Entrypoint::Program(solana_stake_program::stake_instruction::process_instruction), + ), + Builtin::new( + "vote_program", + solana_vote_program::id(), + Entrypoint::Program(solana_vote_program::vote_instruction::process_instruction), + ), + ] +} + +/// Builtin programs that activate at the given (operating_mode, epoch) +pub fn get_epoch_activated_builtins( + _operating_mode: OperatingMode, + _epoch: Epoch, +) -> Option> { + None +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 985f69aa5..d78d793ed 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -9,7 +9,7 @@ pub mod bank_forks; pub mod bank_utils; mod blockhash_queue; pub mod bloom; -pub mod builtin_programs; +pub mod builtins; pub mod commitment; pub mod epoch_stakes; pub mod genesis_utils; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 311caf198..f5c17402c 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, clock::Epoch, - entrypoint_native::{InvokeContext, Logger, ProcessInstruction}, + entrypoint_native::{InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext}, instruction::{CompiledInstruction, InstructionError}, message::Message, native_loader, @@ -247,9 +247,6 @@ impl Logger for ThisLogger { } } -pub type ProcessInstructionWithContext = - fn(&Pubkey, &[KeyedAccount], &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>; - #[derive(Deserialize, Serialize)] pub struct MessageProcessor { #[serde(skip)] @@ -353,6 +350,17 @@ impl MessageProcessor { ) -> Result<(), InstructionError> { if native_loader::check_id(&keyed_accounts[0].owner()?) { let root_id = keyed_accounts[0].unsigned_key(); + for (id, process_instruction) in &self.loaders { + if id == root_id { + // Call the program via a builtin loader + return process_instruction( + &root_id, + &keyed_accounts[1..], + instruction_data, + invoke_context, + ); + } + } for (id, process_instruction) in &self.programs { if id == root_id { // Call the builtin program @@ -367,9 +375,9 @@ impl MessageProcessor { invoke_context, ); } else { - let owner_id = keyed_accounts[0].owner()?; + let owner_id = &keyed_accounts[0].owner()?; for (id, process_instruction) in &self.loaders { - if *id == owner_id { + if id == owner_id { // Call the program via a builtin loader return process_instruction( &owner_id, diff --git a/sdk/src/builtins.rs b/sdk/src/builtins.rs new file mode 100644 index 000000000..9df4fdd48 --- /dev/null +++ b/sdk/src/builtins.rs @@ -0,0 +1,140 @@ +//! @brief Solana builtin helper macros + +#[rustversion::since(1.46.0)] +#[macro_export] +macro_rules! declare_builtin_name { + ($name:ident, $id:path, $entrypoint:expr) => { + #[macro_export] + macro_rules! $name { + () => { + // Subtle: + // The outer `declare_builtin_name!` macro may be expanded in another + // crate, causing the macro `$name!` to be defined in that + // crate. We want to emit a call to `$crate::id()`, and have + // `$crate` be resolved in the crate where `$name!` gets defined, + // *not* in this crate (where `declare_builtin_name! is defined). + // + // When a macro_rules! macro gets expanded, any $crate tokens + // in its output will be 'marked' with the crate they were expanded + // from. This includes nested macros like our macro `$name` - even + // though it looks like a separate macro, Rust considers it to be + // just another part of the output of `declare_program!`. + // + // We pass `$name` as the second argument to tell `respan!` to + // apply use the `Span` of `$name` when resolving `$crate::id`. + // This causes `$crate` to behave as though it was written + // at the same location as the `$name` value passed + // to `declare_builtin_name!` (e.g. the 'foo' in + // `declare_builtin_name(foo)` + // + // See the `respan!` macro for more details. + // This should use `crate::respan!` once + // https://github.com/rust-lang/rust/pull/72121 is merged: + // see https://github.com/solana-labs/solana/issues/10933. + // For now, we need to use `::solana_sdk` + // + // `respan!` respans the path `$crate::id`, which we then call (hence the extra + // parens) + ( + stringify!($name).to_string(), + ::solana_sdk::respan!($crate::$id, $name)(), + $entrypoint, + ) + }; + } + }; +} + +#[rustversion::not(since(1.46.0))] +#[macro_export] +macro_rules! declare_builtin_name { + ($name:ident, $id:path, $entrypoint:expr) => { + #[macro_export] + macro_rules! $name { + () => { + (stringify!($name).to_string(), $crate::$id(), $entrypoint) + }; + } + }; +} + +/// Convenience macro to declare a builtin +/// +/// bs58_string: bs58 string representation the program's id +/// name: Name of the program +/// entrypoint: Program's entrypoint, must be of `type Entrypoint` +/// id: Path to the program id access function, used if this macro is not +/// called in `src/lib` +/// +/// # Examples +/// +/// ``` +/// use std::str::FromStr; +/// // wrapper is used so that the macro invocation occurs in the item position +/// // rather than in the statement position which isn't allowed. +/// mod item_wrapper { +/// use solana_sdk::account::KeyedAccount; +/// use solana_sdk::instruction::InstructionError; +/// use solana_sdk::pubkey::Pubkey; +/// use solana_sdk::declare_builtin; +/// +/// fn my_process_instruction( +/// program_id: &Pubkey, +/// keyed_accounts: &[KeyedAccount], +/// instruction_data: &[u8], +/// ) -> Result<(), InstructionError> { +/// // Process an instruction +/// Ok(()) +/// } +/// +/// declare_builtin!( +/// "My11111111111111111111111111111111111111111", +/// solana_my_program, +/// my_process_instruction +/// ); +/// +/// # } +/// # use solana_sdk::pubkey::Pubkey; +/// # use item_wrapper::id; +/// let my_id = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap(); +/// assert_eq!(id(), my_id); +/// ``` +/// ``` +/// use std::str::FromStr; +/// # // wrapper is used so that the macro invocation occurs in the item position +/// # // rather than in the statement position which isn't allowed. +/// # mod item_wrapper { +/// use solana_sdk::account::KeyedAccount; +/// use solana_sdk::instruction::InstructionError; +/// use solana_sdk::pubkey::Pubkey; +/// use solana_sdk::declare_builtin; +/// +/// fn my_process_instruction( +/// program_id: &Pubkey, +/// keyed_accounts: &[KeyedAccount], +/// instruction_data: &[u8], +/// ) -> Result<(), InstructionError> { +/// // Process an instruction +/// Ok(()) +/// } +/// +/// declare_builtin!( +/// solana_sdk::system_program::ID, +/// solana_my_program, +/// my_process_instruction +/// ); +/// } +/// +/// # use item_wrapper::id; +/// assert_eq!(id(), solana_sdk::system_program::ID); +/// ``` +#[macro_export] +macro_rules! declare_builtin { + ($bs58_string:expr, $name:ident, $entrypoint:expr) => { + $crate::declare_builtin!($bs58_string, $name, $entrypoint, id); + }; + ($bs58_string:expr, $name:ident, $entrypoint:expr, $id:path) => { + $crate::declare_id!($bs58_string); + $crate::declare_builtin_name!($name, $id, $entrypoint); + }; +} diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index dfd1a5c6b..6b17a277e 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -93,7 +93,8 @@ macro_rules! declare_name { /// name: Name of the program /// filename: must match the library name in Cargo.toml /// entrypoint: Program's entrypoint, must be of `type Entrypoint` -/// id: Path to the program id access function, used if not called in `src/lib` +/// id: Path to the program id access function, used if this macro is not +/// called in `src/lib` /// /// # Examples /// @@ -174,32 +175,9 @@ macro_rules! declare_program( ) ); -/// Same as declare_program but for native loaders -#[macro_export] -macro_rules! declare_loader { - ($bs58_string:expr, $name:ident, $entrypoint:expr) => { - $crate::declare_loader!($bs58_string, $name, $entrypoint, $name, id); - }; - ($bs58_string:expr, $name:ident, $entrypoint:expr, $filename:ident) => { - $crate::declare_loader!($bs58_string, $name, $entrypoint, $filename, id); - }; - ($bs58_string:expr, $name:ident, $entrypoint:expr, $filename:ident, $id:path) => { - $crate::declare_id!($bs58_string); - $crate::declare_name!($name, $filename, $id); - - #[no_mangle] - pub extern "C" fn $name( - program_id: &$crate::pubkey::Pubkey, - keyed_accounts: &[$crate::account::KeyedAccount], - instruction_data: &[u8], - invoke_context: &mut dyn $crate::entrypoint_native::InvokeContext, - ) -> Result<(), $crate::instruction::InstructionError> { - $entrypoint(program_id, keyed_accounts, instruction_data, invoke_context) - } - }; -} - pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>; +pub type ProcessInstructionWithContext = + fn(&Pubkey, &[KeyedAccount], &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>; /// Invocation context passed to loaders pub trait InvokeContext { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index e8e69e92b..6ff135d84 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -13,6 +13,7 @@ pub mod account; pub mod account_utils; pub mod bpf_loader; pub mod bpf_loader_deprecated; +pub mod builtins; pub mod clock; pub mod commitment_config; pub mod decode_error;