Direct entrypoint for execution (#7746)

This commit is contained in:
Jack May
2020-01-10 13:20:15 -08:00
committed by GitHub
parent 27d2c0aaf3
commit 08ba27627d
9 changed files with 230 additions and 276 deletions

View File

@ -9,7 +9,7 @@ use solana_rbpf::{memory_region::MemoryRegion, EbpfVm};
use solana_sdk::{
account::KeyedAccount,
instruction::InstructionError,
instruction_processor_utils::{limited_deserialize, next_keyed_account},
instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account},
loader_instruction::LoaderInstruction,
pubkey::Pubkey,
sysvar::rent,
@ -92,12 +92,57 @@ pub fn deserialize_parameters(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]
pub fn process_instruction(
program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
ix_data: &[u8],
instruction_data: &[u8],
) -> Result<(), InstructionError> {
solana_logger::setup_with_default("solana=info");
if let Ok(instruction) = limited_deserialize(ix_data) {
match instruction {
if keyed_accounts.is_empty() {
warn!("No account keys");
return Err(InstructionError::NotEnoughAccountKeys);
}
if is_executable(keyed_accounts) {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let program = next_keyed_account(&mut keyed_accounts_iter)?;
if !program.account.executable {
warn!("BPF program account not executable");
return Err(InstructionError::AccountNotExecutable);
}
let (mut vm, heap_region) = match create_vm(&program.account.data) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(InstructionError::GenericError);
}
};
let parameter_accounts = keyed_accounts_iter.into_slice();
let mut parameter_bytes =
serialize_parameters(program_id, parameter_accounts, &instruction_data);
info!("Call BPF program");
match vm.execute_program(parameter_bytes.as_mut_slice(), &[], &[heap_region]) {
Ok(status) => match u32::try_from(status) {
Ok(status) => {
if status > 0 {
warn!("BPF program failed: {}", status);
return Err(InstructionError::CustomError(status));
}
}
Err(e) => {
warn!("BPF VM encountered invalid status: {}", e);
return Err(InstructionError::GenericError);
}
},
Err(e) => {
warn!("BPF VM failed to run program: {}", e);
return Err(InstructionError::GenericError);
}
}
deserialize_parameters(parameter_accounts, &parameter_bytes);
info!("BPF program success");
} else if !keyed_accounts.is_empty() {
match limited_deserialize(instruction_data)? {
LoaderInstruction::Write { offset, bytes } => {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let program = next_keyed_account(&mut keyed_accounts_iter)?;
@ -138,51 +183,7 @@ pub fn process_instruction(
program.account.executable = true;
info!("Finalize: account {:?}", program.signer_key().unwrap());
}
LoaderInstruction::InvokeMain { data } => {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let program = next_keyed_account(&mut keyed_accounts_iter)?;
if !program.account.executable {
warn!("BPF program account not executable");
return Err(InstructionError::AccountNotExecutable);
}
let (mut vm, heap_region) = match create_vm(&program.account.data) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(InstructionError::GenericError);
}
};
let parameter_accounts = keyed_accounts_iter.into_slice();
let mut parameter_bytes =
serialize_parameters(program_id, parameter_accounts, &data);
info!("Call BPF program");
match vm.execute_program(parameter_bytes.as_mut_slice(), &[], &[heap_region]) {
Ok(status) => match u32::try_from(status) {
Ok(status) => {
if status > 0 {
warn!("BPF program failed: {}", status);
return Err(InstructionError::CustomError(status));
}
}
Err(e) => {
warn!("BPF VM encountered invalid status: {}", e);
return Err(InstructionError::GenericError);
}
},
Err(e) => {
warn!("BPF VM failed to run program: {}", e);
return Err(InstructionError::GenericError);
}
}
deserialize_parameters(parameter_accounts, &parameter_bytes);
info!("BPF program success");
}
}
} else {
warn!("Invalid instruction data: {:?}", ix_data);
return Err(InstructionError::InvalidInstructionData);
}
Ok(())
}
@ -218,7 +219,7 @@ mod tests {
let program_key = Pubkey::new_rand();
let mut program_account = Account::new(1, 0, &program_id);
let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &mut program_account)];
let ix_data = bincode::serialize(&LoaderInstruction::Write {
let instruction_data = bincode::serialize(&LoaderInstruction::Write {
offset: 3,
bytes: vec![1, 2, 3],
})
@ -227,13 +228,13 @@ mod tests {
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&program_id, &mut vec![], &ix_data)
process_instruction(&program_id, &mut vec![], &instruction_data)
);
// Case: Not signed
assert_eq!(
Err(InstructionError::MissingRequiredSignature),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
// Case: Write bytes to an offset
@ -241,7 +242,7 @@ mod tests {
keyed_accounts[0].account.data = vec![0; 6];
assert_eq!(
Ok(()),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
assert_eq!(vec![0, 0, 0, 1, 2, 3], keyed_accounts[0].account.data);
@ -250,7 +251,7 @@ mod tests {
keyed_accounts[0].account.data = vec![0; 5];
assert_eq!(
Err(InstructionError::AccountDataTooSmall),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
}
@ -266,12 +267,12 @@ mod tests {
let mut program_account = Account::new(rent.minimum_balance(elf.len()), 0, &program_id);
program_account.data = elf;
let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &mut program_account)];
let ix_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap();
let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap();
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&program_id, &mut vec![], &ix_data)
process_instruction(&program_id, &mut vec![], &instruction_data)
);
let mut rent_account = rent::create_account(1, &rent);
@ -280,7 +281,7 @@ mod tests {
// Case: Not signed
assert_eq!(
Err(InstructionError::MissingRequiredSignature),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
// Case: Finalize
@ -290,9 +291,10 @@ mod tests {
];
assert_eq!(
Ok(()),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
assert!(keyed_accounts[0].account.executable);
program_account.executable = false; // Un-finalize the account
// Case: Finalize
program_account.data[0] = 0; // bad elf
@ -302,7 +304,7 @@ mod tests {
];
assert_eq!(
Err(InstructionError::InvalidAccountData),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &instruction_data)
);
}
@ -320,25 +322,24 @@ mod tests {
program_account.executable = true;
let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &mut program_account)];
let ix_data = bincode::serialize(&LoaderInstruction::InvokeMain { data: vec![] }).unwrap();
// Case: Empty keyed accounts
assert_eq!(
Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&program_id, &mut vec![], &ix_data)
process_instruction(&program_id, &mut vec![], &vec![])
);
// Case: Only a program account
assert_eq!(
Ok(()),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &vec![])
);
// Case: Account not executable
keyed_accounts[0].account.executable = false;
assert_eq!(
Err(InstructionError::AccountNotExecutable),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
Err(InstructionError::InvalidInstructionData),
process_instruction(&program_id, &mut keyed_accounts, &vec![])
);
keyed_accounts[0].account.executable = true;
@ -351,7 +352,7 @@ mod tests {
));
assert_eq!(
Ok(()),
process_instruction(&program_id, &mut keyed_accounts, &ix_data)
process_instruction(&program_id, &mut keyed_accounts, &vec![])
);
}
}

