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:
parent
176cec6215
commit
77ea8b9b3e
|
@ -85,11 +85,46 @@ fn deserialize_parameters(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) {
|
||||||
pub fn process_instruction(
|
pub fn process_instruction(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
tx_data: &[u8],
|
ix_data: &[u8],
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
|
|
||||||
if keyed_accounts[0].account.executable {
|
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);
|
||||||
|
if keyed_accounts[0].account.data.len() < offset + len {
|
||||||
|
warn!(
|
||||||
|
"Write overflow: {} < {}",
|
||||||
|
keyed_accounts[0].account.data.len(),
|
||||||
|
offset + len
|
||||||
|
);
|
||||||
|
return Err(InstructionError::GenericError);
|
||||||
|
}
|
||||||
|
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 (progs, params) = keyed_accounts.split_at_mut(1);
|
||||||
let prog = &progs[0].account.data;
|
let prog = &progs[0].account.data;
|
||||||
info!("Call BPF program");
|
info!("Call BPF program");
|
||||||
|
@ -100,7 +135,7 @@ pub fn process_instruction(
|
||||||
return Err(InstructionError::GenericError);
|
return Err(InstructionError::GenericError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut v = serialize_parameters(program_id, params, &tx_data);
|
let mut v = serialize_parameters(program_id, params, &data);
|
||||||
|
|
||||||
match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) {
|
match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
|
@ -119,36 +154,10 @@ pub fn process_instruction(
|
||||||
"BPF program executed {} instructions",
|
"BPF program executed {} instructions",
|
||||||
vm.get_last_instruction_count()
|
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);
|
|
||||||
}
|
|
||||||
match instruction {
|
|
||||||
LoaderInstruction::Write { offset, bytes } => {
|
|
||||||
let offset = offset as usize;
|
|
||||||
let len = bytes.len();
|
|
||||||
debug!("Write: offset={} length={}", offset, len);
|
|
||||||
if keyed_accounts[0].account.data.len() < offset + len {
|
|
||||||
warn!(
|
|
||||||
"Write overflow: {} < {}",
|
|
||||||
keyed_accounts[0].account.data.len(),
|
|
||||||
offset + len
|
|
||||||
);
|
|
||||||
return Err(InstructionError::GenericError);
|
|
||||||
}
|
|
||||||
keyed_accounts[0].account.data[offset..offset + len].copy_from_slice(&bytes);
|
|
||||||
}
|
|
||||||
LoaderInstruction::Finalize => {
|
|
||||||
keyed_accounts[0].account.executable = true;
|
|
||||||
info!(
|
|
||||||
"Finalize: account {:?}",
|
|
||||||
keyed_accounts[0].signer_key().unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("Invalid program transaction: {:?}", tx_data);
|
warn!("Invalid instruction data: {:?}", ix_data);
|
||||||
return Err(InstructionError::GenericError);
|
return Err(InstructionError::GenericError);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -6,11 +6,13 @@ use solana_sdk::account::{
|
||||||
};
|
};
|
||||||
use solana_sdk::instruction::{CompiledInstruction, InstructionError};
|
use solana_sdk::instruction::{CompiledInstruction, InstructionError};
|
||||||
use solana_sdk::instruction_processor_utils;
|
use solana_sdk::instruction_processor_utils;
|
||||||
|
use solana_sdk::loader_instruction::LoaderInstruction;
|
||||||
use solana_sdk::message::Message;
|
use solana_sdk::message::Message;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::system_program;
|
use solana_sdk::system_program;
|
||||||
use solana_sdk::transaction::TransactionError;
|
use solana_sdk::transaction::TransactionError;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -92,6 +94,27 @@ fn verify_instruction(
|
||||||
Ok(())
|
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 =
|
pub type ProcessInstruction =
|
||||||
fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), InstructionError>;
|
fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), InstructionError>;
|
||||||
|
|
||||||
|
@ -141,6 +164,13 @@ impl MessageProcessor {
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let program_id = instruction.program_id(&message.account_keys);
|
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_accounts = create_keyed_credit_only_accounts(executable_accounts);
|
||||||
let mut keyed_accounts2: Vec<_> = instruction
|
let mut keyed_accounts2: Vec<_> = instruction
|
||||||
.accounts
|
.accounts
|
||||||
|
@ -168,18 +198,19 @@ impl MessageProcessor {
|
||||||
|
|
||||||
for (id, process_instruction) in &self.instruction_processors {
|
for (id, process_instruction) in &self.instruction_processors {
|
||||||
if id == program_id {
|
if id == program_id {
|
||||||
return process_instruction(
|
return process_instruction(&program_id, &mut keyed_accounts[1..], &ix_data);
|
||||||
&program_id,
|
|
||||||
&mut keyed_accounts[1..],
|
|
||||||
&instruction.data,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
native_loader::entrypoint(
|
assert!(
|
||||||
|
keyed_accounts[0].account.executable,
|
||||||
|
"loader not executable"
|
||||||
|
);
|
||||||
|
|
||||||
|
native_loader::invoke_entrypoint(
|
||||||
&program_id,
|
&program_id,
|
||||||
&mut keyed_accounts,
|
&mut keyed_accounts,
|
||||||
&instruction.data,
|
ix_data,
|
||||||
&self.symbol_cache,
|
&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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,13 +64,12 @@ fn library_open(path: &PathBuf) -> std::io::Result<Library> {
|
||||||
Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
|
Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entrypoint(
|
pub fn invoke_entrypoint(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
ix_data: &[u8],
|
ix_data: &[u8],
|
||||||
symbol_cache: &SymbolCache,
|
symbol_cache: &SymbolCache,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if keyed_accounts[0].account.executable {
|
|
||||||
// dispatch it
|
// dispatch it
|
||||||
let (names, params) = keyed_accounts.split_at_mut(1);
|
let (names, params) = keyed_accounts.split_at_mut(1);
|
||||||
let name_vec = &names[0].account.data;
|
let name_vec = &names[0].account.data;
|
||||||
|
@ -114,8 +113,4 @@ pub fn entrypoint(
|
||||||
Err(InstructionError::GenericError)
|
Err(InstructionError::GenericError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
warn!("Invalid data in instruction: {:?}", ix_data);
|
|
||||||
Err(InstructionError::GenericError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,11 @@ pub enum LoaderInstruction {
|
||||||
///
|
///
|
||||||
/// The transaction must be signed by key[0]
|
/// The transaction must be signed by key[0]
|
||||||
Finalize,
|
Finalize,
|
||||||
|
|
||||||
|
/// Invoke the "main" entrypoint with the given data.
|
||||||
|
///
|
||||||
|
/// * key[0] - an executable account
|
||||||
|
InvokeMain { data: Vec<u8> },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(
|
pub fn write(
|
||||||
|
|
Loading…
Reference in New Issue