diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index cb1ae07d33..982eefff57 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1963,6 +1963,20 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-bpf-rust-invoke-and-error" +version = "1.4.15" +dependencies = [ + "solana-program", +] + +[[package]] +name = "solana-bpf-rust-invoke-and-ok" +version = "1.4.15" +dependencies = [ + "solana-program", +] + [[package]] name = "solana-bpf-rust-invoked" version = "1.4.15" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index d4e1d8156f..36c7650627 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 66a6a03769..2132bed9a0 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -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..89630a9302 --- /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.4.15" +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.4.15" } + +[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..368bd6ce15 --- /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.4.15" +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.4.15" } + +[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 4ec5e0f89f..5b1e4bf19f 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -22,10 +22,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, MockInvokeContext}, pubkey::Pubkey, signature::{Keypair, Signer}, + system_instruction, sysvar::{clock, fees, rent, slot_hashes, stake_history}, transaction::{Transaction, TransactionError}, }; @@ -52,13 +54,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( @@ -1009,3 +1039,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 * 10, // needs to be big enough for second write + &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 * 10, // needs to be big enough for second write + &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 4e709e5608..01a802da35 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -275,25 +275,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); @@ -2788,7 +2786,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 {