View File

@ -5,7 +5,7 @@ pub mod librapay_transaction;
extern crate solana_move_loader_program;
use solana_move_loader_program::account_state::LibraAccountState;
use solana_move_loader_program::{account_state::LibraAccountState, processor::MoveProcessor};
use solana_runtime::loader_utils::load_program;
use solana_sdk::{
account::KeyedAccount,
@ -86,5 +86,5 @@ pub fn process_instruction(
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
) -> Result<(), InstructionError> {
solana_move_loader_program::processor::process_instruction(program_id, keyed_accounts, data)
MoveProcessor::process_instruction(program_id, keyed_accounts, data)
}

View File

@ -1,17 +1,17 @@
use bincode;
use solana_move_loader_program::{account_state::pubkey_to_address, processor::InvokeCommand};
use solana_move_loader_program::{
account_state::pubkey_to_address,
processor::{Executable, MoveLoaderInstruction},
};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
loader_instruction::LoaderInstruction,
pubkey::Pubkey,
};
use types::{account_config, transaction::TransactionArgument};
pub fn genesis(genesis_pubkey: &Pubkey, microlibras: u64) -> Instruction {
let data = bincode::serialize(&InvokeCommand::CreateGenesis(microlibras)).unwrap();
let ix_data = LoaderInstruction::InvokeMain { data };
let instruction_data = MoveLoaderInstruction::CreateGenesis(microlibras);
let accounts = vec![AccountMeta::new(*genesis_pubkey, true)];
Instruction::new(solana_sdk::move_loader::id(), &ix_data, accounts)
Instruction::new(solana_sdk::move_loader::id(), &instruction_data, accounts)
}
pub fn mint(
@ -25,13 +25,11 @@ pub fn mint(
TransactionArgument::U64(microlibras),
];
let data = bincode::serialize(&InvokeCommand::RunScript {
let instruction_data = Executable::RunScript {
sender_address: account_config::association_address(),
function_name: "main".to_string(),
args,
})
.unwrap();
let ix_data = LoaderInstruction::InvokeMain { data };
};
let accounts = vec![
AccountMeta::new_readonly(*script_pubkey, false),
@ -39,7 +37,7 @@ pub fn mint(
AccountMeta::new(*to_pubkey, false),
];
Instruction::new(solana_sdk::move_loader::id(), &ix_data, accounts)
Instruction::new(solana_sdk::move_loader::id(), &instruction_data, accounts)
}
pub fn transfer(
@ -54,13 +52,11 @@ pub fn transfer(
TransactionArgument::U64(microlibras),
];
let data = bincode::serialize(&InvokeCommand::RunScript {
let instruction_data = Executable::RunScript {
sender_address: pubkey_to_address(from_pubkey),
function_name: "main".to_string(),
args,
})
.unwrap();
let ix_data = LoaderInstruction::InvokeMain { data };
};
let accounts = vec![
AccountMeta::new_readonly(*script_pubkey, false),
@ -69,7 +65,7 @@ pub fn transfer(
AccountMeta::new(*to_pubkey, false),
];
Instruction::new(solana_sdk::move_loader::id(), &ix_data, accounts)
Instruction::new(solana_sdk::move_loader::id(), &instruction_data, accounts)
}
#[cfg(test)]

View File

@ -151,6 +151,7 @@ pub fn get_libra_balance<T: Client>(
mod tests {
use super::*;
use crate::{create_genesis, upload_mint_script, upload_payment_script};
use solana_move_loader_program::processor::MoveProcessor;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_config::create_genesis_config;
@ -163,7 +164,7 @@ mod tests {
let mut bank = Bank::new(&genesis_config);
bank.add_instruction_processor(
solana_sdk::move_loader::id(),
solana_move_loader_program::processor::process_instruction,
MoveProcessor::process_instruction,
);
let shared_bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&shared_bank);

View File

@ -3,10 +3,10 @@ pub mod data_store;
pub mod error_mappers;
pub mod processor;
use crate::processor::process_instruction;
use crate::processor::MoveProcessor;
solana_sdk::declare_program!(
solana_sdk::move_loader::ID,
solana_move_loader_program,
process_instruction
MoveProcessor::process_instruction
);

View File

@ -7,8 +7,7 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::KeyedAccount,
instruction::InstructionError,
instruction_processor_utils::{limited_deserialize, next_keyed_account},
loader_instruction::LoaderInstruction,
instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account},
move_loader::id,
pubkey::Pubkey,
sysvar::rent,
@ -36,30 +35,42 @@ use vm_runtime::{
};
use vm_runtime_types::value::Value;
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
) -> Result<(), InstructionError> {
solana_logger::setup();
/// Instruction data passed to perform a loader operation, must be based
/// on solana_sdk::loader_instruction::LoaderInstruction
#[derive(Serialize, Deserialize, Debug)]
pub enum MoveLoaderInstruction {
/// Write program data into an account
///
/// * key[0] - the account to write into.
///
/// The transaction must be signed by key[0]
Write {
offset: u32,
#[serde(with = "serde_bytes")]
bytes: Vec<u8>,
},
match limited_deserialize(data)? {
LoaderInstruction::Write { offset, bytes } => {
MoveProcessor::do_write(keyed_accounts, offset, &bytes)
}
LoaderInstruction::Finalize => MoveProcessor::do_finalize(keyed_accounts),
LoaderInstruction::InvokeMain { data } => {
MoveProcessor::do_invoke_main(keyed_accounts, &data)
}
}
/// Finalize an account loaded with program data for execution.
/// The exact preparation steps is loader specific but on success the loader must set the executable
/// bit of the Account
///
/// * key[0] - the account to prepare for execution
/// * key[1] - rent sysvar account
///
/// The transaction must be signed by key[0]
Finalize,
/// Create a new genesis account
///
/// * key[0] - the account to write the genesis into
///
/// The transaction must be signed by key[0]
CreateGenesis(u64),
}
/// Command to invoke
#[derive(Debug, Serialize, Deserialize)]
pub enum InvokeCommand {
/// Create a new genesis account
CreateGenesis(u64),
/// run a Move script
/// Instruction data passed when executing a Move script
#[derive(Serialize, Deserialize, Debug)]
pub enum Executable {
RunScript {
/// Sender of the "transaction", the "sender" who is running this script
sender_address: AccountAddress,
@ -349,72 +360,98 @@ impl MoveProcessor {
Ok(())
}
pub fn do_create_genesis(
keyed_accounts: &mut [KeyedAccount],
amount: u64,
) -> Result<(), InstructionError> {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let script = next_keyed_account(&mut keyed_accounts_iter)?;
if script.account.owner != id() {
debug!("Error: Move script account not owned by Move loader");
return Err(InstructionError::InvalidArgument);
}
match limited_deserialize(&script.account.data)? {
LibraAccountState::Unallocated => Self::serialize_and_enforce_length(
&LibraAccountState::create_genesis(amount)?,
&mut script.account.data,
),
_ => {
debug!("Error: Must provide an unallocated account");
Err(InstructionError::InvalidArgument)
}
}
}
pub fn do_invoke_main(
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
sender_address: AccountAddress,
function_name: String,
args: Vec<TransactionArgument>,
) -> Result<(), InstructionError> {
match limited_deserialize(&data)? {
InvokeCommand::CreateGenesis(amount) => {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let script = next_keyed_account(&mut keyed_accounts_iter)?;
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let script = next_keyed_account(&mut keyed_accounts_iter)?;
if script.account.owner != id() {
debug!("Error: Move script account not owned by Move loader");
return Err(InstructionError::InvalidArgument);
}
trace!(
"Run script {:?} with entrypoint {:?}",
script.unsigned_key(),
function_name
);
match limited_deserialize(&script.account.data)? {
LibraAccountState::Unallocated => Self::serialize_and_enforce_length(
&LibraAccountState::create_genesis(amount)?,
&mut script.account.data,
),
_ => {
debug!("Error: Must provide an unallocated account");
Err(InstructionError::InvalidArgument)
}
}
}
InvokeCommand::RunScript {
sender_address,
function_name,
args,
} => {
let mut keyed_accounts_iter = keyed_accounts.iter_mut();
let script = next_keyed_account(&mut keyed_accounts_iter)?;
if script.account.owner != id() {
debug!("Error: Move script account not owned by Move loader");
return Err(InstructionError::InvalidArgument);
}
if !script.account.executable {
debug!("Error: Move script account not executable");
return Err(InstructionError::AccountNotExecutable);
}
trace!(
"Run script {:?} with entrypoint {:?}",
script.unsigned_key(),
function_name
);
let data_accounts = keyed_accounts_iter.into_slice();
if script.account.owner != id() {
debug!("Error: Move script account not owned by Move loader");
return Err(InstructionError::InvalidArgument);
}
if !script.account.executable {
debug!("Error: Move script account not executable");
return Err(InstructionError::AccountNotExecutable);
}
let mut data_store = Self::keyed_accounts_to_data_store(&data_accounts)?;
let verified_script = Self::deserialize_verified_script(&script.account.data)?;
let data_accounts = keyed_accounts_iter.into_slice();
let output = Self::execute(
sender_address,
&function_name,
args,
verified_script,
&data_store,
)?;
for event in output.events() {
trace!("Event: {:?}", event);
}
let mut data_store = Self::keyed_accounts_to_data_store(&data_accounts)?;
let verified_script = Self::deserialize_verified_script(&script.account.data)?;
data_store.apply_write_set(&output.write_set());
Self::data_store_to_keyed_accounts(data_store, data_accounts)
}
let output = Self::execute(
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
instruction_data: &[u8],
) -> Result<(), InstructionError> {
solana_logger::setup();
if is_executable(keyed_accounts) {
match limited_deserialize(&instruction_data)? {
Executable::RunScript {
sender_address,
&function_name,
function_name,
args,
verified_script,
&data_store,
)?;
for event in output.events() {
trace!("Event: {:?}", event);
} => Self::do_invoke_main(keyed_accounts, sender_address, function_name, args),
}
} else {
match limited_deserialize(instruction_data)? {
MoveLoaderInstruction::Write { offset, bytes } => {
Self::do_write(keyed_accounts, offset, &bytes)
}
MoveLoaderInstruction::Finalize => Self::do_finalize(keyed_accounts),
MoveLoaderInstruction::CreateGenesis(amount) => {
Self::do_create_genesis(keyed_accounts, amount)
}
data_store.apply_write_set(&output.write_set());
Self::data_store_to_keyed_accounts(data_store, data_accounts)
}
}
}
@ -484,11 +521,7 @@ mod tests {
false,
&mut unallocated.account,
)];
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::CreateGenesis(amount)).unwrap(),
)
.unwrap();
MoveProcessor::do_create_genesis(&mut keyed_accounts, amount).unwrap();
assert_eq!(
bincode::deserialize::<LibraAccountState>(
@ -524,12 +557,9 @@ mod tests {
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::RunScript {
sender_address,
function_name: "main".to_string(),
args: vec![],
})
.unwrap(),
sender_address,
"main".to_string(),
vec![],
)
.unwrap();
}
@ -588,12 +618,9 @@ mod tests {
assert_eq!(
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::RunScript {
sender_address,
function_name: "main".to_string(),
args: vec![],
})
.unwrap(),
sender_address,
"main".to_string(),
vec![],
),
Err(InstructionError::CustomError(4002))
);
@ -668,15 +695,12 @@ mod tests {
let amount = 2;
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::RunScript {
sender_address: sender.address.clone(),
function_name: "main".to_string(),
args: vec![
TransactionArgument::Address(payee.address.clone()),
TransactionArgument::U64(amount),
],
})
.unwrap(),
sender.address.clone(),
"main".to_string(),
vec![
TransactionArgument::Address(payee.address.clone()),
TransactionArgument::U64(amount),
],
)
.unwrap();
@ -768,12 +792,9 @@ mod tests {
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::RunScript {
sender_address: sender.address.clone(),
function_name: "main".to_string(),
args: vec![TransactionArgument::Address(payee.address.clone())],
})
.unwrap(),
sender.address.clone(),
"main".to_string(),
vec![TransactionArgument::Address(payee.address.clone())],
)
.unwrap();
@ -819,15 +840,12 @@ mod tests {
MoveProcessor::do_invoke_main(
&mut keyed_accounts,
&bincode::serialize(&InvokeCommand::RunScript {
sender_address: account_config::association_address(),
function_name: "main".to_string(),
args: vec![
TransactionArgument::Address(pubkey_to_address(&payee.key)),
TransactionArgument::U64(amount),
],
})
.unwrap(),
account_config::association_address(),
"main".to_string(),
vec![
TransactionArgument::Address(pubkey_to_address(&payee.key)),
TransactionArgument::U64(amount),
],
)
.unwrap();