Initial integration of dynamic contracts and native module loading (#1256)
* Integration of native dynamic programs
This commit is contained in:
parent
a1f01fb8f8
commit
26b1466ef6
23
Cargo.toml
23
Cargo.toml
|
@ -89,6 +89,8 @@ jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc", rev = "4b
|
||||||
jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc", rev = "4b6060b" }
|
jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc", rev = "4b6060b" }
|
||||||
ipnetwork = "0.12.7"
|
ipnetwork = "0.12.7"
|
||||||
itertools = "0.7.8"
|
itertools = "0.7.8"
|
||||||
|
libc = "0.2.43"
|
||||||
|
libloading = "0.5.0"
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
matches = "0.1.6"
|
matches = "0.1.6"
|
||||||
nix = "0.11.0"
|
nix = "0.11.0"
|
||||||
|
@ -106,7 +108,11 @@ sys-info = "0.5.6"
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
tokio-codec = "0.1"
|
tokio-codec = "0.1"
|
||||||
untrusted = "0.6.2"
|
untrusted = "0.6.2"
|
||||||
libc = "0.2.43"
|
|
||||||
|
[dev-dependencies]
|
||||||
|
noop = { path = "programs/noop" }
|
||||||
|
print = { path = "programs/print" }
|
||||||
|
move_funds = { path = "programs/move_funds" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bank"
|
name = "bank"
|
||||||
|
@ -122,3 +128,18 @@ name = "signature"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "sigverify"
|
name = "sigverify"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
".",
|
||||||
|
"programs/noop",
|
||||||
|
"programs/print",
|
||||||
|
"programs/move_funds",
|
||||||
|
]
|
||||||
|
default-members = [
|
||||||
|
".",
|
||||||
|
"programs/noop",
|
||||||
|
"programs/print",
|
||||||
|
"programs/move_funds",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "move_funds"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"Anatoly Yakovenko <anatoly@solana.com>",
|
||||||
|
"Greg Fitzgerald <greg@solana.com>",
|
||||||
|
"Stephen Akridge <stephen@solana.com>",
|
||||||
|
"Michael Vines <mvines@solana.com>",
|
||||||
|
"Rob Walker <rob@solana.com>",
|
||||||
|
"Pankaj Garg <pankaj@solana.com>",
|
||||||
|
"Tyera Eulberg <tyera@solana.com>",
|
||||||
|
"Jack May <jack@solana.com>",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = "1.0.0"
|
||||||
|
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||||
|
libloading = "0.5.0"
|
||||||
|
solana = { path = "../.." }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "move_funds"
|
||||||
|
crate-type = ["dylib"]
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
extern crate bincode;
|
||||||
|
extern crate solana;
|
||||||
|
|
||||||
|
use bincode::deserialize;
|
||||||
|
use solana::dynamic_program::KeyedAccount;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, data: &[u8]) {
|
||||||
|
let tokens: i64 = deserialize(data).unwrap();
|
||||||
|
if infos[0].account.tokens >= tokens {
|
||||||
|
infos[0].account.tokens -= tokens;
|
||||||
|
infos[1].account.tokens += tokens;
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Insufficient funds, asked {}, only had {}",
|
||||||
|
tokens, infos[0].account.tokens
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bincode::serialize;
|
||||||
|
use solana::bank::Account;
|
||||||
|
use solana::signature::Pubkey;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_funds() {
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
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<KeyedAccount> = Vec::new();
|
||||||
|
for (key, account) in keys.iter().zip(&mut accounts).collect::<Vec<_>>() {
|
||||||
|
infos.push(KeyedAccount { key, account });
|
||||||
|
}
|
||||||
|
|
||||||
|
process(&mut infos, &data);
|
||||||
|
}
|
||||||
|
assert_eq!(0, accounts[0].tokens);
|
||||||
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "noop"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"Anatoly Yakovenko <anatoly@solana.com>",
|
||||||
|
"Greg Fitzgerald <greg@solana.com>",
|
||||||
|
"Stephen Akridge <stephen@solana.com>",
|
||||||
|
"Michael Vines <mvines@solana.com>",
|
||||||
|
"Rob Walker <rob@solana.com>",
|
||||||
|
"Pankaj Garg <pankaj@solana.com>",
|
||||||
|
"Tyera Eulberg <tyera@solana.com>",
|
||||||
|
"Jack May <jack@solana.com>",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libloading = "0.5.0"
|
||||||
|
solana = { path = "../.." }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "noop"
|
||||||
|
crate-type = ["dylib"]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
extern crate solana;
|
||||||
|
|
||||||
|
use solana::dynamic_program::KeyedAccount;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {}
|
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "print"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"Anatoly Yakovenko <anatoly@solana.com>",
|
||||||
|
"Greg Fitzgerald <greg@solana.com>",
|
||||||
|
"Stephen Akridge <stephen@solana.com>",
|
||||||
|
"Michael Vines <mvines@solana.com>",
|
||||||
|
"Rob Walker <rob@solana.com>",
|
||||||
|
"Pankaj Garg <pankaj@solana.com>",
|
||||||
|
"Tyera Eulberg <tyera@solana.com>",
|
||||||
|
"Jack May <jack@solana.com>",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libloading = "0.5.0"
|
||||||
|
solana = { path = "../.." }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "print"
|
||||||
|
crate-type = ["dylib"]
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
extern crate solana;
|
||||||
|
|
||||||
|
use solana::dynamic_program::KeyedAccount;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
|
||||||
|
println!("AccountInfos: {:#?}", infos);
|
||||||
|
//println!("data: {:#?}", data);
|
||||||
|
}
|
36
src/bank.rs
36
src/bank.rs
|
@ -7,6 +7,7 @@ use bincode::deserialize;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use budget_program::BudgetState;
|
use budget_program::BudgetState;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
|
use dynamic_program::{DynamicProgram, KeyedAccount};
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::{hash, Hash};
|
use hash::{hash, Hash};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -136,6 +137,9 @@ pub struct Bank {
|
||||||
|
|
||||||
// The latest finality time for the network
|
// The latest finality time for the network
|
||||||
finality_time: AtomicUsize,
|
finality_time: AtomicUsize,
|
||||||
|
|
||||||
|
// loaded contracts hashed by program_id
|
||||||
|
loaded_contracts: RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Bank {
|
impl Default for Bank {
|
||||||
|
@ -147,6 +151,7 @@ impl Default for Bank {
|
||||||
transaction_count: AtomicUsize::new(0),
|
transaction_count: AtomicUsize::new(0),
|
||||||
is_leader: true,
|
is_leader: true,
|
||||||
finality_time: AtomicUsize::new(std::usize::MAX),
|
finality_time: AtomicUsize::new(std::usize::MAX),
|
||||||
|
loaded_contracts: RwLock::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,6 +312,7 @@ impl Bank {
|
||||||
Ok(called_accounts)
|
Ok(called_accounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_accounts(
|
fn load_accounts(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
|
@ -317,6 +323,7 @@ impl Bank {
|
||||||
.map(|tx| self.load_account(tx, accounts, error_counters))
|
.map(|tx| self.load_account(tx, accounts, error_counters))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_transaction(
|
pub fn verify_transaction(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
pre_program_id: &Pubkey,
|
pre_program_id: &Pubkey,
|
||||||
|
@ -341,11 +348,33 @@ impl Bank {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn loaded_contract(&self, tx: &Transaction, accounts: &mut [Account]) -> bool {
|
||||||
|
let loaded_contracts = self.loaded_contracts.write().unwrap();
|
||||||
|
match loaded_contracts.get(&tx.program_id) {
|
||||||
|
Some(dc) => {
|
||||||
|
let mut infos: Vec<_> = (&tx.keys)
|
||||||
|
.into_iter()
|
||||||
|
.zip(accounts)
|
||||||
|
.map(|(key, account)| KeyedAccount { key, account })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
dc.call(&mut infos, &tx.userdata);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a transaction.
|
/// Execute a transaction.
|
||||||
/// This method calls the contract's process_transaction method and verifies that the result of
|
/// This method calls the contract's process_transaction method and verifies that the result of
|
||||||
/// the contract does not violate the bank's accounting rules.
|
/// the contract does not violate the bank's accounting rules.
|
||||||
/// The accounts are committed back to the bank only if this function returns Ok(_).
|
/// The accounts are committed back to the bank only if this function returns Ok(_).
|
||||||
fn execute_transaction(tx: Transaction, accounts: &mut [Account]) -> Result<Transaction> {
|
fn execute_transaction(
|
||||||
|
&self,
|
||||||
|
tx: Transaction,
|
||||||
|
accounts: &mut [Account],
|
||||||
|
) -> Result<Transaction> {
|
||||||
let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum();
|
let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum();
|
||||||
let pre_data: Vec<_> = accounts
|
let pre_data: Vec<_> = accounts
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -355,13 +384,14 @@ impl Bank {
|
||||||
// Call the contract method
|
// Call the contract method
|
||||||
// It's up to the contract to implement its own rules on moving funds
|
// It's up to the contract to implement its own rules on moving funds
|
||||||
if SystemProgram::check_id(&tx.program_id) {
|
if SystemProgram::check_id(&tx.program_id) {
|
||||||
SystemProgram::process_transaction(&tx, accounts)
|
SystemProgram::process_transaction(&tx, accounts, &self.loaded_contracts)
|
||||||
} else if BudgetState::check_id(&tx.program_id) {
|
} else if BudgetState::check_id(&tx.program_id) {
|
||||||
// TODO: the runtime should be checking read/write access to memory
|
// TODO: the runtime should be checking read/write access to memory
|
||||||
// we are trusting the hard coded contracts not to clobber or allocate
|
// we are trusting the hard coded contracts not to clobber or allocate
|
||||||
BudgetState::process_transaction(&tx, accounts)
|
BudgetState::process_transaction(&tx, accounts)
|
||||||
} else if StorageProgram::check_id(&tx.program_id) {
|
} else if StorageProgram::check_id(&tx.program_id) {
|
||||||
StorageProgram::process_transaction(&tx, accounts)
|
StorageProgram::process_transaction(&tx, accounts)
|
||||||
|
} else if self.loaded_contract(&tx, accounts) {
|
||||||
} else {
|
} else {
|
||||||
return Err(BankError::UnknownContractId(tx.program_id));
|
return Err(BankError::UnknownContractId(tx.program_id));
|
||||||
}
|
}
|
||||||
|
@ -418,7 +448,7 @@ impl Bank {
|
||||||
.zip(txs.into_iter())
|
.zip(txs.into_iter())
|
||||||
.map(|(acc, tx)| match acc {
|
.map(|(acc, tx)| match acc {
|
||||||
Err(e) => Err(e.clone()),
|
Err(e) => Err(e.clone()),
|
||||||
Ok(ref mut accounts) => Self::execute_transaction(tx, accounts),
|
Ok(ref mut accounts) => self.execute_transaction(tx, accounts),
|
||||||
}).collect();
|
}).collect();
|
||||||
let execution_elapsed = now.elapsed();
|
let execution_elapsed = now.elapsed();
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
extern crate bincode;
|
||||||
|
extern crate generic_array;
|
||||||
|
|
||||||
|
use bank::Account;
|
||||||
|
use libc;
|
||||||
|
use libloading;
|
||||||
|
use signature::Pubkey;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const CARGO_PROFILE: &str = "debug";
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const CARGO_PROFILE: &str = "release";
|
||||||
|
|
||||||
|
/// Dynamic link library prefix
|
||||||
|
#[cfg(unix)]
|
||||||
|
const PLATFORM_FILE_PREFIX: &str = "lib";
|
||||||
|
/// Dynamic link library prefix
|
||||||
|
#[cfg(windows)]
|
||||||
|
const PLATFORM_FILE_PREFIX: &str = "";
|
||||||
|
/// Dynamic link library file extension specific to the platform
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
const PLATFORM_FILE_EXTENSION: &str = "dylib";
|
||||||
|
/// Dynamic link library file extension specific to the platform
|
||||||
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
|
||||||
|
const PLATFORM_FILE_EXTENSION: &str = "so";
|
||||||
|
/// Dynamic link library file extension specific to the platform
|
||||||
|
#[cfg(windows)]
|
||||||
|
const PLATFORM_FILE_EXTENSION: &str = "dll";
|
||||||
|
|
||||||
|
/// Creates a platform-specific file path
|
||||||
|
fn create_library_path(name: &str) -> PathBuf {
|
||||||
|
let mut path = PathBuf::new();
|
||||||
|
path.push("target");
|
||||||
|
path.push(CARGO_PROFILE);
|
||||||
|
path.push("deps");
|
||||||
|
path.push(PLATFORM_FILE_PREFIX.to_string() + name);
|
||||||
|
path.set_extension(PLATFORM_FILE_EXTENSION);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KeyedAccount<'a> {
|
||||||
|
pub key: &'a Pubkey,
|
||||||
|
pub account: &'a mut Account,
|
||||||
|
}
|
||||||
|
|
||||||
|
// All programs export a symbol named process()
|
||||||
|
const ENTRYPOINT: &str = "process";
|
||||||
|
type Entrypoint = unsafe extern "C" fn(infos: &mut Vec<KeyedAccount>, data: &[u8]);
|
||||||
|
|
||||||
|
#[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: libloading::Library,
|
||||||
|
},
|
||||||
|
/// Bpf program
|
||||||
|
/// * Transaction::keys[0..] - program dependent
|
||||||
|
/// * TODO BPF specific stuff
|
||||||
|
/// * userdata - program specific user data
|
||||||
|
Bpf { userdata: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicProgram {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
// TODO determine what kind of module to load
|
||||||
|
|
||||||
|
// create native program
|
||||||
|
let path = create_library_path(&name);
|
||||||
|
// TODO linux tls bug can cause crash on dlclose, workaround by never unloading
|
||||||
|
let os_lib =
|
||||||
|
libloading::os::unix::Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
|
||||||
|
.unwrap();
|
||||||
|
let library = libloading::Library::from(os_lib);
|
||||||
|
DynamicProgram::Native { name, library }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, infos: &mut Vec<KeyedAccount>, data: &[u8]) {
|
||||||
|
match self {
|
||||||
|
DynamicProgram::Native { name, library } => unsafe {
|
||||||
|
let entrypoint: libloading::Symbol<Entrypoint> =
|
||||||
|
match library.get(ENTRYPOINT.as_bytes()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => panic!(
|
||||||
|
"{:?} Unable to find {:?} in program {}",
|
||||||
|
e, ENTRYPOINT, name
|
||||||
|
),
|
||||||
|
};
|
||||||
|
entrypoint(infos, data);
|
||||||
|
},
|
||||||
|
DynamicProgram::Bpf { .. } => {
|
||||||
|
// TODO BPF
|
||||||
|
println!{"Bpf program not supported"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bank::Account;
|
||||||
|
use bincode::serialize;
|
||||||
|
use signature::Pubkey;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_library_path() {
|
||||||
|
let path = create_library_path("noop");
|
||||||
|
assert_eq!(true, Path::new(&path).exists());
|
||||||
|
let path = create_library_path("print");
|
||||||
|
assert_eq!(true, Path::new(&path).exists());
|
||||||
|
let path = create_library_path("move_funds");
|
||||||
|
assert_eq!(true, Path::new(&path).exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_noop() {
|
||||||
|
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("noop".to_string());
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_program_print() {
|
||||||
|
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("print".to_string());
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_move_funds_success() {
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
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("move_funds".to_string());
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
assert_eq!(0, accounts[0].tokens);
|
||||||
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_move_funds_insufficient_funds() {
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
let keys = vec![Pubkey::default(); 2];
|
||||||
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
|
accounts[0].tokens = 10;
|
||||||
|
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("move_funds".to_string());
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
assert_eq!(10, accounts[0].tokens);
|
||||||
|
assert_eq!(1, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_move_funds_succes_many_threads() {
|
||||||
|
let num_threads = 42; // number of threads to spawn
|
||||||
|
let num_iters = 100; // number of iterations of test in each thread
|
||||||
|
let mut threads = Vec::new();
|
||||||
|
for _t in 0..num_threads {
|
||||||
|
threads.push(thread::spawn(move || {
|
||||||
|
for _i in 0..num_iters {
|
||||||
|
{
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
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("move_funds".to_string());
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
assert_eq!(0, accounts[0].tokens);
|
||||||
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for thread in threads {
|
||||||
|
thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add more tests to validate the Userdata and Account data is
|
||||||
|
// moving across the boundary correctly
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ pub mod instruction;
|
||||||
pub mod crdt;
|
pub mod crdt;
|
||||||
pub mod budget_program;
|
pub mod budget_program;
|
||||||
pub mod drone;
|
pub mod drone;
|
||||||
|
pub mod dynamic_program;
|
||||||
pub mod entry;
|
pub mod entry;
|
||||||
pub mod entry_writer;
|
pub mod entry_writer;
|
||||||
#[cfg(feature = "erasure")]
|
#[cfg(feature = "erasure")]
|
||||||
|
@ -82,6 +83,7 @@ extern crate jsonrpc_core;
|
||||||
extern crate jsonrpc_macros;
|
extern crate jsonrpc_macros;
|
||||||
extern crate jsonrpc_http_server;
|
extern crate jsonrpc_http_server;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
extern crate libloading;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate nix;
|
extern crate nix;
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
use bank::Account;
|
use bank::Account;
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
|
use dynamic_program::DynamicProgram;
|
||||||
use signature::Pubkey;
|
use signature::Pubkey;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::RwLock;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -25,6 +28,10 @@ pub enum SystemProgram {
|
||||||
/// * Transaction::keys[0] - source
|
/// * Transaction::keys[0] - source
|
||||||
/// * Transaction::keys[1] - destination
|
/// * Transaction::keys[1] - destination
|
||||||
Move { tokens: i64 },
|
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];
|
pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32];
|
||||||
|
@ -40,7 +47,11 @@ impl SystemProgram {
|
||||||
pub fn get_balance(account: &Account) -> i64 {
|
pub fn get_balance(account: &Account) -> i64 {
|
||||||
account.tokens
|
account.tokens
|
||||||
}
|
}
|
||||||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) {
|
pub fn process_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
accounts: &mut [Account],
|
||||||
|
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
||||||
|
) {
|
||||||
if let Ok(syscall) = deserialize(&tx.userdata) {
|
if let Ok(syscall) = deserialize(&tx.userdata) {
|
||||||
trace!("process_transaction: {:?}", syscall);
|
trace!("process_transaction: {:?}", syscall);
|
||||||
match syscall {
|
match syscall {
|
||||||
|
@ -74,6 +85,10 @@ impl SystemProgram {
|
||||||
accounts[0].tokens -= tokens;
|
accounts[0].tokens -= tokens;
|
||||||
accounts[1].tokens += tokens;
|
accounts[1].tokens += tokens;
|
||||||
}
|
}
|
||||||
|
SystemProgram::Load { program_id, name } => {
|
||||||
|
let mut hashmap = loaded_programs.write().unwrap();
|
||||||
|
hashmap.insert(program_id, DynamicProgram::new(name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Invalid transaction userdata: {:?}", tx.userdata);
|
info!("Invalid transaction userdata: {:?}", tx.userdata);
|
||||||
|
@ -83,17 +98,24 @@ impl SystemProgram {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use bank::Account;
|
use bank::Account;
|
||||||
|
use bincode::serialize;
|
||||||
|
use dynamic_program::KeyedAccount;
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use signature::{Keypair, KeypairUtil, Pubkey};
|
use signature::{Keypair, KeypairUtil, Pubkey};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
use std::thread;
|
||||||
use system_program::SystemProgram;
|
use system_program::SystemProgram;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_noop() {
|
fn test_create_noop() {
|
||||||
let from = Keypair::new();
|
let from = Keypair::new();
|
||||||
let to = Keypair::new();
|
let to = Keypair::new();
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -104,7 +126,8 @@ mod test {
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
accounts[0].tokens = 1;
|
accounts[0].tokens = 1;
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +139,8 @@ mod test {
|
||||||
accounts[0].tokens = 1;
|
accounts[0].tokens = 1;
|
||||||
accounts[0].program_id = from.pubkey();
|
accounts[0].program_id = from.pubkey();
|
||||||
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 1);
|
assert_eq!(accounts[0].tokens, 1);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +151,8 @@ mod test {
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
let tx =
|
let tx =
|
||||||
Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0);
|
Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0);
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[0].userdata.is_empty());
|
assert!(accounts[0].userdata.is_empty());
|
||||||
assert_eq!(accounts[1].userdata.len(), 1);
|
assert_eq!(accounts[1].userdata.len(), 1);
|
||||||
assert_eq!(accounts[1].program_id, to.pubkey());
|
assert_eq!(accounts[1].program_id, to.pubkey());
|
||||||
|
@ -147,7 +172,8 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -165,7 +191,8 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -183,16 +210,124 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[1].userdata.len(), 3);
|
assert_eq!(accounts[1].userdata.len(), 3);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
fn test_load_call() {
|
||||||
|
// first load the program
|
||||||
|
let loaded_programs = RwLock::new(HashMap::new());
|
||||||
|
{
|
||||||
|
let from = Keypair::new();
|
||||||
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
|
let program_id = Pubkey::default(); // same program id for both
|
||||||
|
let tx = Transaction::system_load(
|
||||||
|
&from,
|
||||||
|
Hash::default(),
|
||||||
|
0,
|
||||||
|
program_id,
|
||||||
|
"move_funds".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &loaded_programs);
|
||||||
|
}
|
||||||
|
// then call the program
|
||||||
|
{
|
||||||
|
let program_id = Pubkey::default(); // same program id for both
|
||||||
|
let keys = vec![Pubkey::default(), Pubkey::default()];
|
||||||
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
|
accounts[0].tokens = 100;
|
||||||
|
accounts[1].tokens = 1;
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
{
|
||||||
|
let hash = loaded_programs.write().unwrap();
|
||||||
|
match hash.get(&program_id) {
|
||||||
|
Some(dp) => {
|
||||||
|
let mut infos: Vec<_> = (&keys)
|
||||||
|
.into_iter()
|
||||||
|
.zip(&mut accounts)
|
||||||
|
.map(|(key, account)| KeyedAccount { key, account })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
None => panic!("failed to find program in hash"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(0, accounts[0].tokens);
|
||||||
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_load_call_many_threads() {
|
||||||
|
let num_threads = 42;
|
||||||
|
let num_iters = 100;
|
||||||
|
let mut threads = Vec::new();
|
||||||
|
for _t in 0..num_threads {
|
||||||
|
threads.push(thread::spawn(move || {
|
||||||
|
let _tid = thread::current().id();
|
||||||
|
for _i in 0..num_iters {
|
||||||
|
// first load the program
|
||||||
|
let loaded_programs = RwLock::new(HashMap::new());
|
||||||
|
{
|
||||||
|
let from = Keypair::new();
|
||||||
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
|
let program_id = Pubkey::default(); // same program id for both
|
||||||
|
let tx = Transaction::system_load(
|
||||||
|
&from,
|
||||||
|
Hash::default(),
|
||||||
|
0,
|
||||||
|
program_id,
|
||||||
|
"move_funds".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &loaded_programs);
|
||||||
|
}
|
||||||
|
// then call the program
|
||||||
|
{
|
||||||
|
let program_id = Pubkey::default(); // same program id for both
|
||||||
|
let keys = vec![Pubkey::default(), Pubkey::default()];
|
||||||
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
|
accounts[0].tokens = 100;
|
||||||
|
accounts[1].tokens = 1;
|
||||||
|
let tokens: i64 = 100;
|
||||||
|
let data: Vec<u8> = serialize(&tokens).unwrap();
|
||||||
|
{
|
||||||
|
let hash = loaded_programs.write().unwrap();
|
||||||
|
match hash.get(&program_id) {
|
||||||
|
Some(dp) => {
|
||||||
|
let mut infos: Vec<_> = (&keys)
|
||||||
|
.into_iter()
|
||||||
|
.zip(&mut accounts)
|
||||||
|
.map(|(key, account)| KeyedAccount { key, account })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
dp.call(&mut infos, &data);
|
||||||
|
}
|
||||||
|
None => panic!("failed to find program in hash"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(0, accounts[0].tokens);
|
||||||
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for thread in threads {
|
||||||
|
thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
fn test_create_assign() {
|
fn test_create_assign() {
|
||||||
let from = Keypair::new();
|
let from = Keypair::new();
|
||||||
let program = Keypair::new();
|
let program = Keypair::new();
|
||||||
let mut accounts = vec![Account::default()];
|
let mut accounts = vec![Account::default()];
|
||||||
let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0);
|
let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0);
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].program_id, program.pubkey());
|
assert_eq!(accounts[0].program_id, program.pubkey());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -202,11 +337,11 @@ mod test {
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
let mut accounts = vec![Account::default(), Account::default()];
|
||||||
accounts[0].tokens = 1;
|
accounts[0].tokens = 1;
|
||||||
let tx = Transaction::new(&from, to.pubkey(), 1, Hash::default());
|
let tx = Transaction::new(&from, to.pubkey(), 1, Hash::default());
|
||||||
SystemProgram::process_transaction(&tx, &mut accounts);
|
let hash = RwLock::new(HashMap::new());
|
||||||
|
SystemProgram::process_transaction(&tx, &mut accounts, &hash);
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect binary changes in the serialized program userdata, which could have a downstream
|
/// Detect binary changes in the serialized program userdata, which could have a downstream
|
||||||
/// affect on SDKs and DApps
|
/// affect on SDKs and DApps
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -234,6 +234,24 @@ impl Transaction {
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/// Create and sign new SystemProgram::Load transaction
|
||||||
|
pub fn system_load(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
last_id: Hash,
|
||||||
|
fee: i64,
|
||||||
|
program_id: Pubkey,
|
||||||
|
name: String,
|
||||||
|
) -> Self {
|
||||||
|
let load = SystemProgram::Load { program_id, name };
|
||||||
|
Transaction::new_with_userdata(
|
||||||
|
from_keypair,
|
||||||
|
&[],
|
||||||
|
SystemProgram::id(),
|
||||||
|
serialize(&load).unwrap(),
|
||||||
|
last_id,
|
||||||
|
fee,
|
||||||
|
)
|
||||||
|
}
|
||||||
/// Create and sign new SystemProgram::Move transaction
|
/// Create and sign new SystemProgram::Move transaction
|
||||||
pub fn new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self {
|
pub fn new(from_keypair: &Keypair, to: Pubkey, tokens: i64, last_id: Hash) -> Self {
|
||||||
Transaction::system_move(from_keypair, to, tokens, last_id, 0)
|
Transaction::system_move(from_keypair, to, tokens, last_id, 0)
|
||||||
|
|
Loading…
Reference in New Issue