Add LoaderInstruction::InvokeMain (#5116)

* Remove unreachable, untested runtime check

* tx_data -> ix_data

* Add LoaderInstruction::InvokeMain

* Add test and allow loaders to be registered statically.

* Fix clippy error
This commit is contained in:
Greg Fitzgerald 2019-07-16 10:45:32 -06:00 committed by GitHub
parent 176cec6215
commit 77ea8b9b3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 90 deletions

View File

@ -85,47 +85,17 @@ fn deserialize_parameters(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) {
pub fn process_instruction(
program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
tx_data: &[u8],
ix_data: &[u8],
) -> Result<(), InstructionError> {
solana_logger::setup();
if keyed_accounts[0].account.executable {
let (progs, params) = keyed_accounts.split_at_mut(1);
let prog = &progs[0].account.data;
info!("Call BPF program");
let (mut vm, heap_region) = match create_vm(prog) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(InstructionError::GenericError);
}
};
let mut v = serialize_parameters(program_id, params, &tx_data);
match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) {
Ok(status) => {
if 0 == status {
warn!("BPF program failed: {}", status);
return Err(InstructionError::GenericError);
}
}
Err(e) => {
warn!("BPF VM failed to run program: {}", e);
return Err(InstructionError::GenericError);
}
}
deserialize_parameters(params, &v);
info!(
"BPF program executed {} instructions",
vm.get_last_instruction_count()
);
} else if let Ok(instruction) = bincode::deserialize(tx_data) {
if keyed_accounts[0].signer_key().is_none() {
warn!("key[0] did not sign the transaction");
return Err(InstructionError::GenericError);
}
if let Ok(instruction) = bincode::deserialize(ix_data) {
match instruction {
LoaderInstruction::Write { offset, bytes } => {
if keyed_accounts[0].signer_key().is_none() {
warn!("key[0] did not sign the transaction");
return Err(InstructionError::GenericError);
}
let offset = offset as usize;
let len = bytes.len();
debug!("Write: offset={} length={}", offset, len);
@ -140,15 +110,54 @@ pub fn process_instruction(
keyed_accounts[0].account.data[offset..offset + len].copy_from_slice(&bytes);
}
LoaderInstruction::Finalize => {
if keyed_accounts[0].signer_key().is_none() {
warn!("key[0] did not sign the transaction");
return Err(InstructionError::GenericError);
}
keyed_accounts[0].account.executable = true;
info!(
"Finalize: account {:?}",
keyed_accounts[0].signer_key().unwrap()
);
}
LoaderInstruction::InvokeMain { data } => {
if !keyed_accounts[0].account.executable {
warn!("BPF account not executable");
return Err(InstructionError::GenericError);
}
let (progs, params) = keyed_accounts.split_at_mut(1);
let prog = &progs[0].account.data;
info!("Call BPF program");
let (mut vm, heap_region) = match create_vm(prog) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(InstructionError::GenericError);
}
};
let mut v = serialize_parameters(program_id, params, &data);
match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) {
Ok(status) => {
if 0 == status {
warn!("BPF program failed: {}", status);
return Err(InstructionError::GenericError);
}
}
Err(e) => {
warn!("BPF VM failed to run program: {}", e);
return Err(InstructionError::GenericError);
}
}
deserialize_parameters(params, &v);
info!(
"BPF program executed {} instructions",
vm.get_last_instruction_count()
);
}
}
} else {
warn!("Invalid program transaction: {:?}", tx_data);
warn!("Invalid instruction data: {:?}", ix_data);
return Err(InstructionError::GenericError);
}
Ok(())

View File

