From 882d0332333384fca1c4ae9adb57f3610de659f7 Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 4 Dec 2020 21:54:42 -0800 Subject: [PATCH] Don't use stale executor cache (#13966) --- programs/bpf/Cargo.lock | 14 + programs/bpf/Cargo.toml | 2 + programs/bpf/build.rs | 20 +- programs/bpf/rust/invoke_and_error/Cargo.toml | 18 ++ programs/bpf/rust/invoke_and_error/Xargo.toml | 2 + programs/bpf/rust/invoke_and_error/src/lib.rs | 32 +++ programs/bpf/rust/invoke_and_ok/Cargo.toml | 18 ++ programs/bpf/rust/invoke_and_ok/Xargo.toml | 2 + programs/bpf/rust/invoke_and_ok/src/lib.rs | 32 +++ programs/bpf/tests/programs.rs | 246 +++++++++++++++++- runtime/src/bank.rs | 34 +-- 11 files changed, 391 insertions(+), 29 deletions(-) create mode 100644 programs/bpf/rust/invoke_and_error/Cargo.toml create mode 100644 programs/bpf/rust/invoke_and_error/Xargo.toml create mode 100644 programs/bpf/rust/invoke_and_error/src/lib.rs create mode 100644 programs/bpf/rust/invoke_and_ok/Cargo.toml create mode 100644 programs/bpf/rust/invoke_and_ok/Xargo.toml create mode 100644 programs/bpf/rust/invoke_and_ok/src/lib.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 0c99a29fb2..789dbf72bd 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1984,6 +1984,20 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-bpf-rust-invoke-and-error" +version = "1.5.0" +dependencies = [ + "solana-program", +] + +[[package]] +name = "solana-bpf-rust-invoke-and-ok" +version = "1.5.0" +dependencies = [ + "solana-program", +] + [[package]] name = "solana-bpf-rust-invoked" version = "1.5.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 961a7b6337..ccfc73a552 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -47,6 +47,8 @@ members = [ "rust/external_spend", "rust/instruction_introspection", "rust/invoke", + "rust/invoke_and_error", + "rust/invoke_and_ok", "rust/invoked", "rust/iter", "rust/many_args", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index a7cfae5a99..7c5068b9ee 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -37,17 +37,17 @@ fn rerun_if_changed(files: &[&str], directories: &[&str], excludes: &[&str]) { fn main() { 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(); + let install_dir = + "OUT_DIR=../target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); println!("cargo:warning=(not a warning) Building C-based BPF programs"); - assert!(Command::new("make") - .current_dir("c") - .arg("programs") - .arg(&install_dir) - .status() - .expect("Failed to build C-based BPF programs") - .success()); + assert!(Command::new("make") + .current_dir("c") + .arg("programs") + .arg(&install_dir) + .status() + .expect("Failed to build C-based BPF programs") + .success()); rerun_if_changed(&["c/makefile"], &["c/src", "../../sdk"], &["/target/"]); } @@ -70,6 +70,8 @@ fn main() { "external_spend", "instruction_introspection", "invoke", + "invoke_and_error", + "invoke_and_ok", "invoked", "iter", "many_args", diff --git a/programs/bpf/rust/invoke_and_error/Cargo.toml b/programs/bpf/rust/invoke_and_error/Cargo.toml new file mode 100644 index 0000000000..a4673532d1 --- /dev/null +++ b/programs/bpf/rust/invoke_and_error/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "solana-bpf-rust-invoke-and-error" +version = "1.5.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "1.5.0" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/invoke_and_error/Xargo.toml b/programs/bpf/rust/invoke_and_error/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/invoke_and_error/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/invoke_and_error/src/lib.rs b/programs/bpf/rust/invoke_and_error/src/lib.rs new file mode 100644 index 0000000000..2501210751 --- /dev/null +++ b/programs/bpf/rust/invoke_and_error/src/lib.rs @@ -0,0 +1,32 @@ +//! @brief Invokes an instruction and returns an error, the instruction invoked +//! uses the instruction data provided and all the accounts + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, instruction::AccountMeta, + instruction::Instruction, program::invoke, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let to_call = accounts[0].key; + let infos = accounts; + let instruction = Instruction { + accounts: accounts[1..] + .iter() + .map(|acc| AccountMeta { + pubkey: *acc.key, + is_signer: acc.is_signer, + is_writable: acc.is_writable, + }) + .collect(), + data: instruction_data.to_owned(), + program_id: *to_call, + }; + let _ = invoke(&instruction, &infos); + + Err(42.into()) +} diff --git a/programs/bpf/rust/invoke_and_ok/Cargo.toml b/programs/bpf/rust/invoke_and_ok/Cargo.toml new file mode 100644 index 0000000000..c71f50dd84 --- /dev/null +++ b/programs/bpf/rust/invoke_and_ok/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "solana-bpf-rust-invoke-and-ok" +version = "1.5.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "1.5.0" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/invoke_and_ok/Xargo.toml b/programs/bpf/rust/invoke_and_ok/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/invoke_and_ok/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/invoke_and_ok/src/lib.rs b/programs/bpf/rust/invoke_and_ok/src/lib.rs new file mode 100644 index 0000000000..d2ad45999b --- /dev/null +++ b/programs/bpf/rust/invoke_and_ok/src/lib.rs @@ -0,0 +1,32 @@ +//! @brief Invokes an instruction and returns an error, the instruction invoked +//! uses the instruction data provided and all the accounts + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, instruction::AccountMeta, + instruction::Instruction, program::invoke, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let to_call = accounts[0].key; + let infos = accounts; + let instruction = Instruction { + accounts: accounts[1..] + .iter() + .map(|acc| AccountMeta { + pubkey: *acc.key, + is_signer: acc.is_signer, + is_writable: acc.is_writable, + }) + .collect(), + data: instruction_data.to_owned(), + program_id: *to_call, + }; + let _ = invoke(&instruction, &infos); + + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 5a61b02486..f4a913f127 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -24,10 +24,12 @@ use solana_sdk::{ entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, keyed_account::KeyedAccount, + loader_instruction, message::Message, process_instruction::{BpfComputeBudget, InvokeContext, MockInvokeContext}, pubkey::Pubkey, signature::{Keypair, Signer}, + system_instruction, sysvar::{clock, fees, rent, slot_hashes, stake_history}, transaction::{Transaction, TransactionError}, }; @@ -54,13 +56,41 @@ fn load_bpf_program( payer_keypair: &Keypair, name: &str, ) -> Pubkey { + let elf = read_bpf_program(name); + load_program(bank_client, payer_keypair, loader_id, elf) +} + +fn read_bpf_program(name: &str) -> Vec { let path = create_bpf_path(name); let mut file = File::open(&path).unwrap_or_else(|err| { panic!("Failed to open {}: {}", path.display(), err); }); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - load_program(bank_client, payer_keypair, loader_id, elf) + + elf +} + +fn write_bpf_program( + bank_client: &BankClient, + loader_id: &Pubkey, + payer_keypair: &Keypair, + program_keypair: &Keypair, + elf: &[u8], +) { + let chunk_size = 256; // Size of chunk just needs to fit into tx + let mut offset = 0; + for chunk in elf.chunks(chunk_size) { + let instruction = + loader_instruction::write(&program_keypair.pubkey(), loader_id, offset, chunk.to_vec()); + let message = Message::new(&[instruction], Some(&payer_keypair.pubkey())); + + bank_client + .send_and_confirm_message(&[payer_keypair, &program_keypair], message) + .unwrap(); + + offset += chunk_size as u32; + } } fn run_program( @@ -931,7 +961,6 @@ fn test_program_bpf_ro_modify() { let instruction = Instruction::new(program_pubkey, &[1_u8], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); - println!("result {:?}", result); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(0xb9f0002)) @@ -940,7 +969,6 @@ fn test_program_bpf_ro_modify() { let instruction = Instruction::new(program_pubkey, &[3_u8], account_metas.clone()); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); - println!("result {:?}", result); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError(0, InstructionError::Custom(0xb9f0002)) @@ -1107,3 +1135,215 @@ fn test_program_bpf_instruction_introspection() { .get_account(&solana_sdk::sysvar::instructions::id()) .is_none()); } + +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_test_use_latest_executor() { + 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(&name, id, entrypoint); + let bank_client = BankClient::new(bank); + let panic_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_panic", + ); + + let program_keypair = Keypair::new(); + + // Write the panic program into the program account + let elf = read_bpf_program("solana_bpf_rust_panic"); + let message = Message::new( + &[system_instruction::create_account( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + 1, + elf.len() as u64 * 2, + &bpf_loader::id(), + )], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + write_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + &program_keypair, + &elf, + ); + + // Finalize the panic program, but fail the tx + let message = Message::new( + &[ + loader_instruction::finalize(&program_keypair.pubkey(), &bpf_loader::id()), + Instruction::new(panic_id, &0u8, vec![]), + ], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_err()); + + // Write the noop program into the same program account + let elf = read_bpf_program("solana_bpf_rust_noop"); + write_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + &program_keypair, + &elf, + ); + + // Finalize the noop program + let message = Message::new( + &[loader_instruction::finalize( + &program_keypair.pubkey(), + &bpf_loader::id(), + )], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + + // Call the noop program, should get noop not panic + let message = Message::new( + &[Instruction::new(program_keypair.pubkey(), &0u8, vec![])], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair], message) + .is_ok()); +} + +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_test_use_latest_executor2() { + 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(&name, id, entrypoint); + let bank_client = BankClient::new(bank); + let invoke_and_error = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_invoke_and_error", + ); + let invoke_and_ok = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_invoke_and_ok", + ); + + let program_keypair = Keypair::new(); + + // Write the panic program into the program account + let elf = read_bpf_program("solana_bpf_rust_panic"); + let message = Message::new( + &[system_instruction::create_account( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + 1, + elf.len() as u64 * 2, + &bpf_loader::id(), + )], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + write_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + &program_keypair, + &elf, + ); + + // - invoke finalize and return error, swallow error + let mut instruction = + loader_instruction::finalize(&program_keypair.pubkey(), &bpf_loader::id()); + instruction.accounts.insert( + 0, + AccountMeta { + is_signer: false, + is_writable: false, + pubkey: instruction.program_id, + }, + ); + instruction.program_id = invoke_and_ok; + instruction.accounts.insert( + 0, + AccountMeta { + is_signer: false, + is_writable: false, + pubkey: invoke_and_error, + }, + ); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + + // invoke program, verify not found + let message = Message::new( + &[Instruction::new(program_keypair.pubkey(), &0u8, vec![])], + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + bank_client + .send_and_confirm_message(&[&mint_keypair], message) + .unwrap_err() + .unwrap(), + TransactionError::InvalidProgramForExecution + ); + + // Write the noop program into the same program account + let elf = read_bpf_program("solana_bpf_rust_noop"); + write_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + &program_keypair, + &elf, + ); + + // Finalize the noop program + let message = Message::new( + &[loader_instruction::finalize( + &program_keypair.pubkey(), + &bpf_loader::id(), + )], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + + // Call the program, should get noop, not panic + let message = Message::new( + &[Instruction::new(program_keypair.pubkey(), &0u8, vec![])], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair], message) + .is_ok()); +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 06e8ca2392..eab3db251c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -273,25 +273,23 @@ impl CachedExecutors { }) } 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(Relaxed); - if count < least { - least = count; - least_key = key; - } + if !self.executors.contains_key(pubkey) && 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(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)); + let least_key = *least_key; + let _ = self.executors.remove(&least_key); } + let _ = self + .executors + .insert(*pubkey, (AtomicU64::new(0), executor)); } fn remove(&mut self, pubkey: &Pubkey) { let _ = self.executors.remove(pubkey); @@ -2786,7 +2784,9 @@ impl Bank { loader_refcells, ); - self.update_executors(executors); + if process_result.is_ok() { + self.update_executors(executors); + } let nonce_rollback = if let Err(TransactionError::InstructionError(_, _)) = &process_result {