Support cross-program invocation to native programs (#10136)

This commit is contained in:
Jack May 2020-05-20 09:24:57 -07:00 committed by GitHub
parent 9d89fb5c35
commit 4a72c2b054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 70 deletions

View File

@ -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, &params, 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(

View File

@ -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)}};

View File

@ -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");
{
{

View File

@ -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());

View File

@ -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(()) => (),

View File

@ -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<Account>)],
accounts: &[Rc<RefCell<Account>>],
) -> 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<Account>)],
accounts: &[Rc<RefCell<Account>>],
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