@ -6,11 +6,13 @@ use solana_sdk::account::{
};
use solana_sdk::instruction::{CompiledInstruction, InstructionError};
use solana_sdk::instruction_processor_utils;
use solana_sdk::loader_instruction::LoaderInstruction;
use solana_sdk::message::Message;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_program;
use solana_sdk::transaction::TransactionError;
use std::collections::HashMap;
use std::io::Write;
use std::sync::RwLock;
#[cfg(unix)]
@ -92,6 +94,27 @@ fn verify_instruction(
Ok(())
}
/// Return instruction data to pass to process_instruction().
/// When a loader is detected, the instruction data is wrapped with a LoaderInstruction
/// to signal to the loader that the instruction data should be used as arguments when
/// invoking a "main()" function.
fn get_loader_instruction_data<'a>(
loaders: &[(Pubkey, Account)],
ix_data: &'a [u8],
loader_ix_data: &'a mut Vec<u8>,
) -> &'a [u8] {
if loaders.len() > 1 {
let ix = LoaderInstruction::InvokeMain {
data: ix_data.to_vec(),
};
let ix_data = bincode::serialize(&ix).unwrap();
loader_ix_data.write_all(&ix_data).unwrap();
loader_ix_data
} else {
ix_data
}
}
pub type ProcessInstruction =
fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), InstructionError>;
@ -141,6 +164,13 @@ impl MessageProcessor {
) -> Result<(), InstructionError> {
let program_id = instruction.program_id(&message.account_keys);
let mut loader_ix_data = vec![];
let ix_data = get_loader_instruction_data(
executable_accounts,
&instruction.data,
&mut loader_ix_data,
);
let mut keyed_accounts = create_keyed_credit_only_accounts(executable_accounts);
let mut keyed_accounts2: Vec<_> = instruction
.accounts
@ -168,18 +198,19 @@ impl MessageProcessor {
for (id, process_instruction) in &self.instruction_processors {
if id == program_id {
return process_instruction(
&program_id,
&mut keyed_accounts[1..],
&instruction.data,
);
return process_instruction(&program_id, &mut keyed_accounts[1..], &ix_data);
}
}
native_loader::entrypoint(
assert!(
keyed_accounts[0].account.executable,
"loader not executable"
);
native_loader::invoke_entrypoint(
&program_id,
&mut keyed_accounts,
&instruction.data,
ix_data,
&self.symbol_cache,
)
}
@ -523,4 +554,37 @@ mod tests {
))
);
}
#[test]
fn test_get_loader_instruction_data() {
// First ensure the ix_data is unaffected if not invoking via a loader.
let ix_data = [1];
let mut loader_ix_data = vec![];
let native_pubkey = Pubkey::new_rand();
let native_loader = (native_pubkey, Account::new(0, 0, &native_pubkey));
assert_eq!(
get_loader_instruction_data(&[native_loader.clone()], &ix_data, &mut loader_ix_data),
&ix_data
);
// Now ensure the ix_data is wrapped when there's a loader present.
let acme_pubkey = Pubkey::new_rand();
let acme_loader = (acme_pubkey, Account::new(0, 0, &native_pubkey));
let expected_ix = LoaderInstruction::InvokeMain {
data: ix_data.to_vec(),
};
let expected_ix_data = bincode::serialize(&expected_ix).unwrap();
assert_eq!(
get_loader_instruction_data(
&[native_loader.clone(), acme_loader.clone()],
&ix_data,
&mut loader_ix_data
),
&expected_ix_data[..]
);
// Note there was an allocation in the input vector.
assert_eq!(loader_ix_data, expected_ix_data);
}
}

View File

@ -64,58 +64,53 @@ fn library_open(path: &PathBuf) -> std::io::Result<Library> {
Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
}
pub fn entrypoint(
pub fn invoke_entrypoint(
program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
ix_data: &[u8],
symbol_cache: &SymbolCache,
) -> Result<(), InstructionError> {
if keyed_accounts[0].account.executable {
// dispatch it
let (names, params) = keyed_accounts.split_at_mut(1);
let name_vec = &names[0].account.data;
if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) {
unsafe {
return entrypoint(program_id, params, ix_data);
}
// dispatch it
let (names, params) = keyed_accounts.split_at_mut(1);
let name_vec = &names[0].account.data;
if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) {
unsafe {
return entrypoint(program_id, params, ix_data);
}
let name = match str::from_utf8(name_vec) {
Ok(v) => v,
Err(e) => {
warn!("Invalid UTF-8 sequence: {}", e);
return Err(InstructionError::GenericError);
}
};
trace!("Call native {:?}", name);
let path = create_path(&name);
match library_open(&path) {
Ok(library) => unsafe {
let entrypoint: Symbol<instruction_processor_utils::Entrypoint> =
match library.get(instruction_processor_utils::ENTRYPOINT.as_bytes()) {
Ok(s) => s,
Err(e) => {
warn!(
"{:?}: Unable to find {:?} in program",
e,
instruction_processor_utils::ENTRYPOINT
);
return Err(InstructionError::GenericError);
}
};
let ret = entrypoint(program_id, params, ix_data);
symbol_cache
.write()
.unwrap()
.insert(name_vec.to_vec(), entrypoint);
ret
},
Err(e) => {
warn!("Unable to load: {:?}", e);
Err(InstructionError::GenericError)
}
}
let name = match str::from_utf8(name_vec) {
Ok(v) => v,
Err(e) => {
warn!("Invalid UTF-8 sequence: {}", e);
return Err(InstructionError::GenericError);
}
};
trace!("Call native {:?}", name);
let path = create_path(&name);
match library_open(&path) {
Ok(library) => unsafe {
let entrypoint: Symbol<instruction_processor_utils::Entrypoint> =
match library.get(instruction_processor_utils::ENTRYPOINT.as_bytes()) {
Ok(s) => s,
Err(e) => {
warn!(
"{:?}: Unable to find {:?} in program",
e,
instruction_processor_utils::ENTRYPOINT
);
return Err(InstructionError::GenericError);
}
};
let ret = entrypoint(program_id, params, ix_data);
symbol_cache
.write()
.unwrap()
.insert(name_vec.to_vec(), entrypoint);
ret
},
Err(e) => {
warn!("Unable to load: {:?}", e);
Err(InstructionError::GenericError)
}
} else {
warn!("Invalid data in instruction: {:?}", ix_data);
Err(InstructionError::GenericError)
}
}

View File

@ -18,6 +18,11 @@ pub enum LoaderInstruction {
///
/// The transaction must be signed by key[0]
Finalize,
/// Invoke the "main" entrypoint with the given data.
///
/// * key[0] - an executable account
InvokeMain { data: Vec<u8> },
}
pub fn write(