From 4a72c2b054932f07e5108bf76ddd2b359abdc421 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 20 May 2020 09:24:57 -0700 Subject: [PATCH] Support cross-program invocation to native programs (#10136) --- programs/bpf/c/src/invoke/invoke.c | 31 +++++++- programs/bpf/c/src/invoked/invoked.c | 8 +- programs/bpf/rust/invoke/src/lib.rs | 14 ++++ programs/bpf/tests/programs.rs | 12 ++- programs/bpf_loader/src/syscalls.rs | 15 ++-- runtime/src/message_processor.rs | 114 ++++++++++++++------------- 6 files changed, 124 insertions(+), 70 deletions(-) diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index f253c6d084..0439ae881f 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -13,17 +13,35 @@ static const int ARGUMENT_DUP_INDEX = 5; static const int DERIVED_KEY1_INDEX = 6; static const int DERIVED_KEY2_INDEX = 7; static const int DERIVED_KEY3_INDEX = 8; +static const int SYSTEM_PROGRAM_INDEX = 9; +static const int FROM_INDEX = 10; extern uint64_t entrypoint(const uint8_t *input) { sol_log("Invoke C program"); - SolAccountInfo accounts[9]; + SolAccountInfo accounts[11]; SolParameters params = (SolParameters){.ka = accounts}; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(accounts))) { return ERROR_INVALID_ARGUMENT; } + sol_log("Call system program"); + { + sol_assert(*accounts[FROM_INDEX].lamports = 43); + sol_assert(*accounts[ARGUMENT_INDEX].lamports = 41); + SolAccountMeta arguments[] = {{accounts[FROM_INDEX].key, false, true}, + {accounts[ARGUMENT_INDEX].key, false, false}}; + uint8_t data[] = {2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}; + const SolInstruction instruction = {accounts[SYSTEM_PROGRAM_INDEX].key, + arguments, SOL_ARRAY_SIZE(arguments), + data, SOL_ARRAY_SIZE(data)}; + sol_assert(SUCCESS == + sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); + sol_assert(*accounts[FROM_INDEX].lamports = 42); + sol_assert(*accounts[ARGUMENT_INDEX].lamports = 42); + } + sol_log("Test data translation"); { for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) { @@ -37,7 +55,8 @@ extern uint64_t entrypoint(const uint8_t *input) { {accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}}; uint8_t data[] = {TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, - arguments, 4, data, 6}; + arguments, SOL_ARRAY_SIZE(arguments), + data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); @@ -70,8 +89,12 @@ extern uint64_t entrypoint(const uint8_t *input) { const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; - const SolSignerSeed seeds1[] = {{"You pass butter", 15}}; - const SolSignerSeed seeds2[] = {{"Lil'", 4}, {"Bits", 4}}; + char seed1[] = "You pass butter"; + char seed2[] = "Lil'"; + char seed3[] = "Bits"; + const SolSignerSeed seeds1[] = {{seed1, sol_strlen(seed1)}}; + const SolSignerSeed seeds2[] = {{seed2, sol_strlen(seed2)}, + {seed3, sol_strlen(seed3)}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, {seeds2, SOL_ARRAY_SIZE(seeds2)}}; sol_assert(SUCCESS == sol_invoke_signed( diff --git a/programs/bpf/c/src/invoked/invoked.c b/programs/bpf/c/src/invoked/invoked.c index 0d18c4abf8..c12f1b1786 100644 --- a/programs/bpf/c/src/invoked/invoked.c +++ b/programs/bpf/c/src/invoked/invoked.c @@ -100,8 +100,12 @@ extern uint64_t entrypoint(const uint8_t *input) { const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; - const SolSignerSeed seeds1[] = {{"Lil'", 4}, {"Bits", 4}}; - const SolSignerSeed seeds2[] = {{"Gar Ma Nar Nar", 14}}; + char seed1[] = "Lil'"; + char seed2[] = "Bits"; + char seed3[] = "Gar Ma Nar Nar"; + const SolSignerSeed seeds1[] = {{seed1, sol_strlen(seed1)}, + {seed2, sol_strlen(seed2)}}; + const SolSignerSeed seeds2[] = {{seed3, sol_strlen(seed3)}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, {seeds2, SOL_ARRAY_SIZE(seeds2)}}; diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index a20fa7ef5c..f80f0466a0 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -13,6 +13,7 @@ use solana_sdk::{ program::{invoke, invoke_signed}, program_error::ProgramError, pubkey::Pubkey, + system_instruction, }; // const MINT_INDEX: usize = 0; @@ -24,6 +25,8 @@ const INVOKED_PROGRAM_DUP_INDEX: usize = 4; const DERIVED_KEY1_INDEX: usize = 6; const DERIVED_KEY2_INDEX: usize = 7; const DERIVED_KEY3_INDEX: usize = 8; +// const SYSTEM_PROGRAM_INDEX: usize = 9; +const FROM_INDEX: usize = 10; entrypoint!(process_instruction); fn process_instruction( @@ -33,6 +36,17 @@ fn process_instruction( ) -> ProgramResult { info!("invoke Rust program"); + info!("Call system program"); + { + assert_eq!(accounts[FROM_INDEX].lamports(), 43); + assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 41); + let instruction = + system_instruction::transfer(accounts[FROM_INDEX].key, accounts[ARGUMENT_INDEX].key, 1); + invoke(&instruction, accounts)?; + assert_eq!(accounts[FROM_INDEX].lamports(), 42); + assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42); + } + info!("Test data translation"); { { diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 5dc0407a27..dd59723a4e 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -336,14 +336,18 @@ mod bpf { 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 account = Account::new(42, 100, &invoke_program_id); let argument_keypair = Keypair::new(); + let account = Account::new(41, 100, &invoke_program_id); bank.store_account(&argument_keypair.pubkey(), &account); - let account = Account::new(10, 10, &invoked_program_id); 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 = Pubkey::create_program_address(&["You pass butter"], &invoke_program_id).unwrap(); let derived_key2 = @@ -361,6 +365,8 @@ mod bpf { 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), ]; let instruction = Instruction::new(invoke_program_id, &1u8, account_metas); @@ -368,7 +374,7 @@ mod bpf { assert!(bank_client .send_message( - &[&mint_keypair, &argument_keypair, &invoked_argument_keypair], + &[&mint_keypair, &argument_keypair, &invoked_argument_keypair, &from_keypair], message, ) .is_ok()); diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 8717beedaf..590380285d 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -6,7 +6,7 @@ use solana_rbpf::{ memory_region::{translate_addr, MemoryRegion}, EbpfVm, }; -use solana_runtime::message_processor::MessageProcessor; +use solana_runtime::{builtin_programs::get_builtin_programs, message_processor::MessageProcessor}; use solana_sdk::{ account::Account, account_info::AccountInfo, @@ -712,19 +712,20 @@ fn call<'a>( // Process instruction let program_account = (*accounts[callee_program_id_index]).clone(); - if program_account.borrow().owner != bpf_loader::id() { - // Only BPF programs supported for now - return Err(SyscallError::ProgramNotSupported.into()); - } let executable_accounts = vec![(callee_program_id, program_account)]; + let mut message_processor = MessageProcessor::default(); + let builtin_programs = get_builtin_programs(); + for program in builtin_programs.iter() { + message_processor.add_program(program.id, program.process_instruction); + } + message_processor.add_loader(bpf_loader::id(), crate::process_instruction); #[allow(clippy::deref_addrof)] - match MessageProcessor::process_cross_program_instruction( + match message_processor.process_cross_program_instruction( &message, &executable_accounts, &accounts, &signers, - crate::process_instruction, *(&mut *invoke_context), ) { Ok(()) => (), diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index e4874cda70..eeb3646f86 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -245,12 +245,15 @@ pub struct MessageProcessor { #[serde(skip)] programs: Vec<(Pubkey, ProcessInstruction)>, #[serde(skip)] + loaders: Vec<(Pubkey, ProcessInstructionWithContext)>, + #[serde(skip)] native_loader: NativeLoader, } impl Clone for MessageProcessor { fn clone(&self) -> Self { MessageProcessor { programs: self.programs.clone(), + loaders: self.loaders.clone(), native_loader: NativeLoader::default(), } } @@ -264,6 +267,17 @@ impl MessageProcessor { } } + pub fn add_loader( + &mut self, + program_id: Pubkey, + process_instruction: ProcessInstructionWithContext, + ) { + match self.loaders.iter_mut().find(|(key, _)| program_id == *key) { + Some((_, processor)) => *processor = process_instruction, + None => self.loaders.push((program_id, process_instruction)), + } + } + /// Create the KeyedAccounts that will be passed to the program fn create_keyed_accounts<'a>( message: &'a Message, @@ -296,46 +310,50 @@ impl MessageProcessor { /// This method calls the instruction's program entrypoint method fn process_instruction( &self, - message: &Message, - instruction: &CompiledInstruction, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], invoke_context: &mut dyn InvokeContext, - executable_accounts: &[(Pubkey, RefCell)], - accounts: &[Rc>], ) -> Result<(), InstructionError> { - let keyed_accounts = - Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; - - for (id, process_instruction) in &self.programs { - let root_program_id = keyed_accounts[0].unsigned_key(); - if id == root_program_id { - return process_instruction( - &root_program_id, - &keyed_accounts[1..], - &instruction.data, - ); + if native_loader::check_id(&keyed_accounts[0].owner()?) { + let root_id = keyed_accounts[0].unsigned_key(); + for (id, process_instruction) in &self.programs { + if id == root_id { + // Call the builtin program + return process_instruction(&root_id, &keyed_accounts[1..], instruction_data); + } + } + // Call the program via the native loader + return self.native_loader.process_instruction( + &native_loader::id(), + keyed_accounts, + instruction_data, + invoke_context, + ); + } else { + let owner_id = keyed_accounts[0].owner()?; + for (id, process_instruction) in &self.loaders { + if *id == owner_id { + // Call the program via a builtin loader + return process_instruction( + &owner_id, + keyed_accounts, + instruction_data, + invoke_context, + ); + } } } - - if native_loader::check_id(&keyed_accounts[0].owner()?) { - self.native_loader.process_instruction( - &native_loader::id(), - &keyed_accounts, - &instruction.data, - invoke_context, - ) - } else { - Err(InstructionError::UnsupportedProgramId) - } + Err(InstructionError::UnsupportedProgramId) } /// Process a cross-program instruction - /// This method calls the instruction's program entrypoint method + /// This method calls the instruction's program entrypoint function pub fn process_cross_program_instruction( + &self, message: &Message, executable_accounts: &[(Pubkey, RefCell)], accounts: &[Rc>], signers: &[Pubkey], - process_instruction: ProcessInstructionWithContext, invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { let instruction = &message.instructions[0]; @@ -349,12 +367,8 @@ impl MessageProcessor { // Invoke callee invoke_context.push(instruction.program_id(&message.account_keys))?; - let mut result = process_instruction( - &keyed_accounts[0].owner()?, - &keyed_accounts, - &instruction.data, - invoke_context, - ); + let mut result = + self.process_instruction(&keyed_accounts, &instruction.data, invoke_context); if result.is_ok() { // Verify the called program has not misbehaved result = invoke_context.verify_and_update(message, instruction, signers, accounts); @@ -502,13 +516,9 @@ impl MessageProcessor { rent_collector.rent, pre_accounts, ); - self.process_instruction( - message, - instruction, - &mut invoke_context, - executable_accounts, - accounts, - )?; + let keyed_accounts = + Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; + self.process_instruction(&keyed_accounts, &instruction.data, &mut invoke_context)?; Self::verify( message, instruction, @@ -1368,15 +1378,10 @@ mod tests { program_id: &Pubkey, keyed_accounts: &[KeyedAccount], data: &[u8], - _invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { assert_eq!(*program_id, keyed_accounts[0].owner()?); - assert_eq!( - keyed_accounts[1].owner()?, - *keyed_accounts[0].unsigned_key() - ); assert_ne!( - keyed_accounts[2].owner()?, + keyed_accounts[1].owner()?, *keyed_accounts[0].unsigned_key() ); @@ -1385,10 +1390,10 @@ mod tests { MockInstruction::NoopSuccess => (), MockInstruction::NoopFail => return Err(InstructionError::GenericError), MockInstruction::ModifyOwned => { - keyed_accounts[1].try_account_ref_mut()?.data[0] = 1 + keyed_accounts[0].try_account_ref_mut()?.data[0] = 1 } MockInstruction::ModifyNotOwned => { - keyed_accounts[2].try_account_ref_mut()?.data[0] = 1 + keyed_accounts[1].try_account_ref_mut()?.data[0] = 1 } } } else { @@ -1399,7 +1404,10 @@ mod tests { let caller_program_id = Pubkey::new_rand(); let callee_program_id = Pubkey::new_rand(); - let mut program_account = Account::new(1, 0, &Pubkey::new_rand()); + let mut message_processor = MessageProcessor::default(); + message_processor.add_program(callee_program_id, mock_process_instruction); + + let mut program_account = Account::new(1, 0, &native_loader::id()); program_account.executable = true; let executable_accounts = vec![(callee_program_id, RefCell::new(program_account))]; @@ -1434,12 +1442,11 @@ mod tests { ); let message = Message::new_with_payer(&[instruction], None); assert_eq!( - MessageProcessor::process_cross_program_instruction( + message_processor.process_cross_program_instruction( &message, &executable_accounts, &accounts, &[], - mock_process_instruction, &mut invoke_context, ), Err(InstructionError::ExternalAccountDataModified) @@ -1463,12 +1470,11 @@ mod tests { let instruction = Instruction::new(callee_program_id, &case.0, metas.clone()); let message = Message::new_with_payer(&[instruction], None); assert_eq!( - MessageProcessor::process_cross_program_instruction( + message_processor.process_cross_program_instruction( &message, &executable_accounts, &accounts, &[], - mock_process_instruction, &mut invoke_context, ), case.1