Program bank integration (#1462)
Native, BPF and Lua loaders integrated into the bank
This commit is contained in:
parent
1b2e9122d5
commit
d09889b1dd
|
@ -109,9 +109,10 @@ 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"
|
||||||
solua = { path = "programs/native/solua" }
|
|
||||||
move_funds = { path = "programs/native/move_funds" }
|
move_funds = { path = "programs/native/move_funds" }
|
||||||
noop = { path = "programs/native/noop" }
|
noop = { path = "programs/native/noop" }
|
||||||
|
sobpf = { path = "programs/native/sobpf" }
|
||||||
|
solua = { path = "programs/native/solua" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bank"
|
name = "bank"
|
||||||
|
@ -136,8 +137,9 @@ name = "chacha"
|
||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"common",
|
"common",
|
||||||
"programs/native/solua",
|
|
||||||
"programs/native/move_funds",
|
"programs/native/move_funds",
|
||||||
"programs/native/noop",
|
"programs/native/noop",
|
||||||
|
"programs/native/sobpf",
|
||||||
|
"programs/native/solua",
|
||||||
"programs/bpf/noop_rust",
|
"programs/bpf/noop_rust",
|
||||||
]
|
]
|
||||||
|
|
9
build.rs
9
build.rs
|
@ -23,6 +23,15 @@ fn main() {
|
||||||
if bpf_c {
|
if bpf_c {
|
||||||
let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap();
|
let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap();
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=programs/bpf/noop_c/build.sh");
|
||||||
|
println!("cargo:rerun-if-changed=programs/bpf/noop_c/src/noop.c");
|
||||||
|
println!("cargo:warning=(not a warning) Compiling noop_c");
|
||||||
|
let status = Command::new("programs/bpf/noop_c/build.sh")
|
||||||
|
.arg(&out_dir)
|
||||||
|
.status()
|
||||||
|
.expect("Failed to call noop_c build script");
|
||||||
|
assert!(status.success());
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/build.sh");
|
println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/build.sh");
|
||||||
println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/src/move_funds.c");
|
println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/src/move_funds.c");
|
||||||
println!("cargo:warning=(not a warning) Compiling move_funds_c");
|
println!("cargo:warning=(not a warning) Compiling move_funds_c");
|
||||||
|
|
|
@ -11,14 +11,23 @@ pub struct Account {
|
||||||
pub userdata: Vec<u8>,
|
pub userdata: Vec<u8>,
|
||||||
/// contract id this contract belongs to
|
/// contract id this contract belongs to
|
||||||
pub program_id: Pubkey,
|
pub program_id: Pubkey,
|
||||||
|
|
||||||
|
/// this account contains a program (and is strictly read-only)
|
||||||
|
pub executable: bool,
|
||||||
|
|
||||||
|
/// the loader for this program (Pubkey::default() for no loader)
|
||||||
|
pub loader_program_id: Pubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
|
// TODO do we want to add executable and leader_program_id even though they should always be false/default?
|
||||||
pub fn new(tokens: i64, space: usize, program_id: Pubkey) -> Account {
|
pub fn new(tokens: i64, space: usize, program_id: Pubkey) -> Account {
|
||||||
Account {
|
Account {
|
||||||
tokens,
|
tokens,
|
||||||
userdata: vec![0u8; space],
|
userdata: vec![0u8; space],
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: false,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
pub mod loader_instruction;
|
||||||
pub mod pubkey;
|
pub mod pubkey;
|
||||||
extern crate bincode;
|
extern crate bincode;
|
||||||
extern crate bs58;
|
extern crate bs58;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum LoaderInstruction {
|
||||||
|
/// Write program data into an account
|
||||||
|
///
|
||||||
|
/// * key[0] - the account to write into.
|
||||||
|
///
|
||||||
|
/// The transaction must be signed by key[0]
|
||||||
|
Write { offset: u32, bytes: Vec<u8> },
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// The transaction must be signed by key[0]
|
||||||
|
Finalize,
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ typedef struct {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SolPubkey *key;
|
SolPubkey *key;
|
||||||
int64_t tokens;
|
int64_t* tokens;
|
||||||
uint64_t userdata_len;
|
uint64_t userdata_len;
|
||||||
uint8_t *userdata;
|
uint8_t *userdata;
|
||||||
SolPubkey *program_id;
|
SolPubkey *program_id;
|
||||||
|
@ -50,7 +50,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) {
|
||||||
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
|
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
uint8_t **userdata, uint64_t *userdata_len) {
|
uint8_t **userdata, uint64_t *userdata_len) {
|
||||||
if (num_ka != *(uint64_t *)src) {
|
if (num_ka != *(uint64_t *)src) {
|
||||||
return -1;
|
return 0;
|
||||||
}
|
}
|
||||||
src += sizeof(uint64_t);
|
src += sizeof(uint64_t);
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||||
src += SIZE_PUBKEY;
|
src += SIZE_PUBKEY;
|
||||||
|
|
||||||
// tokens
|
// tokens
|
||||||
ka[i].tokens = *(uint64_t *)src;
|
ka[i].tokens = (int64_t *)src;
|
||||||
src += sizeof(uint64_t);
|
src += sizeof(int64_t);
|
||||||
|
|
||||||
// account userdata
|
// account userdata
|
||||||
ka[i].userdata_len = *(uint64_t *)src;
|
ka[i].userdata_len = *(uint64_t *)src;
|
||||||
|
@ -79,7 +79,7 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||||
src += sizeof(uint64_t);
|
src += sizeof(uint64_t);
|
||||||
*userdata = src;
|
*userdata = src;
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
print_key(ka[i].key);
|
print_key(ka[i].key);
|
||||||
|
|
||||||
// tokens
|
// tokens
|
||||||
sol_print(0, 0, 0, 0, ka[i].tokens);
|
sol_print(0, 0, 0, 0, *ka[i].tokens);
|
||||||
|
|
||||||
// account userdata
|
// account userdata
|
||||||
print_userdata(ka[i].userdata, ka[i].userdata_len);
|
print_userdata(ka[i].userdata, ka[i].userdata_len);
|
||||||
|
@ -117,14 +117,24 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
print_userdata(userdata, userdata_len);
|
print_userdata(userdata, userdata_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void entrypoint(char *buf) {
|
uint64_t entrypoint(char *buf) {
|
||||||
SolKeyedAccounts ka[3];
|
SolKeyedAccounts ka[3];
|
||||||
uint64_t userdata_len;
|
uint64_t userdata_len;
|
||||||
uint8_t *userdata;
|
uint8_t *userdata;
|
||||||
|
|
||||||
if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
|
if (1 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
|
||||||
return;
|
return 1;
|
||||||
|
}
|
||||||
|
print_params(3, ka, userdata, userdata_len);
|
||||||
|
|
||||||
|
int64_t tokens = *(int64_t*)userdata;
|
||||||
|
if (*ka[0].tokens >= tokens) {
|
||||||
|
*ka[0].tokens -= tokens;
|
||||||
|
*ka[2].tokens += tokens;
|
||||||
|
//sol_print(0, 0, *ka[0].tokens, *ka[2].tokens, tokens);
|
||||||
|
} else {
|
||||||
|
//sol_print(0, 0, 0xFF, *ka[0].tokens, tokens);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
print_params(3, ka, userdata, userdata_len);
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
OUTDIR="${1:-../../../target/release/}"
|
||||||
|
THISDIR=$(dirname "$0")
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/noop_c.bc -c "$THISDIR"/src/noop.c
|
||||||
|
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/noop_c.o "$OUTDIR"/noop_c.bc
|
||||||
|
|
||||||
|
#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/noop_c.o
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/noop_c.o
|
|
@ -0,0 +1,133 @@
|
||||||
|
|
||||||
|
//#include <stdint.h>
|
||||||
|
//#include <stddef.h>
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
// one way to define a helper function is with index as a fixed value
|
||||||
|
#define BPF_TRACE_PRINTK_IDX 6
|
||||||
|
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
|
||||||
|
#else
|
||||||
|
// relocation is another option
|
||||||
|
extern int sol_print(int, int, int, int, int);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef long long unsigned int uint64_t;
|
||||||
|
typedef long long int int64_t;
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
|
||||||
|
typedef enum { false = 0, true } bool;
|
||||||
|
|
||||||
|
#define SIZE_PUBKEY 32
|
||||||
|
typedef struct {
|
||||||
|
uint8_t x[SIZE_PUBKEY];
|
||||||
|
} SolPubkey;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SolPubkey *key;
|
||||||
|
int64_t* tokens;
|
||||||
|
uint64_t userdata_len;
|
||||||
|
uint8_t *userdata;
|
||||||
|
SolPubkey *program_id;
|
||||||
|
} SolKeyedAccounts;
|
||||||
|
|
||||||
|
// TODO support BPF function calls rather then forcing everything to be inlined
|
||||||
|
#define SOL_FN_PREFIX __attribute__((always_inline)) static
|
||||||
|
|
||||||
|
// TODO move this to a registered helper
|
||||||
|
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
*((uint8_t *)dst + i) = *((uint8_t *)src + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define sol_panic() _sol_panic(__LINE__)
|
||||||
|
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
|
||||||
|
sol_print(0, 0, 0xFF, 0xFF, line);
|
||||||
|
char *pv = (char *)1;
|
||||||
|
*pv = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
|
uint8_t **userdata, uint64_t *userdata_len) {
|
||||||
|
if (num_ka != *(uint64_t *)src) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
src += sizeof(uint64_t);
|
||||||
|
|
||||||
|
// TODO fixed iteration loops ok? unrolled?
|
||||||
|
for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
|
||||||
|
// key
|
||||||
|
ka[i].key = (SolPubkey *)src;
|
||||||
|
src += SIZE_PUBKEY;
|
||||||
|
|
||||||
|
// tokens
|
||||||
|
ka[i].tokens = (int64_t *)src;
|
||||||
|
src += sizeof(int64_t);
|
||||||
|
|
||||||
|
// account userdata
|
||||||
|
ka[i].userdata_len = *(uint64_t *)src;
|
||||||
|
src += sizeof(uint64_t);
|
||||||
|
ka[i].userdata = src;
|
||||||
|
src += ka[i].userdata_len;
|
||||||
|
|
||||||
|
// program_id
|
||||||
|
ka[i].program_id = (SolPubkey *)src;
|
||||||
|
src += SIZE_PUBKEY;
|
||||||
|
}
|
||||||
|
// tx userdata
|
||||||
|
*userdata_len = *(uint64_t *)src;
|
||||||
|
src += sizeof(uint64_t);
|
||||||
|
*userdata = src;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- Debug --
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_key(SolPubkey *key) {
|
||||||
|
for (int j = 0; j < SIZE_PUBKEY; j++) {
|
||||||
|
sol_print(0, 0, 0, j, key->x[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
|
||||||
|
for (int j = 0; j < len; j++) {
|
||||||
|
sol_print(0, 0, 0, j, data[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
|
uint8_t *userdata, uint64_t userdata_len) {
|
||||||
|
sol_print(0, 0, 0, 0, num_ka);
|
||||||
|
for (int i = 0; i < num_ka; i++) {
|
||||||
|
// key
|
||||||
|
print_key(ka[i].key);
|
||||||
|
|
||||||
|
// tokens
|
||||||
|
sol_print(0, 0, 0, 0, *ka[i].tokens);
|
||||||
|
|
||||||
|
// account userdata
|
||||||
|
print_userdata(ka[i].userdata, ka[i].userdata_len);
|
||||||
|
|
||||||
|
// program_id
|
||||||
|
print_key(ka[i].program_id);
|
||||||
|
}
|
||||||
|
// tx userdata
|
||||||
|
print_userdata(userdata, userdata_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Program entrypoint --
|
||||||
|
|
||||||
|
uint64_t entrypoint(char *buf) {
|
||||||
|
SolKeyedAccounts ka[1];
|
||||||
|
uint64_t userdata_len;
|
||||||
|
uint8_t *userdata;
|
||||||
|
|
||||||
|
if (1 != sol_deserialize((uint8_t *)buf, 1, ka, &userdata, &userdata_len)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
print_params(1, ka, userdata, userdata_len);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) {
|
||||||
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
|
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
uint8_t **userdata, uint64_t *userdata_len) {
|
uint8_t **userdata, uint64_t *userdata_len) {
|
||||||
if (num_ka != *(uint64_t *)src) {
|
if (num_ka != *(uint64_t *)src) {
|
||||||
return -1;
|
return 0;
|
||||||
}
|
}
|
||||||
src += sizeof(uint64_t);
|
src += sizeof(uint64_t);
|
||||||
|
|
||||||
|
@ -78,9 +78,55 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount
|
||||||
src += sizeof(uint64_t);
|
src += sizeof(uint64_t);
|
||||||
*userdata = src;
|
*userdata = src;
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // -- Debug --
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_key(SolPubkey *key) {
|
||||||
|
for (int j = 0; j < SIZE_PUBKEY; j++) {
|
||||||
|
sol_print(0, 0, 0, j, key->x[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
|
||||||
|
for (int j = 0; j < len; j++) {
|
||||||
|
sol_print(0, 0, 0, j, data[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
||||||
|
uint8_t *userdata, uint64_t userdata_len) {
|
||||||
|
sol_print(0, 0, 0, 0, num_ka);
|
||||||
|
for (int i = 0; i < num_ka; i++) {
|
||||||
|
// key
|
||||||
|
print_key(ka[i].key);
|
||||||
|
|
||||||
|
// tokens
|
||||||
|
sol_print(0, 0, 0, 0, ka[i].tokens);
|
||||||
|
|
||||||
|
// account userdata
|
||||||
|
print_userdata(ka[i].userdata, ka[i].userdata_len);
|
||||||
|
|
||||||
|
// program_id
|
||||||
|
print_key(ka[i].program_id);
|
||||||
|
}
|
||||||
|
// tx userdata
|
||||||
|
print_userdata(userdata, userdata_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void entrypoint(char *buf) {
|
||||||
|
// SolKeyedAccounts ka[3];
|
||||||
|
// uint64_t userdata_len;
|
||||||
|
// uint8_t *userdata;
|
||||||
|
|
||||||
|
// if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// print_params(3, ka, userdata, userdata_len);
|
||||||
|
// }
|
||||||
|
|
||||||
// -- TicTacToe --
|
// -- TicTacToe --
|
||||||
|
|
||||||
// Board Coodinates
|
// Board Coodinates
|
||||||
|
@ -279,92 +325,48 @@ SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player,
|
||||||
return Result_Ok;
|
return Result_Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void entrypoint(uint8_t *buf) {
|
uint64_t entrypoint(uint8_t *buf) {
|
||||||
SolKeyedAccounts ka[3];
|
SolKeyedAccounts ka[4];
|
||||||
uint64_t userdata_len;
|
uint64_t userdata_len;
|
||||||
uint8_t *userdata;
|
uint8_t *userdata;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) {
|
if (1 != sol_deserialize(buf, 4, ka, &userdata, &userdata_len)) {
|
||||||
sol_panic();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sizeof(Game) > ka[1].userdata_len) {
|
if (sizeof(Game) > ka[2].userdata_len) {
|
||||||
sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len);
|
sol_print(0, 0, 0xFF, sizeof(Game), ka[2].userdata_len);
|
||||||
sol_panic();
|
return 0;
|
||||||
}
|
}
|
||||||
Game game;
|
Game game;
|
||||||
sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len);
|
sol_memcpy(&game, ka[2].userdata, ka[2].userdata_len);
|
||||||
|
|
||||||
Command command = *userdata;
|
Command command = *userdata;
|
||||||
sol_print(0, 0, 0, 0, command);
|
//sol_print(0, 0, 0, 0, command);
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case Command_Init:
|
case Command_Init:
|
||||||
game_create(&game, ka[2].key);
|
game_create(&game, ka[3].key);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command_Join:
|
case Command_Join:
|
||||||
err = game_join(&game, ka[0].key, userdata[8]);
|
err = game_join(&game, ka[3].key, userdata[8]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command_KeepAlive:
|
case Command_KeepAlive:
|
||||||
err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0);
|
err = game_keep_alive(&game, ka[3].key, /*TODO*/ 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command_Move:
|
case Command_Move:
|
||||||
err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]);
|
err = game_next_move(&game, ka[3].key, userdata[8], userdata[9]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sol_panic();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len);
|
sol_memcpy(ka[2].userdata, &game, ka[2].userdata_len);
|
||||||
sol_print(0, 0, 0, err, game.state);
|
sol_print(0, 0, 0, err, game.state);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // -- Debug --
|
|
||||||
|
|
||||||
// SOL_FN_PREFIX void print_key(SolPubkey *key) {
|
|
||||||
// for (int j = 0; j < SIZE_PUBKEY; j++) {
|
|
||||||
// sol_print(0, 0, 0, j, key->x[j]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
|
|
||||||
// for (int j = 0; j < len; j++) {
|
|
||||||
// sol_print(0, 0, 0, j, data[j]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
|
|
||||||
// uint8_t *userdata, uint64_t userdata_len) {
|
|
||||||
// sol_print(0, 0, 0, 0, num_ka);
|
|
||||||
// for (int i = 0; i < num_ka; i++) {
|
|
||||||
// // key
|
|
||||||
// print_key(ka[i].key);
|
|
||||||
|
|
||||||
// // tokens
|
|
||||||
// sol_print(0, 0, 0, 0, ka[i].tokens);
|
|
||||||
|
|
||||||
// // account userdata
|
|
||||||
// print_userdata(ka[i].userdata, ka[i].userdata_len);
|
|
||||||
|
|
||||||
// // program_id
|
|
||||||
// print_key(ka[i].program_id);
|
|
||||||
// }
|
|
||||||
// // tx userdata
|
|
||||||
// print_userdata(userdata, userdata_len);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void entrypoint(char *buf) {
|
|
||||||
// SolKeyedAccounts ka[3];
|
|
||||||
// uint64_t userdata_len;
|
|
||||||
// uint8_t *userdata;
|
|
||||||
|
|
||||||
// if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata_len, &userdata)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// print_params(3, ka, userdata, userdata_len);
|
|
||||||
// }
|
|
||||||
|
|
|
@ -5,16 +5,16 @@ use bincode::deserialize;
|
||||||
use solana_program_interface::account::KeyedAccount;
|
use solana_program_interface::account::KeyedAccount;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
pub extern "C" fn process(keyed_accounts: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
||||||
let tokens: i64 = deserialize(data).unwrap();
|
let tokens: i64 = deserialize(data).unwrap();
|
||||||
if infos[0].account.tokens >= tokens {
|
if keyed_accounts[0].account.tokens >= tokens {
|
||||||
infos[0].account.tokens -= tokens;
|
keyed_accounts[0].account.tokens -= tokens;
|
||||||
infos[1].account.tokens += tokens;
|
keyed_accounts[1].account.tokens += tokens;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Insufficient funds, asked {}, only had {}",
|
"Insufficient funds, asked {}, only had {}",
|
||||||
tokens, infos[0].account.tokens
|
tokens, keyed_accounts[0].account.tokens
|
||||||
);
|
);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,12 @@ mod tests {
|
||||||
accounts[1].tokens = 1;
|
accounts[1].tokens = 1;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut infos: Vec<KeyedAccount> = Vec::new();
|
let mut keyed_accounts: Vec<KeyedAccount> = Vec::new();
|
||||||
for (key, account) in keys.iter().zip(&mut accounts).collect::<Vec<_>>() {
|
for (key, account) in keys.iter().zip(&mut accounts).collect::<Vec<_>>() {
|
||||||
infos.push(KeyedAccount { key, account });
|
infos.push(KeyedAccount { key, account });
|
||||||
}
|
}
|
||||||
|
|
||||||
process(&mut infos, &data);
|
process(&mut keyed_accounts, &data);
|
||||||
}
|
}
|
||||||
assert_eq!(0, accounts[0].tokens);
|
assert_eq!(0, accounts[0].tokens);
|
||||||
assert_eq!(101, accounts[1].tokens);
|
assert_eq!(101, accounts[1].tokens);
|
||||||
|
|
|
@ -3,8 +3,8 @@ extern crate solana_program_interface;
|
||||||
use solana_program_interface::account::KeyedAccount;
|
use solana_program_interface::account::KeyedAccount;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> bool {
|
||||||
println!("noop: AccountInfos: {:#?}", infos);
|
println!("noop: keyed_accounts: {:#?}", keyed_accounts);
|
||||||
println!("noop: data: {:#?}", data);
|
println!("noop: data: {:?}", data);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "sobpf"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = "1.0.0"
|
||||||
|
byteorder = "1.2.1"
|
||||||
|
elf = "0.0.10"
|
||||||
|
libc = "0.2.43"
|
||||||
|
log = "0.4.2"
|
||||||
|
rbpf = { git = "https://github.com/solana-labs/rbpf" }
|
||||||
|
serde = "1.0.27"
|
||||||
|
serde_derive = "1.0.27"
|
||||||
|
solana_program_interface = { path = "../../../common" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "sobpf"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
|
@ -0,0 +1,314 @@
|
||||||
|
use rbpf::ebpf;
|
||||||
|
|
||||||
|
// This “verifier” performs simple checks when the eBPF program is loaded into the VM (before it is
|
||||||
|
// interpreted or JIT-compiled).
|
||||||
|
|
||||||
|
fn verify_prog_len(prog: &[u8]) {
|
||||||
|
if prog.len() % ebpf::INSN_SIZE != 0 {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: eBPF program length must be a multiple of {:?} octets",
|
||||||
|
ebpf::INSN_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if prog.len() > ebpf::PROG_MAX_SIZE {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: eBPF program length limited to {:?}, here {:?}",
|
||||||
|
ebpf::PROG_MAX_INSNS,
|
||||||
|
prog.len() / ebpf::INSN_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if prog.is_empty() {
|
||||||
|
panic!("[Verifier] Error: program is empty");
|
||||||
|
}
|
||||||
|
// TODO BPF program may deterministically exit even if the last
|
||||||
|
// instruction in the block is not an exit (might be earlier and jumped to)
|
||||||
|
// TODO need to validate more intelligently
|
||||||
|
// let last_insn = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1);
|
||||||
|
// if last_insn.opc != ebpf::EXIT {
|
||||||
|
// panic!("[Verifier] Error: program does not end with “EXIT” instruction");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_imm_nonzero(insn: &ebpf::Insn, insn_ptr: usize) {
|
||||||
|
if insn.imm == 0 {
|
||||||
|
panic!("[Verifier] Error: division by 0 (insn #{:?})", insn_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) {
|
||||||
|
match insn.imm {
|
||||||
|
16 | 32 | 64 => return,
|
||||||
|
_ => panic!(
|
||||||
|
"[Verifier] Error: unsupported argument for LE/BE (insn #{:?})",
|
||||||
|
insn_ptr
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_load_dw(prog: &[u8], insn_ptr: usize) {
|
||||||
|
// We know we can reach next insn since we enforce an EXIT insn at the end of program, while
|
||||||
|
// this function should be called only for LD_DW insn, that cannot be last in program.
|
||||||
|
let next_insn = ebpf::get_insn(prog, insn_ptr + 1);
|
||||||
|
if next_insn.opc != 0 {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: incomplete LD_DW instruction (insn #{:?})",
|
||||||
|
insn_ptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_jmp_offset(prog: &[u8], insn_ptr: usize) {
|
||||||
|
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||||
|
if insn.off == -1 {
|
||||||
|
panic!("[Verifier] Error: infinite loop (insn #{:?})", insn_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_insn_ptr = insn_ptr as isize + 1 + insn.off as isize;
|
||||||
|
if dst_insn_ptr < 0 || dst_insn_ptr as usize >= (prog.len() / ebpf::INSN_SIZE) {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: jump out of code to #{:?} (insn #{:?})",
|
||||||
|
dst_insn_ptr, insn_ptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize);
|
||||||
|
if dst_insn.opc == 0 {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: jump to middle of LD_DW at #{:?} (insn #{:?})",
|
||||||
|
dst_insn_ptr, insn_ptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_registers(insn: &ebpf::Insn, store: bool, insn_ptr: usize) {
|
||||||
|
if insn.src > 10 {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: invalid source register (insn #{:?})",
|
||||||
|
insn_ptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match (insn.dst, store) {
|
||||||
|
(0...9, _) | (10, true) => {}
|
||||||
|
(10, false) => panic!(
|
||||||
|
"[Verifier] Error: cannot write into register r10 (insn #{:?})",
|
||||||
|
insn_ptr
|
||||||
|
),
|
||||||
|
(_, _) => panic!(
|
||||||
|
"[Verifier] Error: invalid destination register (insn #{:?})",
|
||||||
|
insn_ptr
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verifier(prog: &[u8]) -> bool {
|
||||||
|
verify_prog_len(prog);
|
||||||
|
|
||||||
|
let mut insn_ptr: usize = 0;
|
||||||
|
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
|
||||||
|
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||||
|
let mut store = false;
|
||||||
|
|
||||||
|
match insn.opc {
|
||||||
|
// BPF_LD class
|
||||||
|
ebpf::LD_ABS_B => {}
|
||||||
|
ebpf::LD_ABS_H => {}
|
||||||
|
ebpf::LD_ABS_W => {}
|
||||||
|
ebpf::LD_ABS_DW => {}
|
||||||
|
ebpf::LD_IND_B => {}
|
||||||
|
ebpf::LD_IND_H => {}
|
||||||
|
ebpf::LD_IND_W => {}
|
||||||
|
ebpf::LD_IND_DW => {}
|
||||||
|
|
||||||
|
ebpf::LD_DW_IMM => {
|
||||||
|
store = true;
|
||||||
|
verify_load_dw(prog, insn_ptr);
|
||||||
|
insn_ptr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BPF_LDX class
|
||||||
|
ebpf::LD_B_REG => {}
|
||||||
|
ebpf::LD_H_REG => {}
|
||||||
|
ebpf::LD_W_REG => {}
|
||||||
|
ebpf::LD_DW_REG => {}
|
||||||
|
|
||||||
|
// BPF_ST class
|
||||||
|
ebpf::ST_B_IMM => store = true,
|
||||||
|
ebpf::ST_H_IMM => store = true,
|
||||||
|
ebpf::ST_W_IMM => store = true,
|
||||||
|
ebpf::ST_DW_IMM => store = true,
|
||||||
|
|
||||||
|
// BPF_STX class
|
||||||
|
ebpf::ST_B_REG => store = true,
|
||||||
|
ebpf::ST_H_REG => store = true,
|
||||||
|
ebpf::ST_W_REG => store = true,
|
||||||
|
ebpf::ST_DW_REG => store = true,
|
||||||
|
ebpf::ST_W_XADD => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
ebpf::ST_DW_XADD => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// BPF_ALU class
|
||||||
|
ebpf::ADD32_IMM => {}
|
||||||
|
ebpf::ADD32_REG => {}
|
||||||
|
ebpf::SUB32_IMM => {}
|
||||||
|
ebpf::SUB32_REG => {}
|
||||||
|
ebpf::MUL32_IMM => {}
|
||||||
|
ebpf::MUL32_REG => {}
|
||||||
|
ebpf::DIV32_IMM => {
|
||||||
|
verify_imm_nonzero(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::DIV32_REG => {}
|
||||||
|
ebpf::OR32_IMM => {}
|
||||||
|
ebpf::OR32_REG => {}
|
||||||
|
ebpf::AND32_IMM => {}
|
||||||
|
ebpf::AND32_REG => {}
|
||||||
|
ebpf::LSH32_IMM => {}
|
||||||
|
ebpf::LSH32_REG => {}
|
||||||
|
ebpf::RSH32_IMM => {}
|
||||||
|
ebpf::RSH32_REG => {}
|
||||||
|
ebpf::NEG32 => {}
|
||||||
|
ebpf::MOD32_IMM => {
|
||||||
|
verify_imm_nonzero(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::MOD32_REG => {}
|
||||||
|
ebpf::XOR32_IMM => {}
|
||||||
|
ebpf::XOR32_REG => {}
|
||||||
|
ebpf::MOV32_IMM => {}
|
||||||
|
ebpf::MOV32_REG => {}
|
||||||
|
ebpf::ARSH32_IMM => {}
|
||||||
|
ebpf::ARSH32_REG => {}
|
||||||
|
ebpf::LE => {
|
||||||
|
verify_imm_endian(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::BE => {
|
||||||
|
verify_imm_endian(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BPF_ALU64 class
|
||||||
|
ebpf::ADD64_IMM => {}
|
||||||
|
ebpf::ADD64_REG => {}
|
||||||
|
ebpf::SUB64_IMM => {}
|
||||||
|
ebpf::SUB64_REG => {}
|
||||||
|
ebpf::MUL64_IMM => {
|
||||||
|
verify_imm_nonzero(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::MUL64_REG => {}
|
||||||
|
ebpf::DIV64_IMM => {
|
||||||
|
verify_imm_nonzero(&insn, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::DIV64_REG => {}
|
||||||
|
ebpf::OR64_IMM => {}
|
||||||
|
ebpf::OR64_REG => {}
|
||||||
|
ebpf::AND64_IMM => {}
|
||||||
|
ebpf::AND64_REG => {}
|
||||||
|
ebpf::LSH64_IMM => {}
|
||||||
|
ebpf::LSH64_REG => {}
|
||||||
|
ebpf::RSH64_IMM => {}
|
||||||
|
ebpf::RSH64_REG => {}
|
||||||
|
ebpf::NEG64 => {}
|
||||||
|
ebpf::MOD64_IMM => {}
|
||||||
|
ebpf::MOD64_REG => {}
|
||||||
|
ebpf::XOR64_IMM => {}
|
||||||
|
ebpf::XOR64_REG => {}
|
||||||
|
ebpf::MOV64_IMM => {}
|
||||||
|
ebpf::MOV64_REG => {}
|
||||||
|
ebpf::ARSH64_IMM => {}
|
||||||
|
ebpf::ARSH64_REG => {}
|
||||||
|
|
||||||
|
// BPF_JMP class
|
||||||
|
ebpf::JA => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JEQ_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JEQ_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JGT_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JGT_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JGE_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JGE_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JLT_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JLT_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JLE_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JLE_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSET_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSET_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JNE_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JNE_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSGT_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSGT_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSGE_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSGE_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSLT_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSLT_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSLE_IMM => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::JSLE_REG => {
|
||||||
|
verify_jmp_offset(prog, insn_ptr);
|
||||||
|
}
|
||||||
|
ebpf::CALL => {}
|
||||||
|
ebpf::TAIL_CALL => unimplemented!(),
|
||||||
|
ebpf::EXIT => {}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
panic!(
|
||||||
|
"[Verifier] Error: unknown eBPF opcode {:#2x} (insn #{:?})",
|
||||||
|
insn.opc, insn_ptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_registers(&insn, store, insn_ptr);
|
||||||
|
|
||||||
|
insn_ptr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// insn_ptr should now be equal to number of instructions.
|
||||||
|
if insn_ptr != prog.len() / ebpf::INSN_SIZE {
|
||||||
|
panic!("[Verifier] Error: jumped out of code to #{:?}", insn_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
extern crate bincode;
|
||||||
|
extern crate byteorder;
|
||||||
|
extern crate elf;
|
||||||
|
extern crate libc;
|
||||||
|
extern crate rbpf;
|
||||||
|
extern crate solana_program_interface;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
pub mod bpf_verifier;
|
||||||
|
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
|
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
||||||
|
use solana_program_interface::account::KeyedAccount;
|
||||||
|
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||||
|
use solana_program_interface::pubkey::Pubkey;
|
||||||
|
use std::env;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::mem;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
/// Dynamic link library prefixs
|
||||||
|
const PLATFORM_FILE_PREFIX_BPF: &str = "";
|
||||||
|
|
||||||
|
/// Dynamic link library file extension specific to the platform
|
||||||
|
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
|
||||||
|
|
||||||
|
/// Section name
|
||||||
|
pub const PLATFORM_SECTION_RS: &str = ".text,entrypoint";
|
||||||
|
pub 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().parent().unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
pathbuf.join(
|
||||||
|
PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name)
|
||||||
|
.with_extension(PLATFORM_FILE_EXTENSION_BPF),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_state(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> Vec<u8> {
|
||||||
|
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||||
|
|
||||||
|
let mut v: Vec<u8> = Vec::new();
|
||||||
|
v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
|
||||||
|
.unwrap();
|
||||||
|
for info in keyed_accounts.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();
|
||||||
|
}
|
||||||
|
v.write_u64::<LittleEndian>(data.len() as u64).unwrap();
|
||||||
|
v.write_all(data).unwrap();
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_state(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) {
|
||||||
|
assert_eq!(32, mem::size_of::<Pubkey>());
|
||||||
|
|
||||||
|
let mut start = mem::size_of::<u64>();
|
||||||
|
for info in keyed_accounts.iter_mut() {
|
||||||
|
start += mem::size_of::<Pubkey>(); // skip pubkey
|
||||||
|
info.account.tokens = LittleEndian::read_i64(&buffer[start..]);
|
||||||
|
|
||||||
|
start += mem::size_of::<u64>() // skip tokens
|
||||||
|
+ mem::size_of::<u64>(); // skip length tag
|
||||||
|
let end = start + info.account.userdata.len();
|
||||||
|
info.account.userdata.clone_from_slice(&buffer[start..end]);
|
||||||
|
|
||||||
|
start += info.account.userdata.len() // skip userdata
|
||||||
|
+ mem::size_of::<Pubkey>(); // skip program_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub enum BpfLoader {
|
||||||
|
File { name: String },
|
||||||
|
Bytes { bytes: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||||
|
if keyed_accounts[0].account.executable {
|
||||||
|
let prog: Vec<u8>;
|
||||||
|
if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) {
|
||||||
|
match program {
|
||||||
|
BpfLoader::File { name } => {
|
||||||
|
trace!("Call Bpf with file {:?}", name);
|
||||||
|
let path = create_path(&name);
|
||||||
|
let file = match elf::File::open_path(&path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Error opening ELF {:?}: {:?}", path, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
warn!("Failed to find elf section {:?}", PLATFORM_SECTION_C);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
prog = text_section.data.clone();
|
||||||
|
}
|
||||||
|
BpfLoader::Bytes { bytes } => {
|
||||||
|
trace!("Call Bpf with bytes");
|
||||||
|
prog = bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("deserialize failed: {:?}", tx_data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
trace!("Call BPF, {} Instructions", prog.len() / 8);
|
||||||
|
|
||||||
|
let mut vm = rbpf::EbpfVmRaw::new(&prog, Some(bpf_verifier::verifier));
|
||||||
|
vm.register_helper(
|
||||||
|
rbpf::helpers::BPF_TRACE_PRINTK_IDX,
|
||||||
|
rbpf::helpers::bpf_trace_printf,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut v = serialize_state(&mut keyed_accounts[1..], &tx_data);
|
||||||
|
if 0 == vm.prog_exec(v.as_mut_slice()) {
|
||||||
|
warn!("BPF program failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
deserialize_state(&mut keyed_accounts[1..], &v);
|
||||||
|
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||||
|
match instruction {
|
||||||
|
LoaderInstruction::Write { offset, bytes } => {
|
||||||
|
trace!("BPFLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||||
|
let offset = offset as usize;
|
||||||
|
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let name = match str::from_utf8(&bytes) {
|
||||||
|
Ok(s) => s.to_string(),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Invalid UTF-8 sequence: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
trace!("name: {:?}", name);
|
||||||
|
let s = serialize(&BpfLoader::File { name }).unwrap();
|
||||||
|
keyed_accounts[0]
|
||||||
|
.account
|
||||||
|
.userdata
|
||||||
|
.splice(0..s.len(), s.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
LoaderInstruction::Finalize => {
|
||||||
|
keyed_accounts[0].account.executable = true;
|
||||||
|
keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id;
|
||||||
|
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||||
|
trace!(
|
||||||
|
"BPFLoader::Finalize prog: {:?} loader {:?}",
|
||||||
|
keyed_accounts[0].account.program_id,
|
||||||
|
keyed_accounts[0].account.loader_program_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid program transaction: {:?}", tx_data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
|
@ -4,7 +4,11 @@ version = "0.1.0"
|
||||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bincode = "1.0.0"
|
||||||
|
log = "0.4.2"
|
||||||
rlua = "0.15.2"
|
rlua = "0.15.2"
|
||||||
|
serde = "1.0.27"
|
||||||
|
serde_derive = "1.0.27"
|
||||||
solana_program_interface = { path = "../../../common" }
|
solana_program_interface = { path = "../../../common" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
|
extern crate bincode;
|
||||||
extern crate rlua;
|
extern crate rlua;
|
||||||
extern crate solana_program_interface;
|
extern crate solana_program_interface;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use bincode::{deserialize, serialize};
|
||||||
use rlua::{Lua, Result, Table};
|
use rlua::{Lua, Result, Table};
|
||||||
use solana_program_interface::account::KeyedAccount;
|
use solana_program_interface::account::KeyedAccount;
|
||||||
|
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub enum LuaLoader {
|
||||||
|
File { name: String },
|
||||||
|
Bytes { bytes: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
/// Make KeyAccount values available to Lua.
|
/// Make KeyAccount values available to Lua.
|
||||||
fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||||
let accounts = lua.create_table()?;
|
let accounts = lua.create_table()?;
|
||||||
|
@ -21,7 +34,7 @@ fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Resul
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit the new KeyedAccount values.
|
/// Commit the new KeyedAccount values.
|
||||||
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec<KeyedAccount>) -> Result<()> {
|
fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut [KeyedAccount]) -> Result<()> {
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
let accounts: Table = globals.get(name)?;
|
let accounts: Table = globals.get(name)?;
|
||||||
for (i, keyed_account) in keyed_accounts.into_iter().enumerate() {
|
for (i, keyed_account) in keyed_accounts.into_iter().enumerate() {
|
||||||
|
@ -33,7 +46,7 @@ fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec<KeyedAccount>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_lua(keyed_accounts: &mut Vec<KeyedAccount>, code: &str, data: &[u8]) -> Result<()> {
|
fn run_lua(keyed_accounts: &mut [KeyedAccount], code: &str, data: &[u8]) -> Result<()> {
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
let data_str = lua.create_string(data)?;
|
let data_str = lua.create_string(data)?;
|
||||||
|
@ -45,10 +58,67 @@ fn run_lua(keyed_accounts: &mut Vec<KeyedAccount>, code: &str, data: &[u8]) -> R
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn process(keyed_accounts: &mut Vec<KeyedAccount>, data: &[u8]) -> bool {
|
pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||||
let code_data = keyed_accounts[0].account.userdata.clone();
|
if keyed_accounts[0].account.executable {
|
||||||
let code = str::from_utf8(&code_data).unwrap();
|
let prog: Vec<u8>;
|
||||||
run_lua(keyed_accounts, &code, data).unwrap();
|
if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) {
|
||||||
|
match program {
|
||||||
|
LuaLoader::File { name } => {
|
||||||
|
trace!("Call Lua with file {:?}", name);
|
||||||
|
panic!("Not supported");
|
||||||
|
}
|
||||||
|
LuaLoader::Bytes { bytes } => {
|
||||||
|
trace!("Call Lua with bytes, code size {}", bytes.len());
|
||||||
|
prog = bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("deserialize failed: {:?}", tx_data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = str::from_utf8(&prog).unwrap();
|
||||||
|
match run_lua(&mut keyed_accounts[1..], &code, tx_data) {
|
||||||
|
Ok(()) => {
|
||||||
|
trace!("Lua success");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Lua Error: {:#?}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||||
|
match instruction {
|
||||||
|
LoaderInstruction::Write { offset, bytes } => {
|
||||||
|
trace!("LuaLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||||
|
let offset = offset as usize;
|
||||||
|
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||||
|
warn!("program overflow offset {} len {}", offset, bytes.len());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let s = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||||
|
keyed_accounts[0]
|
||||||
|
.account
|
||||||
|
.userdata
|
||||||
|
.splice(0..s.len(), s.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
LoaderInstruction::Finalize => {
|
||||||
|
keyed_accounts[0].account.executable = true;
|
||||||
|
keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id;
|
||||||
|
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||||
|
trace!(
|
||||||
|
"LuaLoader::Finalize prog: {:?} loader {:?}",
|
||||||
|
keyed_accounts[0].account.program_id,
|
||||||
|
keyed_accounts[0].account.loader_program_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid program transaction: {:?}", tx_data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +168,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_move_funds_with_lua_via_process() {
|
fn test_move_funds_with_lua_via_process() {
|
||||||
let userdata = r#"
|
let bytes = r#"
|
||||||
local tokens, _ = string.unpack("I", data)
|
local tokens, _ = string.unpack("I", data)
|
||||||
accounts[1].tokens = accounts[1].tokens - tokens
|
accounts[1].tokens = accounts[1].tokens - tokens
|
||||||
accounts[2].tokens = accounts[2].tokens + tokens
|
accounts[2].tokens = accounts[2].tokens + tokens
|
||||||
"#.as_bytes()
|
"#.as_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||||
|
|
||||||
let alice_pubkey = Pubkey::default();
|
let alice_pubkey = Pubkey::default();
|
||||||
let bob_pubkey = Pubkey::default();
|
let bob_pubkey = Pubkey::default();
|
||||||
|
@ -111,89 +182,26 @@ mod tests {
|
||||||
|
|
||||||
let mut accounts = [
|
let mut accounts = [
|
||||||
(
|
(
|
||||||
alice_pubkey,
|
Pubkey::default(),
|
||||||
Account {
|
Account {
|
||||||
tokens: 100,
|
tokens: 1,
|
||||||
userdata,
|
userdata,
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: true,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(alice_pubkey, Account::new(100, 0, program_id)),
|
||||||
(bob_pubkey, Account::new(1, 0, program_id)),
|
(bob_pubkey, Account::new(1, 0, program_id)),
|
||||||
];
|
];
|
||||||
let data = serialize(&10u64).unwrap();
|
let data = serialize(&10u64).unwrap();
|
||||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||||
assert_eq!(accounts[0].1.tokens, 90);
|
assert_eq!(accounts[1].1.tokens, 90);
|
||||||
assert_eq!(accounts[1].1.tokens, 11);
|
assert_eq!(accounts[2].1.tokens, 11);
|
||||||
|
|
||||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
process(&mut create_keyed_accounts(&mut accounts), &data);
|
||||||
assert_eq!(accounts[0].1.tokens, 80);
|
assert_eq!(accounts[1].1.tokens, 80);
|
||||||
assert_eq!(accounts[1].1.tokens, 21);
|
assert_eq!(accounts[2].1.tokens, 21);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_move_funds_with_lua_via_process_and_terminate() {
|
|
||||||
let userdata = r#"
|
|
||||||
local tokens, _ = string.unpack("I", data)
|
|
||||||
accounts[1].tokens = accounts[1].tokens - tokens
|
|
||||||
accounts[2].tokens = accounts[2].tokens + tokens
|
|
||||||
accounts[1].userdata = ""
|
|
||||||
"#.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let alice_pubkey = Pubkey::default();
|
|
||||||
let bob_pubkey = Pubkey::default();
|
|
||||||
let program_id = Pubkey::default();
|
|
||||||
|
|
||||||
let mut accounts = [
|
|
||||||
(
|
|
||||||
alice_pubkey,
|
|
||||||
Account {
|
|
||||||
tokens: 100,
|
|
||||||
userdata,
|
|
||||||
program_id,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(bob_pubkey, Account::new(1, 0, program_id)),
|
|
||||||
];
|
|
||||||
let data = serialize(&10).unwrap();
|
|
||||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
|
||||||
assert_eq!(accounts[0].1.tokens, 90);
|
|
||||||
assert_eq!(accounts[1].1.tokens, 11);
|
|
||||||
|
|
||||||
// Verify the program modified itself to a no-op.
|
|
||||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
|
||||||
assert_eq!(accounts[0].1.tokens, 90);
|
|
||||||
assert_eq!(accounts[1].1.tokens, 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_abort_tx_with_lua() {
|
|
||||||
let userdata = r#"
|
|
||||||
if data == accounts[1].key then
|
|
||||||
accounts[1].userdata = ""
|
|
||||||
end
|
|
||||||
"#.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let alice_pubkey = Pubkey::default();
|
|
||||||
let program_id = Pubkey::default();
|
|
||||||
|
|
||||||
let mut accounts = [(
|
|
||||||
alice_pubkey,
|
|
||||||
Account {
|
|
||||||
tokens: 100,
|
|
||||||
userdata,
|
|
||||||
program_id,
|
|
||||||
},
|
|
||||||
)];
|
|
||||||
|
|
||||||
// Abort
|
|
||||||
let data = alice_pubkey.to_string().as_bytes().to_vec();
|
|
||||||
|
|
||||||
assert_ne!(accounts[0].1.userdata, vec![]);
|
|
||||||
process(&mut create_keyed_accounts(&mut accounts), &data);
|
|
||||||
assert_eq!(accounts[0].1.tokens, 100);
|
|
||||||
assert_eq!(accounts[0].1.userdata, vec![]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_test_file(name: &str) -> Vec<u8> {
|
fn read_test_file(name: &str) -> Vec<u8> {
|
||||||
|
@ -207,27 +215,35 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_lua_library() {
|
fn test_load_lua_library() {
|
||||||
let userdata = r#"
|
let bytes = r#"
|
||||||
local serialize = load(accounts[2].userdata)().serialize
|
local serialize = load(accounts[2].userdata)().serialize
|
||||||
accounts[3].userdata = serialize({a=1, b=2, c=3}, nil, "s")
|
accounts[3].userdata = serialize({a=1, b=2, c=3}, nil, "s")
|
||||||
"#.as_bytes()
|
"#.as_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap();
|
||||||
|
|
||||||
let program_id = Pubkey::default();
|
let program_id = Pubkey::default();
|
||||||
|
|
||||||
let alice_account = Account {
|
let program_account = Account {
|
||||||
tokens: 100,
|
tokens: 1,
|
||||||
userdata,
|
userdata,
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: true,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let alice_account = Account::new(100, 0, program_id);
|
||||||
|
|
||||||
let serialize_account = Account {
|
let serialize_account = Account {
|
||||||
tokens: 100,
|
tokens: 100,
|
||||||
userdata: read_test_file("serialize.lua"),
|
userdata: read_test_file("serialize.lua"),
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: false,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut accounts = [
|
let mut accounts = [
|
||||||
|
(Pubkey::default(), program_account),
|
||||||
(Pubkey::default(), alice_account),
|
(Pubkey::default(), alice_account),
|
||||||
(Pubkey::default(), serialize_account),
|
(Pubkey::default(), serialize_account),
|
||||||
(Pubkey::default(), Account::new(1, 0, program_id)),
|
(Pubkey::default(), Account::new(1, 0, program_id)),
|
||||||
|
@ -238,7 +254,7 @@ mod tests {
|
||||||
|
|
||||||
// Verify deterministic ordering of a serialized Lua table.
|
// Verify deterministic ordering of a serialized Lua table.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
str::from_utf8(&keyed_accounts[2].account.userdata).unwrap(),
|
str::from_utf8(&keyed_accounts[3].account.userdata).unwrap(),
|
||||||
"{a=1,b=2,c=3}"
|
"{a=1,b=2,c=3}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -255,20 +271,36 @@ mod tests {
|
||||||
let dan_pubkey = Pubkey::new(&[5; 32]);
|
let dan_pubkey = Pubkey::new(&[5; 32]);
|
||||||
let erin_pubkey = Pubkey::new(&[6; 32]);
|
let erin_pubkey = Pubkey::new(&[6; 32]);
|
||||||
|
|
||||||
|
let userdata = serialize(&LuaLoader::Bytes {
|
||||||
|
bytes: read_test_file("multisig.lua"),
|
||||||
|
}).unwrap();
|
||||||
|
let program_account = Account {
|
||||||
|
tokens: 1,
|
||||||
|
userdata,
|
||||||
|
program_id,
|
||||||
|
executable: true,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
|
};
|
||||||
|
|
||||||
let alice_account = Account {
|
let alice_account = Account {
|
||||||
tokens: 100,
|
tokens: 100,
|
||||||
userdata: read_test_file("multisig.lua"),
|
userdata: Vec::new(),
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: true,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialize_account = Account {
|
let serialize_account = Account {
|
||||||
tokens: 100,
|
tokens: 100,
|
||||||
userdata: read_test_file("serialize.lua"),
|
userdata: read_test_file("serialize.lua"),
|
||||||
program_id,
|
program_id,
|
||||||
|
executable: true,
|
||||||
|
loader_program_id: Pubkey::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut accounts = [
|
let mut accounts = [
|
||||||
(alice_pubkey, alice_account), // The payer and where the program is stored.
|
(Pubkey::default(), program_account), // Account holding the program
|
||||||
|
(alice_pubkey, alice_account), // The payer
|
||||||
(serialize_pubkey, serialize_account), // Where the serialize library is stored.
|
(serialize_pubkey, serialize_account), // Where the serialize library is stored.
|
||||||
(state_pubkey, Account::new(1, 0, program_id)), // Where program state is stored.
|
(state_pubkey, Account::new(1, 0, program_id)), // Where program state is stored.
|
||||||
(bob_pubkey, Account::new(1, 0, program_id)), // The payee once M signatures are collected.
|
(bob_pubkey, Account::new(1, 0, program_id)), // The payee once M signatures are collected.
|
||||||
|
@ -282,18 +314,18 @@ mod tests {
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
process(&mut keyed_accounts, &data);
|
process(&mut keyed_accounts, &data);
|
||||||
assert_eq!(keyed_accounts[3].account.tokens, 1);
|
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
||||||
|
|
||||||
let data = format!(r#""{}""#, carol_pubkey).into_bytes();
|
let data = format!(r#""{}""#, carol_pubkey).into_bytes();
|
||||||
process(&mut keyed_accounts, &data);
|
process(&mut keyed_accounts, &data);
|
||||||
assert_eq!(keyed_accounts[3].account.tokens, 1);
|
assert_eq!(keyed_accounts[4].account.tokens, 1);
|
||||||
|
|
||||||
let data = format!(r#""{}""#, dan_pubkey).into_bytes();
|
let data = format!(r#""{}""#, dan_pubkey).into_bytes();
|
||||||
process(&mut keyed_accounts, &data);
|
process(&mut keyed_accounts, &data);
|
||||||
assert_eq!(keyed_accounts[3].account.tokens, 101); // Pay day!
|
assert_eq!(keyed_accounts[4].account.tokens, 101); // Pay day!
|
||||||
|
|
||||||
let data = format!(r#""{}""#, erin_pubkey).into_bytes();
|
let data = format!(r#""{}""#, erin_pubkey).into_bytes();
|
||||||
process(&mut keyed_accounts, &data);
|
process(&mut keyed_accounts, &data);
|
||||||
assert_eq!(keyed_accounts[3].account.tokens, 101); // No change!
|
assert_eq!(keyed_accounts[4].account.tokens, 101); // No change!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
106
src/bank.rs
106
src/bank.rs
|
@ -8,7 +8,7 @@ use bincode::serialize;
|
||||||
use budget_program::BudgetState;
|
use budget_program::BudgetState;
|
||||||
use budget_transaction::BudgetTransaction;
|
use budget_transaction::BudgetTransaction;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use dynamic_program::DynamicProgram;
|
use dynamic_program;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::{hash, Hash};
|
use hash::{hash, Hash};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -100,6 +100,9 @@ pub enum BankError {
|
||||||
|
|
||||||
/// Recoding into PoH failed
|
/// Recoding into PoH failed
|
||||||
RecordFailure,
|
RecordFailure,
|
||||||
|
|
||||||
|
/// Loader call chain too deep
|
||||||
|
CallChainTooDeep,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, BankError>;
|
pub type Result<T> = result::Result<T, BankError>;
|
||||||
|
@ -148,9 +151,6 @@ 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>>,
|
|
||||||
|
|
||||||
// Mapping of account ids to Subscriber ids and sinks to notify on userdata update
|
// Mapping of account ids to Subscriber ids and sinks to notify on userdata update
|
||||||
account_subscriptions: RwLock<HashMap<Pubkey, HashMap<Pubkey, Sink<Account>>>>,
|
account_subscriptions: RwLock<HashMap<Pubkey, HashMap<Pubkey, Sink<Account>>>>,
|
||||||
|
|
||||||
|
@ -176,7 +176,6 @@ 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()),
|
|
||||||
account_subscriptions: RwLock::new(HashMap::new()),
|
account_subscriptions: RwLock::new(HashMap::new()),
|
||||||
signature_subscriptions: RwLock::new(HashMap::new()),
|
signature_subscriptions: RwLock::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
|
@ -431,6 +430,7 @@ impl Bank {
|
||||||
error_counters.duplicate_signature += 1;
|
error_counters.duplicate_signature += 1;
|
||||||
}
|
}
|
||||||
err?;
|
err?;
|
||||||
|
|
||||||
let mut called_accounts: Vec<Account> = tx
|
let mut called_accounts: Vec<Account> = tx
|
||||||
.account_keys
|
.account_keys
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -502,7 +502,7 @@ impl Bank {
|
||||||
&& SystemProgram::check_id(&pre_program_id)))
|
&& SystemProgram::check_id(&pre_program_id)))
|
||||||
{
|
{
|
||||||
//TODO, this maybe redundant bpf should be able to guarantee this property
|
//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.
|
// 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 {
|
if *tx_program_id != account.program_id && pre_tokens > account.tokens {
|
||||||
|
@ -516,32 +516,6 @@ impl Bank {
|
||||||
Ok(())
|
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.
|
/// 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
|
/// 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.
|
/// for the borrow checker to track them with regards to the original set.
|
||||||
|
@ -592,12 +566,7 @@ 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) {
|
||||||
if SystemProgram::process_transaction(
|
if SystemProgram::process_transaction(&tx, instruction_index, program_accounts).is_err()
|
||||||
&tx,
|
|
||||||
instruction_index,
|
|
||||||
program_accounts,
|
|
||||||
&self.loaded_contracts,
|
|
||||||
).is_err()
|
|
||||||
{
|
{
|
||||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||||
}
|
}
|
||||||
|
@ -632,7 +601,57 @@ impl Bank {
|
||||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Verify the transaction
|
||||||
|
@ -666,8 +685,8 @@ impl Bank {
|
||||||
/// This method calls each instruction in the transaction over the set of loaded Accounts
|
/// 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
|
/// The accounts are committed back to the bank only if every instruction succeeds
|
||||||
fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> {
|
fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> {
|
||||||
for (instruction_index, prog) in tx.instructions.iter().enumerate() {
|
for (instruction_index, instruction) in tx.instructions.iter().enumerate() {
|
||||||
Self::with_subset(tx_accounts, &prog.accounts, |program_accounts| {
|
Self::with_subset(tx_accounts, &instruction.accounts, |program_accounts| {
|
||||||
self.execute_instruction(tx, instruction_index, program_accounts)
|
self.execute_instruction(tx, instruction_index, program_accounts)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -713,7 +732,7 @@ impl Bank {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
|
// 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.
|
// 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.
|
// expires.
|
||||||
let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS / 2);
|
let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS / 2);
|
||||||
let process_time = now.elapsed();
|
let process_time = now.elapsed();
|
||||||
|
@ -1306,6 +1325,7 @@ mod tests {
|
||||||
Some(Err(BankError::ResultWithNegativeTokens(1)))
|
Some(Err(BankError::ResultWithNegativeTokens(1)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_one_tx_two_out_atomic_pass() {
|
fn test_one_tx_two_out_atomic_pass() {
|
||||||
let mint = Mint::new(2);
|
let mint = Mint::new(2);
|
||||||
|
@ -1803,7 +1823,7 @@ mod tests {
|
||||||
let string = transport_receiver.poll();
|
let string = transport_receiver.poll();
|
||||||
assert!(string.is_ok());
|
assert!(string.is_ok());
|
||||||
if let Async::Ready(Some(response)) = string.unwrap() {
|
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);
|
assert_eq!(expected, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,23 @@
|
||||||
extern crate elf;
|
use bincode::deserialize;
|
||||||
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 libc;
|
use libc;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use libloading::os::unix::*;
|
use libloading::os::unix::*;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use libloading::os::windows::*;
|
use libloading::os::windows::*;
|
||||||
use result::Result;
|
|
||||||
|
|
||||||
use solana_program_interface::account::KeyedAccount;
|
use solana_program_interface::account::KeyedAccount;
|
||||||
|
use solana_program_interface::loader_instruction::LoaderInstruction;
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
use solana_program_interface::pubkey::Pubkey;
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
/// Dynamic link library prefixs
|
/// Dynamic link library prefixs
|
||||||
const PLATFORM_FILE_PREFIX_BPF: &str = "";
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib";
|
const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib";
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const PLATFORM_FILE_PREFIX_NATIVE: &str = "";
|
const PLATFORM_FILE_PREFIX_NATIVE: &str = "";
|
||||||
|
|
||||||
/// Dynamic link library file extension specific to the platform
|
/// Dynamic link library file extension specific to the platform
|
||||||
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib";
|
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib";
|
||||||
/// Dynamic link library file extension specific to the platform
|
/// Dynamic link library file extension specific to the platform
|
||||||
|
@ -36,241 +27,92 @@ const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so";
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll";
|
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll";
|
||||||
|
|
||||||
/// Section name
|
fn create_path(name: &str) -> PathBuf {
|
||||||
const PLATFORM_SECTION_RS: &str = ".text,entrypoint";
|
|
||||||
const PLATFORM_SECTION_C: &str = ".text.entrypoint";
|
|
||||||
|
|
||||||
pub enum ProgramPath {
|
|
||||||
Bpf,
|
|
||||||
Native,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgramPath {
|
|
||||||
/// Creates a platform-specific file path
|
|
||||||
pub fn create(&self, name: &str) -> PathBuf {
|
|
||||||
let pathbuf = {
|
let pathbuf = {
|
||||||
let current_exe = env::current_exe().unwrap();
|
let current_exe = env::current_exe().unwrap();
|
||||||
PathBuf::from(current_exe.parent().unwrap())
|
PathBuf::from(current_exe.parent().unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
pathbuf.join(match self {
|
pathbuf.join(
|
||||||
ProgramPath::Bpf => PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name)
|
PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.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),
|
.with_extension(PLATFORM_FILE_EXTENSION_NATIVE),
|
||||||
})
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All programs export a symbol named process()
|
pub const NATIVE_PROGRAM_ID: [u8; 32] = [2u8; 32];
|
||||||
|
|
||||||
|
// All native programs export a symbol named process()
|
||||||
const ENTRYPOINT: &str = "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 fn check_id(program_id: &Pubkey) -> bool {
|
||||||
pub enum DynamicProgram {
|
program_id.as_ref() == NATIVE_PROGRAM_ID
|
||||||
/// 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> },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynamicProgram {
|
pub fn id() -> Pubkey {
|
||||||
pub fn new_native(name: String) -> Result<Self> {
|
Pubkey::new(&NATIVE_PROGRAM_ID)
|
||||||
// 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 new_bpf_from_file(name: String) -> Self {
|
pub fn process_transaction(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool {
|
||||||
// create native program
|
if keyed_accounts[0].account.executable {
|
||||||
let path = ProgramPath::Bpf {}.create(&name);
|
// dispatch it
|
||||||
let file = match elf::File::open_path(&path) {
|
let name = keyed_accounts[0].account.userdata.clone();
|
||||||
Ok(f) => f,
|
let name = match str::from_utf8(&name) {
|
||||||
Err(e) => panic!("Error opening ELF {:?}: {:?}", path, e),
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Invalid UTF-8 sequence: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
trace!("Call native {:?}", name);
|
||||||
let text_section = match file.get_section(PLATFORM_SECTION_RS) {
|
{
|
||||||
Some(s) => s,
|
// create native program
|
||||||
None => match file.get_section(PLATFORM_SECTION_C) {
|
let path = create_path(&name);
|
||||||
Some(s) => s,
|
// TODO linux tls bug can cause crash on dlclose(), workaround by never unloading
|
||||||
None => panic!("Failed to find text section"),
|
let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW).unwrap();
|
||||||
},
|
unsafe {
|
||||||
};
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let entrypoint: Symbol<Entrypoint> = match library.get(ENTRYPOINT.as_bytes()) {
|
let entrypoint: Symbol<Entrypoint> = match library.get(ENTRYPOINT.as_bytes()) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => panic!(
|
Err(e) => {
|
||||||
"Unable to find {:?} in program {}: {:?} ",
|
warn!("{:?}: Unable to find {:?} in program", e, ENTRYPOINT);
|
||||||
e, ENTRYPOINT, name
|
return false;
|
||||||
),
|
}
|
||||||
};
|
};
|
||||||
entrypoint(infos, data)
|
return entrypoint(&mut keyed_accounts[1..], tx_data);
|
||||||
},
|
}
|
||||||
DynamicProgram::Bpf { prog, .. } => {
|
}
|
||||||
println!("Instructions: {}", prog.len() / 8);
|
} else if let Ok(instruction) = deserialize(tx_data) {
|
||||||
//DynamicProgram::dump_prog(name, prog);
|
match instruction {
|
||||||
|
LoaderInstruction::Write { offset, bytes } => {
|
||||||
let mut vm = rbpf::EbpfVmRaw::new(prog, Some(bpf_verifier::verifier));
|
trace!("NativeLoader::Write offset {} bytes {:?}", offset, bytes);
|
||||||
|
let offset = offset as usize;
|
||||||
// TODO register more handlers (memcpy for example)
|
if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() {
|
||||||
vm.register_helper(
|
warn!(
|
||||||
rbpf::helpers::BPF_TRACE_PRINTK_IDX,
|
"Error: Overflow, {} > {}",
|
||||||
rbpf::helpers::bpf_trace_printf,
|
offset + bytes.len(),
|
||||||
|
keyed_accounts[0].account.userdata.len()
|
||||||
);
|
);
|
||||||
|
return false;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// native loader takes a name and we assume it all comes in at once
|
||||||
|
keyed_accounts[0].account.userdata = bytes;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
LoaderInstruction::Finalize => {
|
||||||
mod tests {
|
keyed_accounts[0].account.executable = true;
|
||||||
use super::*;
|
keyed_accounts[0].account.loader_program_id = id();
|
||||||
use std::path::Path;
|
keyed_accounts[0].account.program_id = *keyed_accounts[0].key;
|
||||||
|
trace!(
|
||||||
use solana_program_interface::account::Account;
|
"NativeLoader::Finalize prog: {:?} loader {:?}",
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
keyed_accounts[0].account.program_id,
|
||||||
|
keyed_accounts[0].account.loader_program_id
|
||||||
#[test]
|
);
|
||||||
fn test_path_create_native() {
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
#[test]
|
warn!("Invalid program transaction: {:?}", tx_data);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
false
|
||||||
|
|
||||||
// TODO add more tests to validate the Userdata and Account data is
|
|
||||||
// moving across the boundary correctly
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub mod fullnode;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
pub mod leader_scheduler;
|
pub mod leader_scheduler;
|
||||||
pub mod ledger;
|
pub mod ledger;
|
||||||
|
pub mod loader_transaction;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod metrics;
|
pub mod metrics;
|
||||||
pub mod mint;
|
pub mod mint;
|
||||||
|
|
|
@ -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":{
|
"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],
|
"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,
|
"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}
|
"id":1}
|
||||||
"#;
|
"#;
|
||||||
|
|
|
@ -390,6 +390,8 @@ mod tests {
|
||||||
let contract_funds = Keypair::new();
|
let contract_funds = Keypair::new();
|
||||||
let contract_state = Keypair::new();
|
let contract_state = Keypair::new();
|
||||||
let budget_program_id = BudgetState::id();
|
let budget_program_id = BudgetState::id();
|
||||||
|
let loader_program_id = Pubkey::default(); // TODO
|
||||||
|
let executable = false; // TODO
|
||||||
let bank = Bank::new(&alice);
|
let bank = Bank::new(&alice);
|
||||||
let arc_bank = Arc::new(bank);
|
let arc_bank = Arc::new(bank);
|
||||||
let last_id = arc_bank.last_id();
|
let last_id = arc_bank.last_id();
|
||||||
|
@ -474,7 +476,10 @@ mod tests {
|
||||||
"result": {
|
"result": {
|
||||||
"program_id": budget_program_id,
|
"program_id": budget_program_id,
|
||||||
"tokens": 1,
|
"tokens": 1,
|
||||||
"userdata": expected_userdata
|
"userdata": expected_userdata,
|
||||||
|
"executable": executable,
|
||||||
|
"loader_program_id": loader_program_id,
|
||||||
|
|
||||||
},
|
},
|
||||||
"subscription": 0,
|
"subscription": 0,
|
||||||
}
|
}
|
||||||
|
@ -512,7 +517,9 @@ mod tests {
|
||||||
"result": {
|
"result": {
|
||||||
"program_id": budget_program_id,
|
"program_id": budget_program_id,
|
||||||
"tokens": 51,
|
"tokens": 51,
|
||||||
"userdata": expected_userdata
|
"userdata": expected_userdata,
|
||||||
|
"executable": executable,
|
||||||
|
"loader_program_id": loader_program_id,
|
||||||
},
|
},
|
||||||
"subscription": 0,
|
"subscription": 0,
|
||||||
}
|
}
|
||||||
|
@ -549,7 +556,9 @@ mod tests {
|
||||||
"result": {
|
"result": {
|
||||||
"program_id": budget_program_id,
|
"program_id": budget_program_id,
|
||||||
"tokens": 1,
|
"tokens": 1,
|
||||||
"userdata": expected_userdata
|
"userdata": expected_userdata,
|
||||||
|
"executable": executable,
|
||||||
|
"loader_program_id": loader_program_id,
|
||||||
},
|
},
|
||||||
"subscription": 0,
|
"subscription": 0,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
//! system program
|
//! system program
|
||||||
|
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use dynamic_program::DynamicProgram;
|
|
||||||
use solana_program_interface::account::Account;
|
use solana_program_interface::account::Account;
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
use solana_program_interface::pubkey::Pubkey;
|
||||||
use std;
|
use std;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -42,10 +39,6 @@ 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];
|
||||||
|
@ -65,7 +58,6 @@ impl SystemProgram {
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
pix: usize,
|
pix: usize,
|
||||||
accounts: &mut [&mut Account],
|
accounts: &mut [&mut Account],
|
||||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Ok(syscall) = deserialize(tx.userdata(pix)) {
|
if let Ok(syscall) = deserialize(tx.userdata(pix)) {
|
||||||
trace!("process_transaction: {:?}", syscall);
|
trace!("process_transaction: {:?}", syscall);
|
||||||
|
@ -88,6 +80,8 @@ impl SystemProgram {
|
||||||
accounts[1].tokens += tokens;
|
accounts[1].tokens += tokens;
|
||||||
accounts[1].program_id = program_id;
|
accounts[1].program_id = program_id;
|
||||||
accounts[1].userdata = vec![0; space as usize];
|
accounts[1].userdata = vec![0; space as usize];
|
||||||
|
accounts[1].executable = false;
|
||||||
|
accounts[1].loader_program_id = Pubkey::default();
|
||||||
}
|
}
|
||||||
SystemProgram::Assign { program_id } => {
|
SystemProgram::Assign { program_id } => {
|
||||||
if !Self::check_id(&accounts[0].program_id) {
|
if !Self::check_id(&accounts[0].program_id) {
|
||||||
|
@ -100,16 +94,6 @@ 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_native(name).map_err(|err| {
|
|
||||||
warn!("SystemProgram::Load failure: {:?}", err);
|
|
||||||
Error::InvalidArgument
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,19 +109,13 @@ mod test {
|
||||||
use signature::{Keypair, KeypairUtil};
|
use signature::{Keypair, KeypairUtil};
|
||||||
use solana_program_interface::account::Account;
|
use solana_program_interface::account::Account;
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
use solana_program_interface::pubkey::Pubkey;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use system_program::SystemProgram;
|
use system_program::SystemProgram;
|
||||||
use system_transaction::SystemTransaction;
|
use system_transaction::SystemTransaction;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
|
||||||
fn process_transaction(
|
fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
||||||
tx: &Transaction,
|
|
||||||
accounts: &mut [Account],
|
|
||||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
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]
|
#[test]
|
||||||
|
@ -146,8 +124,7 @@ mod test {
|
||||||
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());
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -158,12 +135,13 @@ 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());
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn test_create_spend_wrong_source() {
|
fn test_create_spend_wrong_source() {
|
||||||
let from = Keypair::new();
|
let from = Keypair::new();
|
||||||
let to = Keypair::new();
|
let to = Keypair::new();
|
||||||
|
@ -171,8 +149,7 @@ 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());
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
|
||||||
assert_eq!(accounts[0].tokens, 1);
|
assert_eq!(accounts[0].tokens, 1);
|
||||||
assert_eq!(accounts[1].tokens, 0);
|
assert_eq!(accounts[1].tokens, 0);
|
||||||
}
|
}
|
||||||
|
@ -183,8 +160,7 @@ 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);
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
|
||||||
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());
|
||||||
|
@ -204,8 +180,7 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -223,8 +198,7 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
|
||||||
assert!(accounts[1].userdata.is_empty());
|
assert!(accounts[1].userdata.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -242,8 +216,7 @@ mod test {
|
||||||
Pubkey::default(),
|
Pubkey::default(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let hash = RwLock::new(HashMap::new());
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
||||||
assert!(process_transaction(&tx, &mut accounts, &hash).is_err());
|
|
||||||
assert_eq!(accounts[1].userdata.len(), 3);
|
assert_eq!(accounts[1].userdata.len(), 3);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -252,8 +225,7 @@ mod test {
|
||||||
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);
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
|
||||||
assert_eq!(accounts[0].program_id, program.pubkey());
|
assert_eq!(accounts[0].program_id, program.pubkey());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -263,8 +235,7 @@ 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());
|
||||||
let hash = RwLock::new(HashMap::new());
|
process_transaction(&tx, &mut accounts).unwrap();
|
||||||
process_transaction(&tx, &mut accounts, &hash).unwrap();
|
|
||||||
assert_eq!(accounts[0].tokens, 0);
|
assert_eq!(accounts[0].tokens, 0);
|
||||||
assert_eq!(accounts[1].tokens, 1);
|
assert_eq!(accounts[1].tokens, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,6 @@ pub trait SystemTransaction {
|
||||||
fee: i64,
|
fee: i64,
|
||||||
) -> Self;
|
) -> Self;
|
||||||
|
|
||||||
fn system_load(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
last_id: Hash,
|
|
||||||
fee: i64,
|
|
||||||
program_id: Pubkey,
|
|
||||||
name: String,
|
|
||||||
) -> Self;
|
|
||||||
fn system_move_many(
|
fn system_move_many(
|
||||||
from_keypair: &Keypair,
|
from_keypair: &Keypair,
|
||||||
moves: &[(Pubkey, i64)],
|
moves: &[(Pubkey, i64)],
|
||||||
|
@ -107,25 +100,7 @@ impl SystemTransaction for Transaction {
|
||||||
fee,
|
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 {
|
fn system_move_many(from: &Keypair, moves: &[(Pubkey, i64)], last_id: Hash, fee: i64) -> Self {
|
||||||
let instructions: Vec<_> = moves
|
let instructions: Vec<_> = moves
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1,347 +1,252 @@
|
||||||
extern crate bincode;
|
extern crate bincode;
|
||||||
|
extern crate elf;
|
||||||
extern crate solana;
|
extern crate solana;
|
||||||
extern crate solana_program_interface;
|
extern crate solana_program_interface;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
#[cfg(feature = "bpf_c")]
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
|
use solana::bank::Bank;
|
||||||
use solana::dynamic_program::DynamicProgram;
|
use solana::dynamic_program;
|
||||||
#[cfg(feature = "bpf_c")]
|
use solana::loader_transaction::LoaderTransaction;
|
||||||
use solana::dynamic_program::ProgramPath;
|
use solana::logger;
|
||||||
use solana::hash::Hash;
|
use solana::mint::Mint;
|
||||||
use solana::signature::{Keypair, KeypairUtil};
|
use solana::signature::{Keypair, KeypairUtil};
|
||||||
use solana::system_program::SystemProgram;
|
|
||||||
use solana::system_transaction::SystemTransaction;
|
use solana::system_transaction::SystemTransaction;
|
||||||
use solana::transaction::Transaction;
|
use solana::transaction::Transaction;
|
||||||
use solana_program_interface::account::{Account, KeyedAccount};
|
|
||||||
use solana_program_interface::pubkey::Pubkey;
|
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
// TODO test modified user data
|
||||||
use solana::tictactoe_program::Command;
|
// TODO test failure if account tokens decrease but not assigned to program
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
fn check_tx_results(bank: &Bank, tx: &Transaction, result: Vec<solana::bank::Result<()>>) {
|
||||||
#[test]
|
assert_eq!(result.len(), 1);
|
||||||
fn test_path_create_bpf() {
|
assert_eq!(result[0], Ok(()));
|
||||||
let path = ProgramPath::Bpf {}.create("move_funds_c");
|
assert_eq!(bank.get_signature(&tx.last_id, &tx.signature), Some(Ok(())));
|
||||||
assert_eq!(true, Path::new(&path).exists());
|
|
||||||
let path = ProgramPath::Bpf {}.create("tictactoe_c");
|
|
||||||
assert_eq!(true, Path::new(&path).exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_bpf_file_noop_rust() {
|
|
||||||
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_file("noop_rust".to_string());
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
|
||||||
#[test]
|
|
||||||
fn test_bpf_file_move_funds_c() {
|
|
||||||
let data: Vec<u8> = vec![0xa, 0xb, 0xc, 0xd, 0xe, 0xf];
|
|
||||||
let keys = vec![Pubkey::new(&[0xAA; 32]), Pubkey::new(&[0xBB; 32])];
|
|
||||||
let mut accounts = vec![
|
|
||||||
Account::new(0x0123456789abcdef, 4, Pubkey::default()),
|
|
||||||
Account::new(1, 8, Pubkey::default()),
|
|
||||||
];
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut infos: Vec<_> = (&keys)
|
|
||||||
.into_iter()
|
|
||||||
.zip(&mut accounts)
|
|
||||||
.map(|(key, account)| KeyedAccount { key, account })
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let dp = DynamicProgram::new_bpf_from_file("move_funds_c".to_string());
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
|
||||||
fn tictactoe_command(command: Command, accounts: &mut Vec<Account>, player: Pubkey) {
|
|
||||||
let p = &command as *const Command as *const u8;
|
|
||||||
let data: &[u8] = unsafe { std::slice::from_raw_parts(p, std::mem::size_of::<Command>()) };
|
|
||||||
|
|
||||||
// Init
|
|
||||||
// player_x pub key in keys[2]
|
|
||||||
// accounts[0].program_id must be tictactoe
|
|
||||||
// accounts[1].userdata must be tictactoe game state
|
|
||||||
|
|
||||||
let keys = vec![player, Pubkey::default(), player];
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut infos: Vec<_> = (&keys)
|
|
||||||
.into_iter()
|
|
||||||
.zip(&mut *accounts)
|
|
||||||
.map(|(key, account)| KeyedAccount { key, account })
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let dp = DynamicProgram::new_bpf_from_file("tictactoe_c".to_string());
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bpf_c")]
|
|
||||||
#[test]
|
|
||||||
fn test_bpf_file_tictactoe_c() {
|
|
||||||
let game_size = 0x78; // corresponds to the C structure size
|
|
||||||
let mut accounts = vec![
|
|
||||||
Account::new(0, 0, Pubkey::default()),
|
|
||||||
Account::new(0, game_size, Pubkey::default()),
|
|
||||||
Account::new(0, 0, Pubkey::default()),
|
|
||||||
];
|
|
||||||
|
|
||||||
tictactoe_command(Command::Init, &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(
|
|
||||||
Command::Join(0xAABBCCDD),
|
|
||||||
&mut accounts,
|
|
||||||
Pubkey::new(&[0xA; 32]),
|
|
||||||
);
|
|
||||||
tictactoe_command(Command::Move(1, 1), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(Command::Move(0, 0), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(Command::Move(2, 0), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(Command::Move(0, 2), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(Command::Move(2, 2), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
tictactoe_command(Command::Move(0, 1), &mut accounts, Pubkey::new(&[0xA; 32]));
|
|
||||||
|
|
||||||
// validate test
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_native_file_noop() {
|
fn test_transaction_load_native() {
|
||||||
let data: Vec<u8> = vec![0];
|
logger::setup();
|
||||||
let keys = vec![Pubkey::default(); 2];
|
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
|
||||||
accounts[0].tokens = 100;
|
|
||||||
accounts[1].tokens = 1;
|
|
||||||
|
|
||||||
{
|
let mint = Mint::new(50);
|
||||||
let mut infos: Vec<_> = (&keys)
|
// TODO in a test like this how should the last_id be incremented, as used here it is always the same
|
||||||
.into_iter()
|
// which leads to duplicate tx signature errors
|
||||||
.zip(&mut accounts)
|
let bank = Bank::new(&mint);
|
||||||
.map(|(key, account)| KeyedAccount { key, account })
|
let program = Keypair::new();
|
||||||
.collect();
|
|
||||||
|
|
||||||
let dp = DynamicProgram::new_native("noop".to_string()).unwrap();
|
// allocate, populate, finalize user program
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
let tx = Transaction::system_create(
|
||||||
fn test_native_file_move_funds_success() {
|
&mint.keypair(),
|
||||||
let tokens: i64 = 100;
|
program.pubkey(),
|
||||||
let data: Vec<u8> = serialize(&tokens).unwrap();
|
mint.last_id(),
|
||||||
let keys = vec![Pubkey::default(); 2];
|
1,
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
56, // TODO How does the user know how much space to allocate, this is really an internally known size
|
||||||
accounts[0].tokens = 100;
|
dynamic_program::id(),
|
||||||
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_native("move_funds".to_string()).unwrap();
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
assert_eq!(0, accounts[0].tokens);
|
|
||||||
assert_eq!(101, accounts[1].tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_native_file_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_native("move_funds".to_string()).unwrap();
|
|
||||||
assert!(!dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
assert_eq!(10, accounts[0].tokens);
|
|
||||||
assert_eq!(1, accounts[1].tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_program_native_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_native("move_funds".to_string()).unwrap();
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
|
||||||
}
|
|
||||||
assert_eq!(0, accounts[0].tokens);
|
|
||||||
assert_eq!(101, accounts[1].tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for thread in threads {
|
|
||||||
thread.join().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn process_transaction(
|
|
||||||
tx: &Transaction,
|
|
||||||
accounts: &mut [Account],
|
|
||||||
loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>,
|
|
||||||
) {
|
|
||||||
let mut refs: Vec<&mut Account> = accounts.iter_mut().collect();
|
|
||||||
SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_system_program_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,
|
0,
|
||||||
program_id,
|
|
||||||
"move_funds".to_string(),
|
|
||||||
);
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
process_transaction(&tx, &mut accounts, &loaded_programs);
|
println!("id: {:?}", dynamic_program::id());
|
||||||
}
|
let name = String::from("noop");
|
||||||
// then call the program
|
let tx = Transaction::write(
|
||||||
{
|
&program,
|
||||||
let program_id = Pubkey::default(); // same program id for both
|
dynamic_program::id(),
|
||||||
let keys = vec![Pubkey::default(), Pubkey::default()];
|
0,
|
||||||
let mut accounts = vec![Account::default(), Account::default()];
|
name.as_bytes().to_vec(),
|
||||||
accounts[0].tokens = 100;
|
mint.last_id(),
|
||||||
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();
|
|
||||||
|
|
||||||
assert!(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_system_program_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,
|
0,
|
||||||
program_id,
|
|
||||||
"move_funds".to_string(),
|
|
||||||
);
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
println!("id after: {:?}", dynamic_program::id());
|
||||||
|
|
||||||
process_transaction(&tx, &mut accounts, &loaded_programs);
|
let tx = Transaction::finalize(&program, dynamic_program::id(), mint.last_id(), 0);
|
||||||
}
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
// 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();
|
|
||||||
|
|
||||||
assert!(dp.call(&mut infos, &data));
|
// Call user program
|
||||||
}
|
|
||||||
None => panic!("failed to find program in hash"),
|
let tx = Transaction::new(
|
||||||
}
|
&mint.keypair(), // TODO
|
||||||
}
|
&[],
|
||||||
assert_eq!(0, accounts[0].tokens);
|
program.pubkey(),
|
||||||
assert_eq!(101, accounts[1].tokens);
|
vec![1u8],
|
||||||
}
|
mint.last_id(),
|
||||||
}
|
0,
|
||||||
}));
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for thread in threads {
|
#[test]
|
||||||
thread.join().unwrap();
|
fn test_transaction_load_lua() {
|
||||||
|
logger::setup();
|
||||||
|
|
||||||
|
let mint = Mint::new(50);
|
||||||
|
// TODO in a test like this how should the last_id be incremented, as used here it is always the same
|
||||||
|
// which leads to duplicate tx signature errors
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
let loader = Keypair::new();
|
||||||
|
let program = Keypair::new();
|
||||||
|
let from = Keypair::new();
|
||||||
|
let to = Keypair::new().pubkey();
|
||||||
|
|
||||||
|
// allocate, populate, and finalize Lua loader
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
loader.pubkey(),
|
||||||
|
mint.last_id(),
|
||||||
|
1,
|
||||||
|
56, // TODO How does the user know how much space to allocate for what should be an internally known size
|
||||||
|
dynamic_program::id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let name = String::from("solua");
|
||||||
|
let tx = Transaction::write(
|
||||||
|
&loader,
|
||||||
|
dynamic_program::id(),
|
||||||
|
0,
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::finalize(&loader, dynamic_program::id(), mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
// allocate, populate, and finalize user program
|
||||||
|
|
||||||
|
let bytes = r#"
|
||||||
|
print("Lua Script!")
|
||||||
|
local tokens, _ = string.unpack("I", data)
|
||||||
|
accounts[1].tokens = accounts[1].tokens - tokens
|
||||||
|
accounts[2].tokens = accounts[2].tokens + tokens
|
||||||
|
"#.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
program.pubkey(),
|
||||||
|
mint.last_id(),
|
||||||
|
1,
|
||||||
|
300, // TODO How does the user know how much space to allocate for what should be an internally known size
|
||||||
|
loader.pubkey(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::write(&program, loader.pubkey(), 0, bytes, mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::finalize(&program, loader.pubkey(), mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
// Call user program with two accounts
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
from.pubkey(),
|
||||||
|
mint.last_id(),
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
program.pubkey(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
to,
|
||||||
|
mint.last_id(),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
program.pubkey(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let data = serialize(&10).unwrap();
|
||||||
|
let tx = Transaction::new(&from, &[to], program.pubkey(), data, mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
assert_eq!(bank.get_balance(&from.pubkey()), 0);
|
||||||
|
assert_eq!(bank.get_balance(&to), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bpf_c")]
|
||||||
|
#[test]
|
||||||
|
fn test_transaction_load_bpf() {
|
||||||
|
logger::setup();
|
||||||
|
|
||||||
|
let mint = Mint::new(50);
|
||||||
|
// TODO in a test like this how should the last_id be incremented, as used here it is always the same
|
||||||
|
// which leads to duplicate tx signature errors
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
let loader = Keypair::new();
|
||||||
|
let program = Keypair::new();
|
||||||
|
|
||||||
|
// allocate, populate, finalize BPF loader
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
loader.pubkey(),
|
||||||
|
mint.last_id(),
|
||||||
|
1,
|
||||||
|
56, // TODO How does the user know how much space to allocate for what should be an internally known size
|
||||||
|
dynamic_program::id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let name = String::from("sobpf");
|
||||||
|
let tx = Transaction::write(
|
||||||
|
&loader,
|
||||||
|
dynamic_program::id(),
|
||||||
|
0,
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::finalize(&loader, dynamic_program::id(), mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
// allocate, populate, and finalize user program
|
||||||
|
|
||||||
|
let tx = Transaction::system_create(
|
||||||
|
&mint.keypair(),
|
||||||
|
program.pubkey(),
|
||||||
|
mint.last_id(),
|
||||||
|
1,
|
||||||
|
56, // TODO How does the user know how much space to allocate for what should be an internally known size
|
||||||
|
loader.pubkey(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let name = String::from("noop_c");
|
||||||
|
let tx = Transaction::write(
|
||||||
|
&program,
|
||||||
|
loader.pubkey(),
|
||||||
|
0,
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
let tx = Transaction::finalize(&program, loader.pubkey(), mint.last_id(), 0);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
|
|
||||||
|
// Call user program
|
||||||
|
|
||||||
|
let tx = Transaction::new(
|
||||||
|
&mint.keypair(), // TODO
|
||||||
|
&[],
|
||||||
|
program.pubkey(),
|
||||||
|
vec![1u8],
|
||||||
|
mint.last_id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()]));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue