Support cross-program invocation to native programs (#10136)
This commit is contained in:
parent
9d89fb5c35
commit
4a72c2b054
|
@ -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(
|
||||
|
|
|
@ -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)}};
|
||||
|
||||
|
|
|
@ -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");
|
||||
{
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(()) => (),
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
if native_loader::check_id(&keyed_accounts[0].owner()?) {
|
||||
let root_id = keyed_accounts[0].unsigned_key();
|
||||
for (id, process_instruction) in &self.programs {
|
||||
let root_program_id = keyed_accounts[0].unsigned_key();
|
||||
if id == root_program_id {
|
||||
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(
|
||||
&root_program_id,
|
||||
&keyed_accounts[1..],
|
||||
&instruction.data,
|
||||
&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
|
||||
|
|
Loading…
Reference in New Issue