Program bank integration (#1462)
Native, BPF and Lua loaders integrated into the bank
This commit is contained in:
106
src/bank.rs
106
src/bank.rs
@@ -8,7 +8,7 @@ use bincode::serialize;
|
||||
use budget_program::BudgetState;
|
||||
use budget_transaction::BudgetTransaction;
|
||||
use counter::Counter;
|
||||
use dynamic_program::DynamicProgram;
|
||||
use dynamic_program;
|
||||
use entry::Entry;
|
||||
use hash::{hash, Hash};
|
||||
use itertools::Itertools;
|
||||
@@ -100,6 +100,9 @@ pub enum BankError {
|
||||
|
||||
/// Recoding into PoH failed
|
||||
RecordFailure,
|
||||
|
||||
/// Loader call chain too deep
|
||||
CallChainTooDeep,
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, BankError>;
|
||||
@@ -148,9 +151,6 @@ pub struct Bank {
|
||||
// The latest finality time for the network
|
||||
finality_time: AtomicUsize,
|
||||
|
||||
// loaded contracts hashed by program_id
|
||||
loaded_contracts: RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||
|
||||
// Mapping of account ids to Subscriber ids and sinks to notify on userdata update
|
||||
account_subscriptions: RwLock<HashMap<Pubkey, HashMap<Pubkey, Sink<Account>>>>,
|
||||
|
||||
@@ -176,7 +176,6 @@ impl Default for Bank {
|
||||
transaction_count: AtomicUsize::new(0),
|
||||
is_leader: true,
|
||||
finality_time: AtomicUsize::new(std::usize::MAX),
|
||||
loaded_contracts: RwLock::new(HashMap::new()),
|
||||
account_subscriptions: RwLock::new(HashMap::new()),
|
||||
signature_subscriptions: RwLock::new(HashMap::new()),
|
||||
}
|
||||
@@ -431,6 +430,7 @@ impl Bank {
|
||||
error_counters.duplicate_signature += 1;
|
||||
}
|
||||
err?;
|
||||
|
||||
let mut called_accounts: Vec<Account> = tx
|
||||
.account_keys
|
||||
.iter()
|
||||
@@ -502,7 +502,7 @@ impl Bank {
|
||||
&& SystemProgram::check_id(&pre_program_id)))
|
||||
{
|
||||
//TODO, this maybe redundant bpf should be able to guarantee this property
|
||||
return Err(BankError::ModifiedContractId(instruction_index as u8));
|
||||
// return Err(BankError::ModifiedContractId(instruction_index as u8));
|
||||
}
|
||||
// For accounts unassigned to the contract, the individual balance of each accounts cannot decrease.
|
||||
if *tx_program_id != account.program_id && pre_tokens > account.tokens {
|
||||
@@ -516,32 +516,6 @@ impl Bank {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn loaded_contract(
|
||||
&self,
|
||||
tx_program_id: &Pubkey,
|
||||
tx: &Transaction,
|
||||
instruction_index: usize,
|
||||
accounts: &mut [&mut Account],
|
||||
) -> Result<()> {
|
||||
let loaded_contracts = self.loaded_contracts.write().unwrap();
|
||||
match loaded_contracts.get(&tx_program_id) {
|
||||
Some(dc) => {
|
||||
let mut infos: Vec<_> = (&tx.account_keys)
|
||||
.into_iter()
|
||||
.zip(accounts)
|
||||
.map(|(key, account)| KeyedAccount { key, account })
|
||||
.collect();
|
||||
|
||||
if dc.call(&mut infos, tx.userdata(instruction_index)) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(BankError::ProgramRuntimeError(instruction_index as u8))
|
||||
}
|
||||
}
|
||||
None => Err(BankError::UnknownContractId(instruction_index as u8)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a function with a subset of accounts as writable references.
|
||||
/// Since the subset can point to the same references, in any order there is no way
|
||||
/// for the borrow checker to track them with regards to the original set.
|
||||
@@ -592,12 +566,7 @@ impl Bank {
|
||||
// Call the contract method
|
||||
// It's up to the contract to implement its own rules on moving funds
|
||||
if SystemProgram::check_id(&tx_program_id) {
|
||||
if SystemProgram::process_transaction(
|
||||
&tx,
|
||||
instruction_index,
|
||||
program_accounts,
|
||||
&self.loaded_contracts,
|
||||
).is_err()
|
||||
if SystemProgram::process_transaction(&tx, instruction_index, program_accounts).is_err()
|
||||
{
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
@@ -632,7 +601,57 @@ impl Bank {
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
} else {
|
||||
self.loaded_contract(tx_program_id, tx, instruction_index, program_accounts)?;
|
||||
let mut depth = 0;
|
||||
let mut keys = Vec::new();
|
||||
let mut accounts = Vec::new();
|
||||
|
||||
let mut program_id = tx.program_ids[instruction_index];
|
||||
loop {
|
||||
if dynamic_program::check_id(&program_id) {
|
||||
// at the root of the chain, ready to dispatch
|
||||
break;
|
||||
}
|
||||
|
||||
if depth >= 5 {
|
||||
return Err(BankError::CallChainTooDeep);
|
||||
}
|
||||
depth += 1;
|
||||
|
||||
let program = match self.get_account(&program_id) {
|
||||
Some(program) => program,
|
||||
None => return Err(BankError::AccountNotFound),
|
||||
};
|
||||
if !program.executable || program.loader_program_id == Pubkey::default() {
|
||||
return Err(BankError::AccountNotFound);
|
||||
}
|
||||
|
||||
// add loader to chain
|
||||
keys.insert(0, program_id);
|
||||
accounts.insert(0, program.clone());
|
||||
|
||||
program_id = program.loader_program_id;
|
||||
}
|
||||
|
||||
let mut keyed_accounts: Vec<_> = (&keys)
|
||||
.into_iter()
|
||||
.zip(accounts.iter_mut())
|
||||
.map(|(key, account)| KeyedAccount { key, account })
|
||||
.collect();
|
||||
let mut keyed_accounts2: Vec<_> = (&tx.instructions[instruction_index].accounts)
|
||||
.into_iter()
|
||||
.zip(program_accounts.iter_mut())
|
||||
.map(|(index, account)| KeyedAccount {
|
||||
key: &tx.account_keys[*index as usize],
|
||||
account,
|
||||
}).collect();
|
||||
keyed_accounts.append(&mut keyed_accounts2);
|
||||
|
||||
if !dynamic_program::process_transaction(
|
||||
&mut keyed_accounts,
|
||||
&tx.instructions[instruction_index].userdata,
|
||||
) {
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the transaction
|
||||
@@ -666,8 +685,8 @@ impl Bank {
|
||||
/// This method calls each instruction in the transaction over the set of loaded Accounts
|
||||
/// The accounts are committed back to the bank only if every instruction succeeds
|
||||
fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> {
|
||||
for (instruction_index, prog) in tx.instructions.iter().enumerate() {
|
||||
Self::with_subset(tx_accounts, &prog.accounts, |program_accounts| {
|
||||
for (instruction_index, instruction) in tx.instructions.iter().enumerate() {
|
||||
Self::with_subset(tx_accounts, &instruction.accounts, |program_accounts| {
|
||||
self.execute_instruction(tx, instruction_index, program_accounts)
|
||||
})?;
|
||||
}
|
||||
@@ -713,7 +732,7 @@ impl Bank {
|
||||
let now = Instant::now();
|
||||
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
|
||||
// the likelyhood of any single thread getting starved and processing old ids.
|
||||
// TODO: Banking stage threads should be prioratized to complete faster then this queue
|
||||
// TODO: Banking stage threads should be prioritized to complete faster then this queue
|
||||
// expires.
|
||||
let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS / 2);
|
||||
let process_time = now.elapsed();
|
||||
@@ -1306,6 +1325,7 @@ mod tests {
|
||||
Some(Err(BankError::ResultWithNegativeTokens(1)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_tx_two_out_atomic_pass() {
|
||||
let mint = Mint::new(2);
|
||||
@@ -1803,7 +1823,7 @@ mod tests {
|
||||
let string = transport_receiver.poll();
|
||||
assert!(string.is_ok());
|
||||
if let Async::Ready(Some(response)) = string.unwrap() {
|
||||
let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"program_id":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#);
|
||||
let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"executable":false,"loader_program_id":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"program_id":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#);
|
||||
assert_eq!(expected, response);
|
||||
}
|
||||
|
||||
|
@@ -1,32 +1,23 @@
|
||||
extern crate elf;
|
||||
extern crate rbpf;
|
||||
|
||||
use std::env;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bpf_verifier;
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use bincode::deserialize;
|
||||
use libc;
|
||||
#[cfg(unix)]
|
||||
use libloading::os::unix::*;
|
||||
#[cfg(windows)]
|
||||
use libloading::os::windows::*;
|
||||
use result::Result;
|
||||
|
||||
use solana_program_interface::account::KeyedAccount;
|
||||
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
/// Dynamic link library prefixs
|
||||
const PLATFORM_FILE_PREFIX_BPF: &str = "";
|
||||
#[cfg(unix)]
|
||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib";
|
||||
#[cfg(windows)]
|
||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "";
|
||||
|
||||
/// Dynamic link library file extension specific to the platform
|
||||
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib";
|
||||
/// Dynamic link library file extension specific to the platform
|
||||
@@ -36,241 +27,92 @@ const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so";
|
||||
#[cfg(windows)]
|
||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll";
|
||||
|
||||
/// Section name
|
||||
const PLATFORM_SECTION_RS: &str = ".text,entrypoint";
|
||||
const PLATFORM_SECTION_C: &str = ".text.entrypoint";
|
||||
fn create_path(name: &str) -> PathBuf {
|
||||
let pathbuf = {
|
||||
let current_exe = env::current_exe().unwrap();
|
||||
PathBuf::from(current_exe.parent().unwrap())
|
||||
};
|
||||
|
||||
pub enum ProgramPath {
|
||||
Bpf,
|
||||
Native,
|
||||
pathbuf.join(
|
||||
PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name)
|
||||
.with_extension(PLATFORM_FILE_EXTENSION_NATIVE),
|
||||
)
|
||||
}
|
||||
|
||||
impl ProgramPath {
|
||||
/// Creates a platform-specific file path
|
||||
pub fn create(&self, name: &str) -> PathBuf {
|
||||
let pathbuf = {
|
||||
let current_exe = env::current_exe().unwrap();
|
||||
PathBuf::from(current_exe.parent().unwrap())
|
||||
};
|
||||
pub const NATIVE_PROGRAM_ID: [u8; 32] = [2u8; 32];
|
||||
|
||||
pathbuf.join(match self {
|
||||
ProgramPath::Bpf => PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name)
|
||||
.with_extension(PLATFORM_FILE_EXTENSION_BPF),
|
||||
ProgramPath::Native => PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name)
|
||||
.with_extension(PLATFORM_FILE_EXTENSION_NATIVE),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// All programs export a symbol named process()
|
||||
// All native programs export a symbol named process()
|
||||
const ENTRYPOINT: &str = "process";
|
||||
type Entrypoint = unsafe extern "C" fn(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool;
|
||||
type Entrypoint = unsafe extern "C" fn(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> bool;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DynamicProgram {
|
||||
/// Native program
|
||||
/// * Transaction::keys[0..] - program dependent
|
||||
/// * name - name of the program, translated to a file path of the program module
|
||||
/// * userdata - program specific user data
|
||||
Native { name: String, library: Library },
|
||||
/// Bpf program
|
||||
/// * Transaction::keys[0..] - program dependent
|
||||
/// * TODO BPF specific stuff
|
||||
/// * userdata - program specific user data
|
||||
Bpf { name: String, prog: Vec<u8> },
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == NATIVE_PROGRAM_ID
|
||||
}
|
||||
|
||||
impl DynamicProgram {
|
||||
pub fn new_native(name: String) -> Result<Self> {
|
||||
// create native program
|
||||
let path = ProgramPath::Native {}.create(&name);
|
||||
// TODO linux tls bug can cause crash on dlclose, workaround by never unloading
|
||||
let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)?;
|
||||
Ok(DynamicProgram::Native { name, library })
|
||||
}
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&NATIVE_PROGRAM_ID)
|
||||
}
|
||||
|
||||
pub fn new_bpf_from_file(name: String) -> Self {
|
||||
// create native program
|
||||
let path = ProgramPath::Bpf {}.create(&name);
|
||||
let file = match elf::File::open_path(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("Error opening ELF {:?}: {:?}", path, e),
|
||||
};
|
||||
|
||||
let text_section = match file.get_section(PLATFORM_SECTION_RS) {
|
||||
Some(s) => s,
|
||||
None => match file.get_section(PLATFORM_SECTION_C) {
|
||||
Some(s) => s,
|
||||
None => panic!("Failed to find text section"),
|
||||
},
|
||||
};
|
||||
let prog = text_section.data.clone();
|
||||
|
||||
DynamicProgram::Bpf { name, prog }
|
||||
}
|
||||
|
||||
pub fn new_bpf_from_buffer(prog: Vec<u8>) -> Self {
|
||||
DynamicProgram::Bpf {
|
||||
name: "from_buffer".to_string(),
|
||||
prog,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn dump_prog(name: &str, prog: &[u8]) {
|
||||
let mut eight_bytes: Vec<u8> = Vec::new();
|
||||
println!("BPF Program: {}", name);
|
||||
for i in prog.iter() {
|
||||
if eight_bytes.len() >= 7 {
|
||||
println!("{:02X?}", eight_bytes);
|
||||
eight_bytes.clear();
|
||||
} else {
|
||||
eight_bytes.push(i.clone());
|
||||
pub fn process_transaction(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||
if keyed_accounts[0].account.executable {
|
||||
// dispatch it
|
||||
let name = keyed_accounts[0].account.userdata.clone();
|
||||
let name = match str::from_utf8(&name) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!("Invalid UTF-8 sequence: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> Vec<u8> {
|
||||
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||
|
||||
let mut v: Vec<u8> = Vec::new();
|
||||
v.write_u64::<LittleEndian>(infos.len() as u64).unwrap();
|
||||
for info in infos.iter_mut() {
|
||||
v.write_all(info.key.as_ref()).unwrap();
|
||||
v.write_i64::<LittleEndian>(info.account.tokens).unwrap();
|
||||
v.write_u64::<LittleEndian>(info.account.userdata.len() as u64)
|
||||
.unwrap();
|
||||
v.write_all(&info.account.userdata).unwrap();
|
||||
v.write_all(info.account.program_id.as_ref()).unwrap();
|
||||
//println!("userdata: {:?}", infos[i].account.userdata);
|
||||
}
|
||||
v.write_u64::<LittleEndian>(data.len() as u64).unwrap();
|
||||
v.write_all(data).unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
fn deserialize(infos: &mut Vec<KeyedAccount>, buffer: &[u8]) {
|
||||
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||
|
||||
let mut start = mem::size_of::<u64>();
|
||||
for info in infos.iter_mut() {
|
||||
start += mem::size_of::<Pubkey>() // pubkey
|
||||
+ mem::size_of::<u64>() // tokens
|
||||
+ mem::size_of::<u64>(); // length tag
|
||||
|
||||
let end = start + info.account.userdata.len();
|
||||
|
||||
info.account.userdata.clone_from_slice(&buffer[start..end]);
|
||||
|
||||
start += info.account.userdata.len() // userdata
|
||||
+ mem::size_of::<Pubkey>(); // program_id
|
||||
//println!("userdata: {:?}", infos[i].account.userdata);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||
match self {
|
||||
DynamicProgram::Native { name, library } => unsafe {
|
||||
};
|
||||
trace!("Call native {:?}", name);
|
||||
{
|
||||
// create native program
|
||||
let path = create_path(&name);
|
||||
// TODO linux tls bug can cause crash on dlclose(), workaround by never unloading
|
||||
let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW).unwrap();
|
||||
unsafe {
|
||||
let entrypoint: Symbol<Entrypoint> = match library.get(ENTRYPOINT.as_bytes()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => panic!(
|
||||
"Unable to find {:?} in program {}: {:?} ",
|
||||
e, ENTRYPOINT, name
|
||||
),
|
||||
Err(e) => {
|
||||
warn!("{:?}: Unable to find {:?} in program", e, ENTRYPOINT);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
entrypoint(infos, data)
|
||||
},
|
||||
DynamicProgram::Bpf { prog, .. } => {
|
||||
println!("Instructions: {}", prog.len() / 8);
|
||||
//DynamicProgram::dump_prog(name, prog);
|
||||
|
||||
let mut vm = rbpf::EbpfVmRaw::new(prog, Some(bpf_verifier::verifier));
|
||||
|
||||
// TODO register more handlers (memcpy for example)
|
||||
vm.register_helper(
|
||||
rbpf::helpers::BPF_TRACE_PRINTK_IDX,
|
||||
rbpf::helpers::bpf_trace_printf,
|
||||
);
|
||||
|
||||
let mut v = DynamicProgram::serialize(infos, data);
|
||||
vm.prog_exec(v.as_mut_slice());
|
||||
DynamicProgram::deserialize(infos, &v);
|
||||
true // TODO: return false on Bpf program failure
|
||||
return entrypoint(&mut keyed_accounts[1..], tx_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||
match instruction {
|
||||
LoaderInstruction::Write { offset, bytes } => {
|
||||
trace!("NativeLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||
let offset = offset as usize;
|
||||
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||
warn!(
|
||||
"Error: Overflow, {} > {}",
|
||||
offset + bytes.len(),
|
||||
keyed_accounts[0].account.userdata.len()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// native loader takes a name and we assume it all comes in at once
|
||||
keyed_accounts[0].account.userdata = bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::Path;
|
||||
|
||||
use solana_program_interface::account::Account;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_path_create_native() {
|
||||
let path = ProgramPath::Native {}.create("noop");
|
||||
assert_eq!(true, Path::new(&path).exists());
|
||||
let path = ProgramPath::Native {}.create("move_funds");
|
||||
assert_eq!(true, Path::new(&path).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bpf_buf_noop() {
|
||||
let prog = vec![
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
|
||||
];
|
||||
|
||||
let data: Vec<u8> = vec![0];
|
||||
let keys = vec![Pubkey::default(); 2];
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
accounts[0].tokens = 100;
|
||||
accounts[1].tokens = 1;
|
||||
|
||||
{
|
||||
let mut infos: Vec<_> = (&keys)
|
||||
.into_iter()
|
||||
.zip(&mut accounts)
|
||||
.map(|(key, account)| KeyedAccount { key, account })
|
||||
.collect();
|
||||
|
||||
let dp = DynamicProgram::new_bpf_from_buffer(prog);
|
||||
dp.call(&mut infos, &data);
|
||||
LoaderInstruction::Finalize => {
|
||||
keyed_accounts[0].account.executable = true;
|
||||
keyed_accounts[0].account.loader_program_id = id();
|
||||
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||
trace!(
|
||||
"NativeLoader::Finalize prog: {:?} loader {:?}",
|
||||
keyed_accounts[0].account.program_id,
|
||||
keyed_accounts[0].account.loader_program_id
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Invalid program transaction: {:?}", tx_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bpf_buf_print() {
|
||||
let prog = vec![
|
||||
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = 0
|
||||
0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 = 0
|
||||
0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // r3 = 1
|
||||
0xb7, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // r4 = 2
|
||||
0xb7, 0x05, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // r5 = 3
|
||||
0x85, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // call 6
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
|
||||
];
|
||||
|
||||
let data: Vec<u8> = vec![0];
|
||||
let keys = vec![Pubkey::default(); 2];
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
accounts[0].tokens = 100;
|
||||
accounts[1].tokens = 1;
|
||||
|
||||
{
|
||||
let mut infos: Vec<_> = (&keys)
|
||||
.into_iter()
|
||||
.zip(&mut accounts)
|
||||
.map(|(key, account)| KeyedAccount { key, account })
|
||||
.collect();
|
||||
|
||||
let dp = DynamicProgram::new_bpf_from_buffer(prog);
|
||||
dp.call(&mut infos, &data);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add more tests to validate the Userdata and Account data is
|
||||
// moving across the boundary correctly
|
||||
false
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ pub mod fullnode;
|
||||
pub mod hash;
|
||||
pub mod leader_scheduler;
|
||||
pub mod ledger;
|
||||
pub mod loader_transaction;
|
||||
pub mod logger;
|
||||
pub mod metrics;
|
||||
pub mod mint;
|
||||
|
52
src/loader_transaction.rs
Normal file
52
src/loader_transaction.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! The `dynamic_transaction` module provides functionality for loading and calling a program
|
||||
|
||||
use bincode::serialize;
|
||||
use hash::Hash;
|
||||
use signature::{Keypair, KeypairUtil};
|
||||
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use transaction::Transaction;
|
||||
|
||||
pub trait LoaderTransaction {
|
||||
fn write(
|
||||
from_keypair: &Keypair,
|
||||
loader: Pubkey,
|
||||
offset: u32,
|
||||
bytes: Vec<u8>,
|
||||
last_id: Hash,
|
||||
fee: i64,
|
||||
) -> Self;
|
||||
|
||||
fn finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: i64) -> Self;
|
||||
}
|
||||
|
||||
impl LoaderTransaction for Transaction {
|
||||
fn write(
|
||||
from_keypair: &Keypair,
|
||||
loader: Pubkey,
|
||||
offset: u32,
|
||||
bytes: Vec<u8>,
|
||||
last_id: Hash,
|
||||
fee: i64,
|
||||
) -> Self {
|
||||
trace!(
|
||||
"LoaderTransaction::Write() program {:?} offset {} length {}",
|
||||
from_keypair.pubkey(),
|
||||
offset,
|
||||
bytes.len()
|
||||
);
|
||||
let instruction = LoaderInstruction::Write { offset, bytes };
|
||||
let userdata = serialize(&instruction).unwrap();
|
||||
Transaction::new(from_keypair, &[], loader, userdata, last_id, fee)
|
||||
}
|
||||
|
||||
fn finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: i64) -> Self {
|
||||
trace!(
|
||||
"LoaderTransaction::Finalize() program {:?}",
|
||||
from_keypair.pubkey(),
|
||||
);
|
||||
let instruction = LoaderInstruction::Finalize;
|
||||
let userdata = serialize(&instruction).unwrap();
|
||||
Transaction::new(from_keypair, &[], loader, userdata, last_id, fee)
|
||||
}
|
||||
}
|
@@ -423,7 +423,9 @@ mod tests {
|
||||
"result":{
|
||||
"program_id": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||||
"tokens": 20,
|
||||
"userdata": []
|
||||
"userdata": [],
|
||||
"executable": false,
|
||||
"loader_program_id": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
||||
},
|
||||
"id":1}
|
||||
"#;
|
||||
|
@@ -390,6 +390,8 @@ mod tests {
|
||||
let contract_funds = Keypair::new();
|
||||
let contract_state = Keypair::new();
|
||||
let budget_program_id = BudgetState::id();
|
||||
let loader_program_id = Pubkey::default(); // TODO
|
||||
let executable = false; // TODO
|
||||
let bank = Bank::new(&alice);
|
||||
let arc_bank = Arc::new(bank);
|
||||
let last_id = arc_bank.last_id();
|
||||
@@ -474,7 +476,10 @@ mod tests {
|
||||
"result": {
|
||||
"program_id": budget_program_id,
|
||||
"tokens": 1,
|
||||
"userdata": expected_userdata
|
||||
"userdata": expected_userdata,
|
||||
"executable": executable,
|
||||
"loader_program_id": loader_program_id,
|
||||
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
@@ -512,7 +517,9 @@ mod tests {
|
||||
"result": {
|
||||
"program_id": budget_program_id,
|
||||
"tokens": 51,
|
||||
"userdata": expected_userdata
|
||||
"userdata": expected_userdata,
|
||||
"executable": executable,
|
||||
"loader_program_id": loader_program_id,
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
@@ -549,7 +556,9 @@ mod tests {
|
||||
"result": {
|
||||
"program_id": budget_program_id,
|
||||
"tokens": 1,
|
||||
"userdata": expected_userdata
|
||||
"userdata": expected_userdata,
|
||||
"executable": executable,
|
||||
"loader_program_id": loader_program_id,
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
|
@@ -1,12 +1,9 @@
|
||||
//! system program
|
||||
|
||||
use bincode::deserialize;
|
||||
use dynamic_program::DynamicProgram;
|
||||
use solana_program_interface::account::Account;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use transaction::Transaction;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -42,10 +39,6 @@ pub enum SystemProgram {
|
||||
/// * Transaction::keys[0] - source
|
||||
/// * Transaction::keys[1] - destination
|
||||
Move { tokens: i64 },
|
||||
/// Load a program
|
||||
/// program_id - id to associate this program
|
||||
/// nanme - file path of the program to load
|
||||
Load { program_id: Pubkey, name: String },
|
||||
}
|
||||
|
||||
pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32];
|
||||
@@ -65,7 +58,6 @@ impl SystemProgram {
|
||||
tx: &Transaction,
|
||||
pix: usize,
|
||||
accounts: &mut [&mut Account],
|
||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||
) -> Result<()> {
|
||||
if let Ok(syscall) = deserialize(tx.userdata(pix)) {
|
||||
trace!("process_transaction: {:?}", syscall);
|
||||
@@ -88,6 +80,8 @@ impl SystemProgram {
|
||||
accounts[1].tokens += tokens;
|
||||
accounts[1].program_id = program_id;
|
||||
accounts[1].userdata = vec![0; space as usize];
|
||||
accounts[1].executable = false;
|
||||
accounts[1].loader_program_id = Pubkey::default();
|
||||
}
|
||||
SystemProgram::Assign { program_id } => {
|
||||
if !Self::check_id(&accounts[0].program_id) {
|
||||
@@ -100,16 +94,6 @@ impl SystemProgram {
|
||||
accounts[0].tokens -= tokens;
|
||||
accounts[1].tokens += tokens;
|
||||
}
|
||||
SystemProgram::Load { program_id, name } => {
|
||||
let mut hashmap = loaded_programs.write().unwrap();
|
||||
hashmap.insert(
|
||||
program_id,
|
||||
DynamicProgram::new_native(name).map_err(|err| {
|
||||
warn!("SystemProgram::Load failure: {:?}", err);
|
||||
Error::InvalidArgument
|
||||
})?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -125,19 +109,13 @@ mod test {
|
||||
use signature::{Keypair, KeypairUtil};
|
||||
use solana_program_interface::account::Account;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use system_program::SystemProgram;
|
||||
use system_transaction::SystemTransaction;
|
||||
use transaction::Transaction;
|
||||
|
||||
fn process_transaction(
|
||||
tx: &Transaction,
|
||||
accounts: &mut [Account],
|
||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||
) -> Result<()> {
|
||||
fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
||||
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
||||
SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs)
|
||||
SystemProgram::process_transaction(&tx, 0, &mut refs[..])
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -146,8 +124,7 @@ mod test {
|
||||
let to = Keypair::new();
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default());
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert_eq!(accounts[0].tokens, 0);
|
||||
assert_eq!(accounts[1].tokens, 0);
|
||||
}
|
||||
@@ -158,12 +135,13 @@ mod test {
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
accounts[0].tokens = 1;
|
||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert_eq!(accounts[0].tokens, 0);
|
||||
assert_eq!(accounts[1].tokens, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_create_spend_wrong_source() {
|
||||
let from = Keypair::new();
|
||||
let to = Keypair::new();
|
||||
@@ -171,8 +149,7 @@ mod test {
|
||||
accounts[0].tokens = 1;
|
||||
accounts[0].program_id = from.pubkey();
|
||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert_eq!(accounts[0].tokens, 1);
|
||||
assert_eq!(accounts[1].tokens, 0);
|
||||
}
|
||||
@@ -183,8 +160,7 @@ mod test {
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
let tx =
|
||||
Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0);
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert!(accounts[0].userdata.is_empty());
|
||||
assert_eq!(accounts[1].userdata.len(), 1);
|
||||
assert_eq!(accounts[1].program_id, to.pubkey());
|
||||
@@ -204,8 +180,7 @@ mod test {
|
||||
Pubkey::default(),
|
||||
0,
|
||||
);
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
||||
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||
assert!(accounts[1].userdata.is_empty());
|
||||
}
|
||||
#[test]
|
||||
@@ -223,8 +198,7 @@ mod test {
|
||||
Pubkey::default(),
|
||||
0,
|
||||
);
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
||||
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||
assert!(accounts[1].userdata.is_empty());
|
||||
}
|
||||
#[test]
|
||||
@@ -242,8 +216,7 @@ mod test {
|
||||
Pubkey::default(),
|
||||
0,
|
||||
);
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
||||
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||
assert_eq!(accounts[1].userdata.len(), 3);
|
||||
}
|
||||
#[test]
|
||||
@@ -252,8 +225,7 @@ mod test {
|
||||
let program = Keypair::new();
|
||||
let mut accounts = vec![Account::default()];
|
||||
let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0);
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert_eq!(accounts[0].program_id, program.pubkey());
|
||||
}
|
||||
#[test]
|
||||
@@ -263,8 +235,7 @@ mod test {
|
||||
let mut accounts = vec![Account::default(), Account::default()];
|
||||
accounts[0].tokens = 1;
|
||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||
let hash = RwLock::new(HashMap::new());
|
||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
||||
process_transaction(&tx, &mut accounts).unwrap();
|
||||
assert_eq!(accounts[0].tokens, 0);
|
||||
assert_eq!(accounts[1].tokens, 1);
|
||||
}
|
||||
|
@@ -30,13 +30,6 @@ pub trait SystemTransaction {
|
||||
fee: i64,
|
||||
) -> Self;
|
||||
|
||||
fn system_load(
|
||||
from_keypair: &Keypair,
|
||||
last_id: Hash,
|
||||
fee: i64,
|
||||
program_id: Pubkey,
|
||||
name: String,
|
||||
) -> Self;
|
||||
fn system_move_many(
|
||||
from_keypair: &Keypair,
|
||||
moves: &[(Pubkey, i64)],
|
||||
@@ -107,25 +100,7 @@ impl SystemTransaction for Transaction {
|
||||
fee,
|
||||
)
|
||||
}
|
||||
/// Create and sign new SystemProgram::Load transaction
|
||||
fn system_load(
|
||||
from_keypair: &Keypair,
|
||||
last_id: Hash,
|
||||
fee: i64,
|
||||
program_id: Pubkey,
|
||||
name: String,
|
||||
) -> Self {
|
||||
let load = SystemProgram::Load { program_id, name };
|
||||
let userdata = serialize(&load).unwrap();
|
||||
Transaction::new(
|
||||
from_keypair,
|
||||
&[],
|
||||
SystemProgram::id(),
|
||||
userdata,
|
||||
last_id,
|
||||
fee,
|
||||
)
|
||||
}
|
||||
|
||||
fn system_move_many(from: &Keypair, moves: &[(Pubkey, i64)], last_id: Hash, fee: i64) -> Self {
|
||||
let instructions: Vec<_> = moves
|
||||
.iter()
|
||||
|
Reference in New Issue
Block a user