From fab2d44abdd8d86417220c421d11a607d89bc62c Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 1 Sep 2020 17:25:55 -0700 Subject: [PATCH] Add BPF test program instruction monitoring (#11984) --- programs/bpf/build.rs | 6 +- programs/bpf/tests/programs.rs | 166 +++++++++++++++++++++++++++++++-- 2 files changed, 163 insertions(+), 9 deletions(-) diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 24b0c0cfcc..eaaedc7574 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -35,7 +35,7 @@ fn rerun_if_changed(files: &[&str], directories: &[&str], excludes: &[&str]) { } fn main() { - let bpf_c = !env::var("CARGO_FEATURE_BPF_C").is_err(); + let bpf_c = env::var("CARGO_FEATURE_BPF_C").is_ok(); if bpf_c { let install_dir = "OUT_DIR=../target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); @@ -52,7 +52,7 @@ fn main() { rerun_if_changed(&["c/makefile"], &["c/src", "../../sdk"], &["/target/"]); } - let bpf_rust = !env::var("CARGO_FEATURE_BPF_RUST").is_err(); + let bpf_rust = env::var("CARGO_FEATURE_BPF_RUST").is_ok(); if bpf_rust { let install_dir = "target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); @@ -100,7 +100,7 @@ fn main() { .arg(&src) .arg(&install_dir) .status() - .expect(&format!("Failed to cp {} to {}", src, install_dir)) + .unwrap_or_else(|_| panic!("Failed to cp {} to {}", src, install_dir)) .success()); } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 4a98990849..3dc557bfb7 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -3,6 +3,11 @@ #[macro_use] extern crate solana_bpf_loader_program; +use solana_bpf_loader_program::{ + create_vm, + serialization::{deserialize_parameters, serialize_parameters}, +}; +use solana_rbpf::InstructionMeter; use solana_runtime::{ bank::Bank, bank_client::BankClient, @@ -10,20 +15,20 @@ use solana_runtime::{ loader_utils::load_program, }; use solana_sdk::{ - account::Account, + account::{Account, KeyedAccount}, bpf_loader, bpf_loader_deprecated, client::SyncClient, clock::DEFAULT_SLOTS_PER_EPOCH, - entrypoint::MAX_PERMITTED_DATA_INCREASE, - instruction::{AccountMeta, Instruction, InstructionError}, + entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, + entrypoint_native::{ComputeBudget, ComputeMeter, InvokeContext, Logger, ProcessInstruction}, + instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, message::Message, pubkey::Pubkey, - signature::Keypair, - signature::Signer, + signature::{Keypair, Signer}, sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, transaction::TransactionError, }; -use std::{env, fs::File, io::Read, path::PathBuf, sync::Arc}; +use std::{cell::RefCell, env, fs::File, io::Read, path::PathBuf, rc::Rc, sync::Arc}; /// BPF program file extension const PLATFORM_FILE_EXTENSION_BPF: &str = "so"; @@ -53,6 +58,42 @@ fn load_bpf_program( load_program(bank_client, payer_keypair, loader_id, elf) } +fn run_program( + name: &str, + program_id: &Pubkey, + parameter_accounts: &[KeyedAccount], + instruction_data: &[u8], +) -> Result { + 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 invoke_context = MockInvokeContext::default(); + let (mut vm, heap_region) = create_vm( + &bpf_loader::id(), + &program_account.data, + parameter_accounts, + &mut invoke_context, + ) + .unwrap(); + let mut parameter_bytes = serialize_parameters( + &bpf_loader::id(), + program_id, + parameter_accounts, + &instruction_data, + ) + .unwrap(); + assert_eq!( + SUCCESS, + vm.execute_program(parameter_bytes.as_mut_slice(), &[], &[heap_region.clone()]) + .unwrap() + ); + deserialize_parameters(&bpf_loader::id(), parameter_accounts, ¶meter_bytes).unwrap(); + Ok(vm.get_total_instruction_count()) +} + #[test] #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] fn test_program_bpf_sanity() { @@ -506,3 +547,116 @@ fn test_program_bpf_invoke() { } } } + +#[test] +fn assert_instruction_count() { + solana_logger::setup(); + + let mut programs = Vec::new(); + #[cfg(feature = "bpf_c")] + { + programs.extend_from_slice(&[ + ("bpf_to_bpf", 13), + ("multiple_static", 8), + ("noop", 1140), + ("noop++", 1140), + ("relative_call", 10), + ("struct_pass", 8), + ("struct_ret", 22), + ]); + } + #[cfg(feature = "bpf_rust")] + { + programs.extend_from_slice(&[ + ("solana_bpf_rust_128bit", 543), + ("solana_bpf_rust_alloc", 19082), + ("solana_bpf_rust_dep_crate", 2), + ("solana_bpf_rust_external_spend", 465), + ("solana_bpf_rust_iter", 723), + ("solana_bpf_rust_many_args", 231), + ("solana_bpf_rust_noop", 2209), + ("solana_bpf_rust_param_passing", 54), + ]); + } + + for program in programs.iter() { + println!("Test program: {:?}", program.0); + let program_id = Pubkey::new_rand(); + let key = Pubkey::new_rand(); + let mut account = RefCell::new(Account::default()); + let parameter_accounts = vec![KeyedAccount::new(&key, false, &mut account)]; + let count = run_program(program.0, &program_id, ¶meter_accounts[..], &[]).unwrap(); + println!(" {} : {:?} ({:?})", program.0, count, program.1,); + assert!(count <= program.1); + } +} + +// Mock InvokeContext + +#[derive(Debug, Default)] +struct MockInvokeContext { + pub key: Pubkey, + pub logger: MockLogger, + pub compute_meter: MockComputeMeter, +} +impl InvokeContext for MockInvokeContext { + fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> { + Ok(()) + } + fn pop(&mut self) {} + fn verify_and_update( + &mut self, + _message: &Message, + _instruction: &CompiledInstruction, + _accounts: &[Rc>], + ) -> Result<(), InstructionError> { + Ok(()) + } + fn get_caller(&self) -> Result<&Pubkey, InstructionError> { + Ok(&self.key) + } + fn get_programs(&self) -> &[(Pubkey, ProcessInstruction)] { + &[] + } + fn get_logger(&self) -> Rc> { + Rc::new(RefCell::new(self.logger.clone())) + } + fn is_cross_program_supported(&self) -> bool { + true + } + fn get_compute_budget(&self) -> ComputeBudget { + ComputeBudget::default() + } + fn get_compute_meter(&self) -> Rc> { + Rc::new(RefCell::new(self.compute_meter.clone())) + } +} + +#[derive(Debug, Default, Clone)] +struct MockComputeMeter {} +impl ComputeMeter for MockComputeMeter { + fn consume(&mut self, _amount: u64) -> Result<(), InstructionError> { + Ok(()) + } + fn get_remaining(&self) -> u64 { + u64::MAX + } +} +#[derive(Debug, Default, Clone)] +struct MockLogger {} +impl Logger for MockLogger { + fn log_enabled(&self) -> bool { + true + } + fn log(&mut self, _message: &str) { + // println!("{}", message); + } +} + +struct TestInstructionMeter {} +impl InstructionMeter for TestInstructionMeter { + fn consume(&mut self, _amount: u64) {} + fn get_remaining(&self) -> u64 { + u64::MAX + } +}