diff --git a/Cargo.lock b/Cargo.lock index 395d21f0c9..2575192d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2260,9 +2260,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531" +checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" dependencies = [ "paste-impl", "proc-macro-hack", @@ -2270,9 +2270,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606" +checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.19", @@ -4659,9 +4659,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831" +checksum = "962f8f04ac7239fe4dd45fa4ce706ec78b59a0da9f41def463832857e36c60b0" dependencies = [ "byteorder", "combine", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 83975e9c24..49b181eed3 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1176,9 +1176,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.14" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" dependencies = [ "paste-impl", "proc-macro-hack", @@ -1186,14 +1186,11 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.14" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.19", - "quote 1.0.6", - "syn 1.0.27", ] [[package]] @@ -2050,9 +2047,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831" +checksum = "962f8f04ac7239fe4dd45fa4ce706ec78b59a0da9f41def463832857e36c60b0" dependencies = [ "byteorder 1.3.4", "combine", diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index b0d65f8990..4363ce4b0c 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -26,7 +26,7 @@ solana-bpf-loader-program = { path = "../bpf_loader", version = "1.4.0" } solana-logger = { path = "../../logger", version = "1.4.0" } solana-runtime = { path = "../../runtime", version = "1.4.0" } solana-sdk = { path = "../../sdk", version = "1.4.0" } -solana_rbpf = "=0.1.30" +solana_rbpf = "=0.1.31" [[bench]] name = "bpf_loader" diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index 1dcd878633..4c76fd969e 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -6,8 +6,7 @@ extern crate test; extern crate solana_bpf_loader_program; use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; -use solana_bpf_loader_program::serialization::{deserialize_parameters, serialize_parameters}; -use solana_rbpf::EbpfVm; +use solana_rbpf::vm::EbpfVm; use solana_runtime::{ bank::Bank, bank_client::BankClient, @@ -15,11 +14,13 @@ use solana_runtime::{ loader_utils::load_program, }; use solana_sdk::{ - account::{create_keyed_readonly_accounts, Account}, - bpf_loader, bpf_loader_deprecated, + account::Account, + bpf_loader, client::SyncClient, entrypoint::SUCCESS, - entrypoint_native::{ComputeBudget, ComputeMeter, InvokeContext, Logger, ProcessInstruction}, + entrypoint_native::{ + ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction, + }, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, message::Message, pubkey::Pubkey, @@ -42,10 +43,6 @@ fn create_bpf_path(name: &str) -> PathBuf { pathbuf } -fn empty_check(_prog: &[u8]) -> Result<(), solana_bpf_loader_program::BPFError> { - Ok(()) -} - fn load_elf(name: &str) -> Result, std::io::Error> { let path = create_bpf_path(name); let mut file = File::open(&path).expect(&format!("Unable to open {:?}", path)); @@ -71,15 +68,13 @@ const ARMSTRONG_LIMIT: u64 = 500; const ARMSTRONG_EXPECTED: u64 = 5; #[bench] -fn bench_program_verify(bencher: &mut Bencher) { +fn bench_program_create_executable(bencher: &mut Bencher) { let elf = load_elf("bench_alu").unwrap(); - let mut vm = EbpfVm::::new(None).unwrap(); - vm.set_verifier(empty_check).unwrap(); - vm.set_elf(&elf).unwrap(); bencher.iter(|| { - vm.set_verifier(solana_bpf_loader_program::bpf_verifier::check) - .unwrap(); + let _ = + EbpfVm::::create_executable_from_elf(&elf, None) + .unwrap(); }); } @@ -96,8 +91,16 @@ fn bench_program_alu(bencher: &mut Bencher) { let mut invoke_context = MockInvokeContext::default(); let elf = load_elf("bench_alu").unwrap(); - let (mut vm, _) = - solana_bpf_loader_program::create_vm(&loader_id, &elf, &[], &mut invoke_context).unwrap(); + let executable = + EbpfVm::::create_executable_from_elf(&elf, None) + .unwrap(); + let (mut vm, _) = solana_bpf_loader_program::create_vm( + &loader_id, + executable.as_ref(), + &[], + &mut invoke_context, + ) + .unwrap(); println!("Interpreted:"); assert_eq!( @@ -151,8 +154,6 @@ fn bench_program_alu(bencher: &mut Bencher) { #[bench] fn bench_program_execute_noop(bencher: &mut Bencher) { - // solana_logger::setup(); // TODO remove - let GenesisConfigInfo { genesis_config, mint_keypair, @@ -177,7 +178,6 @@ fn bench_program_execute_noop(bencher: &mut Bencher) { .send_and_confirm_message(&[&mint_keypair], message.clone()) .unwrap(); - println!("start bench"); bencher.iter(|| { bank.clear_signatures(); bank_client @@ -186,62 +186,6 @@ fn bench_program_execute_noop(bencher: &mut Bencher) { }); } -fn create_serialization_create_params() -> (Vec, Vec<(Pubkey, RefCell)>) { - let accounts = vec![ - ( - Pubkey::new_rand(), - RefCell::new(Account::new(0, 100, &Pubkey::new_rand())), - ), - ( - Pubkey::new_rand(), - RefCell::new(Account::new(0, 100, &Pubkey::new_rand())), - ), - ( - Pubkey::new_rand(), - RefCell::new(Account::new(0, 250, &Pubkey::new_rand())), - ), - ( - Pubkey::new_rand(), - RefCell::new(Account::new(0, 1000, &Pubkey::new_rand())), - ), - ]; - (vec![0xee; 100], accounts) -} - -#[bench] -fn bench_serialization_aligned(bencher: &mut Bencher) { - let (data, accounts) = create_serialization_create_params(); - let keyed_accounts = create_keyed_readonly_accounts(&accounts); - - bencher.iter(|| { - let buffer = serialize_parameters( - &bpf_loader_deprecated::id(), - &Pubkey::new_rand(), - &keyed_accounts, - &data, - ) - .unwrap(); - deserialize_parameters(&bpf_loader_deprecated::id(), &keyed_accounts, &buffer).unwrap(); - }); -} - -#[bench] -fn bench_serialization_unaligned(bencher: &mut Bencher) { - let (data, accounts) = create_serialization_create_params(); - let keyed_accounts = create_keyed_readonly_accounts(&accounts); - - bencher.iter(|| { - let buffer = serialize_parameters( - &bpf_loader_deprecated::id(), - &Pubkey::new_rand(), - &keyed_accounts, - &data, - ) - .unwrap(); - deserialize_parameters(&bpf_loader_deprecated::id(), &keyed_accounts, &buffer).unwrap(); - }); -} - #[derive(Debug, Default)] pub struct MockInvokeContext { key: Pubkey, @@ -279,6 +223,10 @@ impl InvokeContext for MockInvokeContext { fn get_compute_meter(&self) -> Rc> { Rc::new(RefCell::new(self.mock_compute_meter.clone())) } + fn add_executor(&mut self, _pubkey: &Pubkey, _executor: Arc) {} + fn get_executor(&mut self, _pubkey: &Pubkey) -> Option> { + None + } } #[derive(Debug, Default, Clone)] pub struct MockLogger { diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 4bf7953d56..6ad143d780 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -7,7 +7,7 @@ use solana_bpf_loader_program::{ create_vm, serialization::{deserialize_parameters, serialize_parameters}, }; -use solana_rbpf::InstructionMeter; +use solana_rbpf::vm::{EbpfVm, InstructionMeter}; use solana_runtime::{ bank::Bank, bank_client::BankClient, @@ -20,7 +20,9 @@ use solana_sdk::{ client::SyncClient, clock::DEFAULT_SLOTS_PER_EPOCH, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, - entrypoint_native::{ComputeBudget, ComputeMeter, InvokeContext, Logger, ProcessInstruction}, + entrypoint_native::{ + ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction, + }, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, message::Message, pubkey::Pubkey, @@ -67,14 +69,15 @@ fn run_program( let path = create_bpf_path(name); let mut file = File::open(path).unwrap(); - let mut program_account = Account::default(); - file.read_to_end(&mut program_account.data).unwrap(); - + let mut data = vec![]; + file.read_to_end(&mut data).unwrap(); let loader_id = bpf_loader::id(); let mut invoke_context = MockInvokeContext::default(); + + let executable = EbpfVm::create_executable_from_elf(&data, None).unwrap(); let (mut vm, heap_region) = create_vm( &loader_id, - &program_account.data, + executable.as_ref(), parameter_accounts, &mut invoke_context, ) @@ -631,6 +634,10 @@ impl InvokeContext for MockInvokeContext { fn get_compute_meter(&self) -> Rc> { Rc::new(RefCell::new(self.compute_meter.clone())) } + fn add_executor(&mut self, _pubkey: &Pubkey, _executor: Arc) {} + fn get_executor(&mut self, _pubkey: &Pubkey) -> Option> { + None + } } #[derive(Debug, Default, Clone)] diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index bf9732ec24..8134f7cd60 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -11,11 +11,11 @@ edition = "2018" [dependencies] bincode = "1.3.1" byteorder = "1.3.4" -num-derive = { version = "0.3" } -num-traits = { version = "0.2" } +num-derive = "0.3" +num-traits = "0.2" solana-runtime = { path = "../../runtime", version = "1.4.0" } solana-sdk = { path = "../../sdk", version = "1.4.0" } -solana_rbpf = "=0.1.30" +solana_rbpf = "=0.1.31" thiserror = "1.0" [dev-dependencies] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 35ae8d0b0b..a7d5590464 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -12,22 +12,22 @@ use crate::{ }; use num_derive::{FromPrimitive, ToPrimitive}; use solana_rbpf::{ - ebpf::{EbpfError, UserDefinedError}, + error::{EbpfError, UserDefinedError}, memory_region::MemoryRegion, - EbpfVm, InstructionMeter, + vm::{EbpfVm, Executable, InstructionMeter}, }; use solana_sdk::{ account::{is_executable, next_keyed_account, KeyedAccount}, bpf_loader, bpf_loader_deprecated, decode_error::DecodeError, entrypoint::SUCCESS, - entrypoint_native::{ComputeMeter, InvokeContext}, + entrypoint_native::{ComputeMeter, Executor, InvokeContext}, instruction::InstructionError, loader_instruction::LoaderInstruction, program_utils::limited_deserialize, pubkey::Pubkey, }; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; use thiserror::Error; solana_sdk::declare_builtin!( @@ -36,6 +36,7 @@ solana_sdk::declare_builtin!( solana_bpf_loader_program::process_instruction ); +/// Errors returned by the BPFLoader if the VM fails to run the program #[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] pub enum BPFLoaderError { #[error("failed to create virtual machine")] @@ -49,7 +50,7 @@ impl DecodeError for BPFLoaderError { } } -/// Errors returned by functions the BPF Loader registers with the vM +/// Errors returned by functions the BPF Loader registers with the VM #[derive(Debug, Error, PartialEq)] pub enum BPFError { #[error("{0}")] @@ -59,39 +60,7 @@ pub enum BPFError { } impl UserDefinedError for BPFError {} -pub fn create_vm<'a>( - loader_id: &'a Pubkey, - prog: &'a [u8], - parameter_accounts: &'a [KeyedAccount<'a>], - invoke_context: &'a mut dyn InvokeContext, -) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError> { - let mut vm = EbpfVm::new(None)?; - vm.set_verifier(bpf_verifier::check)?; - vm.set_elf(&prog)?; - - let heap_region = - syscalls::register_syscalls(loader_id, &mut vm, parameter_accounts, invoke_context)?; - - Ok((vm, heap_region)) -} - -pub fn check_elf(prog: &[u8]) -> Result<(), EbpfError> { - let mut vm = EbpfVm::new(None)?; - vm.set_verifier(bpf_verifier::check)?; - vm.set_elf(&prog)?; - Ok(()) -} - -/// Look for a duplicate account and return its position if found -pub fn is_dup(accounts: &[KeyedAccount], keyed_account: &KeyedAccount) -> (bool, usize) { - for (i, account) in accounts.iter().enumerate() { - if account == keyed_account { - return (true, i); - } - } - (false, 0) -} - +/// Point all log messages to the log collector macro_rules! log{ ($logger:ident, $message:expr) => { if let Ok(mut logger) = $logger.try_borrow_mut() { @@ -102,23 +71,42 @@ macro_rules! log{ }; ($logger:ident, $fmt:expr, $($arg:tt)*) => { if let Ok(mut logger) = $logger.try_borrow_mut() { - logger.log(&format!($fmt, $($arg)*)); + if logger.log_enabled() { + logger.log(&format!($fmt, $($arg)*)); + } } }; } -struct ThisInstructionMeter { - compute_meter: Rc>, +pub fn create_and_cache_executor( + program: &KeyedAccount, + invoke_context: &mut dyn InvokeContext, +) -> Result, InstructionError> { + let executable = EbpfVm::create_executable_from_elf( + &program.try_account_ref()?.data, + Some(bpf_verifier::check), + ) + .map_err(|e| { + let logger = invoke_context.get_logger(); + log!(logger, "{}", e); + InstructionError::InvalidAccountData + })?; + let executor = Arc::new(BPFExecutor { executable }); + invoke_context.add_executor(program.unsigned_key(), executor.clone()); + Ok(executor) } -impl InstructionMeter for ThisInstructionMeter { - fn consume(&mut self, amount: u64) { - // 1 to 1 instruction to compute unit mapping - // ignore error, Ebpf will bail if exceeded - let _ = self.compute_meter.borrow_mut().consume(amount); - } - fn get_remaining(&self) -> u64 { - self.compute_meter.borrow().get_remaining() - } + +/// Create the BPF virtual machine +pub fn create_vm<'a>( + loader_id: &'a Pubkey, + executable: &'a dyn Executable, + parameter_accounts: &'a [KeyedAccount<'a>], + invoke_context: &'a mut dyn InvokeContext, +) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError> { + let mut vm = EbpfVm::new(executable)?; + let heap_region = + syscalls::register_syscalls(loader_id, &mut vm, parameter_accounts, invoke_context)?; + Ok((vm, heap_region)) } pub fn process_instruction( @@ -135,8 +123,82 @@ pub fn process_instruction( log!(logger, "No account keys"); return Err(InstructionError::NotEnoughAccountKeys); } + let program = &keyed_accounts[0]; if is_executable(keyed_accounts)? { + let executor = match invoke_context.get_executor(program.unsigned_key()) { + Some(executor) => executor, + None => create_and_cache_executor(program, invoke_context)?, + }; + executor.execute(program_id, keyed_accounts, instruction_data, invoke_context)? + } else if !keyed_accounts.is_empty() { + match limited_deserialize(instruction_data)? { + LoaderInstruction::Write { offset, bytes } => { + if program.signer_key().is_none() { + log!(logger, "key[0] did not sign the transaction"); + return Err(InstructionError::MissingRequiredSignature); + } + let offset = offset as usize; + let len = bytes.len(); + if program.data_len()? < offset + len { + log!( + logger, + "Write overflow: {} < {}", + program.data_len()?, + offset + len + ); + return Err(InstructionError::AccountDataTooSmall); + } + program.try_account_ref_mut()?.data[offset..offset + len].copy_from_slice(&bytes); + } + LoaderInstruction::Finalize => { + if program.signer_key().is_none() { + log!(logger, "key[0] did not sign the transaction"); + return Err(InstructionError::MissingRequiredSignature); + } + + let _ = create_and_cache_executor(program, invoke_context)?; + program.try_account_ref_mut()?.executable = true; + log!( + logger, + "Finalized account {:?}", + program.signer_key().unwrap() + ); + } + } + } + Ok(()) +} + +/// Passed to the VM to enforce the compute budget +struct ThisInstructionMeter { + compute_meter: Rc>, +} +impl InstructionMeter for ThisInstructionMeter { + fn consume(&mut self, amount: u64) { + // 1 to 1 instruction to compute unit mapping + // ignore error, Ebpf will bail if exceeded + let _ = self.compute_meter.borrow_mut().consume(amount); + } + fn get_remaining(&self) -> u64 { + self.compute_meter.borrow().get_remaining() + } +} + +/// BPF Loader's Executor implementation +pub struct BPFExecutor { + executable: Box>, +} +impl Executor for BPFExecutor { + fn execute( + &self, + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + let logger = invoke_context.get_logger(); + let mut keyed_accounts_iter = keyed_accounts.iter(); let program = next_keyed_account(&mut keyed_accounts_iter)?; @@ -149,10 +211,9 @@ pub fn process_instruction( )?; { let compute_meter = invoke_context.get_compute_meter(); - let program_account = program.try_account_ref_mut()?; let (mut vm, heap_region) = match create_vm( program_id, - &program_account.data, + self.executable.as_ref(), ¶meter_accounts, invoke_context, ) { @@ -201,59 +262,15 @@ pub fn process_instruction( } deserialize_parameters(program_id, parameter_accounts, ¶meter_bytes)?; log!(logger, "BPF program {} success", program.unsigned_key()); - } else if !keyed_accounts.is_empty() { - match limited_deserialize(instruction_data)? { - LoaderInstruction::Write { offset, bytes } => { - let mut keyed_accounts_iter = keyed_accounts.iter(); - let program = next_keyed_account(&mut keyed_accounts_iter)?; - if program.signer_key().is_none() { - log!(logger, "key[0] did not sign the transaction"); - return Err(InstructionError::MissingRequiredSignature); - } - let offset = offset as usize; - let len = bytes.len(); - if program.data_len()? < offset + len { - log!( - logger, - "Write overflow: {} < {}", - program.data_len()?, - offset + len - ); - return Err(InstructionError::AccountDataTooSmall); - } - program.try_account_ref_mut()?.data[offset..offset + len].copy_from_slice(&bytes); - } - LoaderInstruction::Finalize => { - let mut keyed_accounts_iter = keyed_accounts.iter(); - let program = next_keyed_account(&mut keyed_accounts_iter)?; - - if program.signer_key().is_none() { - log!(logger, "key[0] did not sign the transaction"); - return Err(InstructionError::MissingRequiredSignature); - } - - if let Err(e) = check_elf(&program.try_account_ref()?.data) { - log!(logger, "{}", e); - return Err(InstructionError::InvalidAccountData); - } - - program.try_account_ref_mut()?.executable = true; - log!( - logger, - "Finalized account {:?}", - program.signer_key().unwrap() - ); - } - } + Ok(()) } - Ok(()) } #[cfg(test)] mod tests { use super::*; use rand::Rng; - use solana_runtime::message_processor::ThisInvokeContext; + use solana_runtime::message_processor::{Executors, ThisInvokeContext}; use solana_sdk::{ account::Account, entrypoint_native::{ComputeBudget, Logger, ProcessInstruction}, @@ -339,6 +356,10 @@ mod tests { fn get_compute_meter(&self) -> Rc> { Rc::new(RefCell::new(self.compute_meter.clone())) } + fn add_executor(&mut self, _pubkey: &Pubkey, _executor: Arc) {} + fn get_executor(&mut self, _pubkey: &Pubkey) -> Option> { + None + } } struct TestInstructionMeter { @@ -364,10 +385,10 @@ mod tests { ]; let input = &mut [0x00]; - let mut vm = EbpfVm::::new(None).unwrap(); - vm.set_verifier(bpf_verifier::check).unwrap(); + let executable = + EbpfVm::create_executable_from_text_bytes(program, Some(bpf_verifier::check)).unwrap(); + let mut vm = EbpfVm::::new(executable.as_ref()).unwrap(); let instruction_meter = TestInstructionMeter { remaining: 10 }; - vm.set_program(program).unwrap(); vm.execute_program_metered(input, &[], &[], instruction_meter) .unwrap(); } @@ -518,38 +539,25 @@ mod tests { let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + let mut invoke_context = MockInvokeContext::default(); + // Case: Empty keyed accounts assert_eq!( Err(InstructionError::NotEnoughAccountKeys), - process_instruction( - &bpf_loader::id(), - &[], - &[], - &mut MockInvokeContext::default() - ) + process_instruction(&bpf_loader::id(), &[], &[], &mut invoke_context) ); // Case: Only a program account assert_eq!( Ok(()), - process_instruction( - &bpf_loader::id(), - &keyed_accounts, - &[], - &mut MockInvokeContext::default() - ) + process_instruction(&bpf_loader::id(), &keyed_accounts, &[], &mut invoke_context) ); // Case: Account not executable keyed_accounts[0].account.borrow_mut().executable = false; assert_eq!( Err(InstructionError::InvalidInstructionData), - process_instruction( - &bpf_loader::id(), - &keyed_accounts, - &[], - &mut MockInvokeContext::default() - ) + process_instruction(&bpf_loader::id(), &keyed_accounts, &[], &mut invoke_context) ); keyed_accounts[0].account.borrow_mut().executable = true; @@ -558,12 +566,7 @@ mod tests { keyed_accounts.push(KeyedAccount::new(&program_key, false, ¶meter_account)); assert_eq!( Ok(()), - process_instruction( - &bpf_loader::id(), - &keyed_accounts, - &[], - &mut MockInvokeContext::default() - ) + process_instruction(&bpf_loader::id(), &keyed_accounts, &[], &mut invoke_context) ); // Case: limited budget @@ -583,6 +586,7 @@ mod tests { invoke_units: 1000, max_invoke_depth: 2, }, + Rc::new(RefCell::new(Executors::default())), ); assert_eq!( Err(InstructionError::Custom(194969602)), diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 273eeea29a..78954ae600 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -1,9 +1,10 @@ use crate::{alloc, BPFError}; use alloc::Alloc; use solana_rbpf::{ - ebpf::{EbpfError, SyscallObject, ELF_INSN_DUMP_OFFSET, MM_HEAP_START}, + ebpf::{ELF_INSN_DUMP_OFFSET, MM_HEAP_START}, + error::EbpfError, memory_region::{translate_addr, MemoryRegion}, - EbpfVm, + vm::{EbpfVm, SyscallObject}, }; use solana_runtime::message_processor::MessageProcessor; use solana_sdk::{ @@ -1282,7 +1283,7 @@ mod tests { assert_eq!( Err(EbpfError::AccessViolation( "programs/bpf_loader/src/syscalls.rs".to_string(), - 247, + 248, 100, 32, " regions: \n0x64-0x73".to_string() diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 53efdee76b..27e2acc774 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -13,7 +13,7 @@ use crate::{ builtins::get_builtins, epoch_stakes::{EpochStakes, NodeVoteAccounts}, log_collector::LogCollector, - message_processor::MessageProcessor, + message_processor::{Executors, MessageProcessor}, nonce_utils, rent_collector::RentCollector, stakes::Stakes, @@ -35,7 +35,9 @@ use solana_sdk::{ Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, SECONDS_PER_DAY, }, - entrypoint_native::{ComputeBudget, ProcessInstruction, ProcessInstructionWithContext}, + entrypoint_native::{ + ComputeBudget, Executor, ProcessInstruction, ProcessInstructionWithContext, + }, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, @@ -44,6 +46,7 @@ use solana_sdk::{ hash::{extend_and_hash, hashv, Hash}, incinerator, inflation::Inflation, + message::Message, native_loader, native_token::sol_to_lamports, nonce, @@ -143,6 +146,58 @@ impl Builtin { } } +const MAX_CACHED_EXECUTORS: usize = 100; // 10 MB assuming programs are around 100k + +/// LFU Cache of executors +#[derive(AbiExample)] +struct CachedExecutors { + max: usize, + executors: HashMap)>, +} +impl Default for CachedExecutors { + fn default() -> Self { + Self { + max: MAX_CACHED_EXECUTORS, + executors: HashMap::new(), + } + } +} +impl CachedExecutors { + pub fn new(max: usize) -> Self { + Self { + max, + executors: HashMap::new(), + } + } + pub fn get(&self, pubkey: &Pubkey) -> Option> { + self.executors.get(pubkey).map(|(count, executor)| { + count.fetch_add(1, Ordering::Relaxed); + executor.clone() + }) + } + pub fn put(&mut self, pubkey: &Pubkey, executor: Arc) { + if !self.executors.contains_key(pubkey) { + if self.executors.len() >= self.max { + let mut least = u64::MAX; + let default_key = Pubkey::default(); + let mut least_key = &default_key; + for (key, (count, _)) in self.executors.iter() { + let count = count.load(Ordering::Relaxed); + if count < least { + least = count; + least_key = key; + } + } + let least_key = *least_key; + let _ = self.executors.remove(&least_key); + } + let _ = self + .executors + .insert(*pubkey, (AtomicU64::new(0), executor)); + } + } +} + #[derive(Default)] pub struct BankRc { /// where all the Accounts are stored @@ -333,9 +388,9 @@ pub(crate) struct BankFieldsToSerialize<'a> { } /// Manager for the state of all accounts and programs after processing its entries. -/// AbiExample is needed even without Serialize/Deserialize; actual (de-)serializeaion +/// AbiExample is needed even without Serialize/Deserialize; actual (de-)serialization /// are implemented elsewhere for versioning -#[derive(Default, AbiExample)] +#[derive(AbiExample, Default)] pub struct Bank { /// References to accounts, parent and signature status pub rc: BankRc, @@ -458,6 +513,9 @@ pub struct Bank { // this is temporary field only to remove rewards_pool entirely pub rewards_pool_pubkeys: Arc>, + + /// Cached executors + cached_executors: Arc>, } impl Default for BlockhashQueue { @@ -536,7 +594,7 @@ impl Bank { epoch, blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()), - // TODO: clean this up, soo much special-case copying... + // TODO: clean this up, so much special-case copying... hashes_per_tick: parent.hashes_per_tick, ticks_per_slot: parent.ticks_per_slot, ns_per_slot: parent.ns_per_slot, @@ -575,6 +633,7 @@ impl Bank { cluster_type: parent.cluster_type, lazy_rent_collection: AtomicBool::new(parent.lazy_rent_collection.load(Relaxed)), rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(), + cached_executors: parent.cached_executors.clone(), }; datapoint_info!( @@ -676,6 +735,7 @@ impl Bank { cluster_type: Some(genesis_config.cluster_type), lazy_rent_collection: new(), rewards_pool_pubkeys: new(), + cached_executors: Arc::new(RwLock::new(CachedExecutors::new(MAX_CACHED_EXECUTORS))), }; bank.finish_init(genesis_config); @@ -1804,6 +1864,49 @@ impl Bank { }); } + /// Get any cached executors needed by the transaction + fn get_executors( + &self, + message: &Message, + loaders: &[Vec<(Pubkey, Account)>], + ) -> Rc> { + let mut num_executors = message.account_keys.len(); + for instruction_loaders in loaders.iter() { + num_executors += instruction_loaders.len(); + } + let mut executors = HashMap::with_capacity(num_executors); + let cache = self.cached_executors.read().unwrap(); + + for key in message.account_keys.iter() { + if let Some(executor) = cache.get(key) { + executors.insert(*key, executor); + } + } + for instruction_loaders in loaders.iter() { + for (key, _) in instruction_loaders.iter() { + if let Some(executor) = cache.get(key) { + executors.insert(*key, executor); + } + } + } + + Rc::new(RefCell::new(Executors { + executors, + is_dirty: false, + })) + } + + /// Add executors back to the bank's cache if modified + fn update_executors(&self, executors: Rc>) { + let executors = executors.borrow(); + if executors.is_dirty { + let mut cache = self.cached_executors.write().unwrap(); + for (key, executor) in executors.executors.iter() { + cache.put(key, (*executor).clone()); + } + } + } + #[allow(clippy::type_complexity)] pub fn load_and_execute_transactions( &self, @@ -1861,6 +1964,8 @@ impl Bank { (Ok((accounts, loaders, _rents)), hash_age_kind) => { signature_count += u64::from(tx.message().header.num_required_signatures); + let executors = self.get_executors(&tx.message, &loaders); + let (account_refcells, loader_refcells) = Self::accounts_to_refcells(accounts, loaders); @@ -1870,6 +1975,7 @@ impl Bank { &account_refcells, &self.rent_collector, log_collector.clone(), + executors.clone(), ); Self::refcells_to_accounts( @@ -1879,6 +1985,8 @@ impl Bank { loader_refcells, ); + self.update_executors(executors); + if let Err(TransactionError::InstructionError(_, _)) = &process_result { error_counters.instruction_error += 1; } @@ -3466,6 +3574,7 @@ mod tests { account::KeyedAccount, account_utils::StateMut, clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT}, + entrypoint_native::InvokeContext, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, genesis_config::create_genesis_config, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, @@ -8629,4 +8738,118 @@ mod tests { info!("NOT-asserting overflowing capitalization for bank0"); } } + + struct TestExecutor {} + impl Executor for TestExecutor { + fn execute( + &self, + _program_id: &Pubkey, + _keyed_accounts: &[KeyedAccount], + _instruction_data: &[u8], + _invoke_context: &mut dyn InvokeContext, + ) -> std::result::Result<(), InstructionError> { + Ok(()) + } + } + + #[test] + fn test_cached_executors() { + let key1 = Pubkey::new_rand(); + let key2 = Pubkey::new_rand(); + let key3 = Pubkey::new_rand(); + let key4 = Pubkey::new_rand(); + let executor: Arc = Arc::new(TestExecutor {}); + let mut cache = CachedExecutors::new(3); + + cache.put(&key1, executor.clone()); + cache.put(&key2, executor.clone()); + cache.put(&key3, executor.clone()); + assert!(cache.get(&key1).is_some()); + assert!(cache.get(&key2).is_some()); + assert!(cache.get(&key3).is_some()); + + assert!(cache.get(&key1).is_some()); + assert!(cache.get(&key1).is_some()); + assert!(cache.get(&key2).is_some()); + cache.put(&key4, executor.clone()); + assert!(cache.get(&key1).is_some()); + assert!(cache.get(&key2).is_some()); + assert!(cache.get(&key3).is_none()); + assert!(cache.get(&key4).is_some()); + + assert!(cache.get(&key4).is_some()); + assert!(cache.get(&key4).is_some()); + assert!(cache.get(&key4).is_some()); + cache.put(&key3, executor.clone()); + assert!(cache.get(&key1).is_some()); + assert!(cache.get(&key2).is_none()); + assert!(cache.get(&key3).is_some()); + assert!(cache.get(&key4).is_some()); + } + + #[test] + fn test_bank_executor_cache() { + solana_logger::setup(); + + let (genesis_config, _) = create_genesis_config(1); + let bank = Bank::new(&genesis_config); + + let key1 = Pubkey::new_rand(); + let key2 = Pubkey::new_rand(); + let key3 = Pubkey::new_rand(); + let key4 = Pubkey::new_rand(); + let executor: Arc = Arc::new(TestExecutor {}); + + let message = Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 1, + }, + account_keys: vec![key1, key2], + recent_blockhash: Hash::default(), + instructions: vec![], + }; + + let loaders = &[ + vec![(key3, Account::default()), (key4, Account::default())], + vec![(key1, Account::default())], + ]; + + // don't do any work if not dirty + let mut executors = Executors::default(); + executors.insert(key1, executor.clone()); + executors.insert(key2, executor.clone()); + executors.insert(key3, executor.clone()); + executors.insert(key4, executor.clone()); + let executors = Rc::new(RefCell::new(executors)); + executors.borrow_mut().is_dirty = false; + bank.update_executors(executors); + let executors = bank.get_executors(&message, loaders); + assert_eq!(executors.borrow().executors.len(), 0); + + // do work + let mut executors = Executors::default(); + executors.insert(key1, executor.clone()); + executors.insert(key2, executor.clone()); + executors.insert(key3, executor.clone()); + executors.insert(key4, executor.clone()); + let executors = Rc::new(RefCell::new(executors)); + bank.update_executors(executors); + let executors = bank.get_executors(&message, loaders); + assert_eq!(executors.borrow().executors.len(), 4); + assert!(executors.borrow().executors.contains_key(&key1)); + assert!(executors.borrow().executors.contains_key(&key2)); + assert!(executors.borrow().executors.contains_key(&key3)); + assert!(executors.borrow().executors.contains_key(&key4)); + + // Check inheritance + let bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::new_rand(), 1); + let executors = bank.get_executors(&message, loaders); + assert_eq!(executors.borrow().executors.len(), 4); + assert!(executors.borrow().executors.contains_key(&key1)); + assert!(executors.borrow().executors.contains_key(&key2)); + assert!(executors.borrow().executors.contains_key(&key3)); + assert!(executors.borrow().executors.contains_key(&key4)); + } } diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index c102405ad9..38add45126 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -8,7 +8,7 @@ use solana_sdk::{ clock::Epoch, entrypoint_native::{ ComputeBudget, ComputeMeter, ErasedProcessInstruction, ErasedProcessInstructionWithContext, - InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext, + Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext, }, instruction::{CompiledInstruction, InstructionError}, message::Message, @@ -18,7 +18,29 @@ use solana_sdk::{ system_program, transaction::TransactionError, }; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; + +pub struct Executors { + pub executors: HashMap>, + pub is_dirty: bool, +} +impl Default for Executors { + fn default() -> Self { + Self { + executors: HashMap::default(), + is_dirty: false, + } + } +} +impl Executors { + pub fn insert(&mut self, key: Pubkey, executor: Arc) { + let _ = self.executors.insert(key, executor); + self.is_dirty = true; + } + pub fn get(&self, key: &Pubkey) -> Option> { + self.executors.get(key).cloned() + } +} // The relevant state of an account before an Instruction executes, used // to verify account integrity after the Instruction completes @@ -182,6 +204,7 @@ pub struct ThisInvokeContext { is_cross_program_supported: bool, compute_budget: ComputeBudget, compute_meter: Rc>, + executors: Rc>, } impl ThisInvokeContext { pub fn new( @@ -192,6 +215,7 @@ impl ThisInvokeContext { log_collector: Option>, is_cross_program_supported: bool, compute_budget: ComputeBudget, + executors: Rc>, ) -> Self { let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth); program_ids.push(*program_id); @@ -206,6 +230,7 @@ impl ThisInvokeContext { compute_meter: Rc::new(RefCell::new(ThisComputeMeter { remaining: compute_budget.max_units, })), + executors, } } } @@ -262,6 +287,12 @@ impl InvokeContext for ThisInvokeContext { fn get_compute_meter(&self) -> Rc> { self.compute_meter.clone() } + fn add_executor(&mut self, pubkey: &Pubkey, executor: Arc) { + self.executors.borrow_mut().insert(*pubkey, executor); + } + fn get_executor(&mut self, pubkey: &Pubkey) -> Option> { + self.executors.borrow().get(&pubkey) + } } pub struct ThisLogger { log_collector: Option>, @@ -633,6 +664,7 @@ impl MessageProcessor { accounts: &[Rc>], rent_collector: &RentCollector, log_collector: Option>, + executors: Rc>, ) -> Result<(), InstructionError> { let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); let mut invoke_context = ThisInvokeContext::new( @@ -643,6 +675,7 @@ impl MessageProcessor { log_collector, self.is_cross_program_supported, self.compute_budget, + executors, ); let keyed_accounts = Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; @@ -668,6 +701,7 @@ impl MessageProcessor { accounts: &[Rc>], rent_collector: &RentCollector, log_collector: Option>, + executors: Rc>, ) -> Result<(), TransactionError> { for (instruction_index, instruction) in message.instructions.iter().enumerate() { self.execute_instruction( @@ -677,6 +711,7 @@ impl MessageProcessor { accounts, rent_collector, log_collector.clone(), + executors.clone(), ) .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; } @@ -733,6 +768,7 @@ mod tests { None, true, ComputeBudget::default(), + Rc::new(RefCell::new(Executors::default())), ); // Check call depth increases and has a limit @@ -1244,6 +1280,8 @@ mod tests { let account = RefCell::new(create_loadable_account("mock_system_program")); loaders.push(vec![(mock_system_program_id, account)]); + let executors = Rc::new(RefCell::new(Executors::default())); + let from_pubkey = Pubkey::new_rand(); let to_pubkey = Pubkey::new_rand(); let account_metas = vec![ @@ -1259,8 +1297,14 @@ mod tests { Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors.clone(), + ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 100); assert_eq!(accounts[1].borrow().lamports, 0); @@ -1274,8 +1318,14 @@ mod tests { Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors.clone(), + ); assert_eq!( result, Err(TransactionError::InstructionError( @@ -1293,8 +1343,14 @@ mod tests { Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors, + ); assert_eq!( result, Err(TransactionError::InstructionError( @@ -1375,6 +1431,8 @@ mod tests { let account = RefCell::new(create_loadable_account("mock_system_program")); loaders.push(vec![(mock_program_id, account)]); + let executors = Rc::new(RefCell::new(Executors::default())); + let from_pubkey = Pubkey::new_rand(); let to_pubkey = Pubkey::new_rand(); let dup_pubkey = from_pubkey; @@ -1393,8 +1451,14 @@ mod tests { )], Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors.clone(), + ); assert_eq!( result, Err(TransactionError::InstructionError( @@ -1412,8 +1476,14 @@ mod tests { )], Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors.clone(), + ); assert_eq!(result, Ok(())); // Do work on the same account but at different location in keyed_accounts[] @@ -1428,8 +1498,14 @@ mod tests { )], Some(&from_pubkey), ); - let result = - message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None); + let result = message_processor.process_message( + &message, + &loaders, + &accounts, + &rent_collector, + None, + executors, + ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 80); assert_eq!(accounts[1].borrow().lamports, 20); @@ -1504,6 +1580,7 @@ mod tests { None, true, ComputeBudget::default(), + Rc::new(RefCell::new(Executors::default())), ); let metas = vec![ AccountMeta::new(owned_key, false), diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index ba2a5c3c75..64994de89a 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -1,10 +1,12 @@ //! @brief Solana Native program entry point +#[cfg(RUSTC_WITH_SPECIALIZATION)] +use crate::abi_example::AbiExample; use crate::{ account::Account, account::KeyedAccount, instruction::CompiledInstruction, instruction::InstructionError, message::Message, pubkey::Pubkey, }; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; // Prototype of a native program entry point /// @@ -218,6 +220,11 @@ pub trait InvokeContext { fn get_compute_budget(&self) -> ComputeBudget; /// Get this invocation's compute meter fn get_compute_meter(&self) -> Rc>; + /// Loaders may need to do work in order to execute a program. Cache + /// the work that can be re-used across executions + fn add_executor(&mut self, pubkey: &Pubkey, executor: Arc); + /// Get the completed loader work that can be re-used across executions + fn get_executor(&mut self, pubkey: &Pubkey) -> Option>; } #[derive(Clone, Copy, Debug)] @@ -265,3 +272,33 @@ pub trait Logger { /// Log a message fn log(&mut self, message: &str); } + +/// Program executor +pub trait Executor: Send + Sync { + /// Execute the program + fn execute( + &self, + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError>; +} +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl AbiExample for Arc { + fn example() -> Self { + struct ExampleExecutor {} + impl Executor for ExampleExecutor { + fn execute( + &self, + _program_id: &Pubkey, + _keyed_accounts: &[KeyedAccount], + _instruction_data: &[u8], + _invoke_context: &mut dyn InvokeContext, + ) -> std::result::Result<(), InstructionError> { + Ok(()) + } + } + Arc::new(ExampleExecutor {}) + } +}