Add BPF support & C-based BPF tic-tac-toe (#1422)

Add initial support for BPF and a C port of tictactoe
This commit is contained in:
jackcmay 2018-10-04 09:44:44 -07:00 committed by GitHub
parent 74b63c12a0
commit 13d4443d4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1321 additions and 124 deletions

View File

@ -66,12 +66,13 @@ path = "src/bin/wallet.rs"
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
[features]
unstable = []
ipv6 = []
bpf_c = []
chacha = []
cuda = []
erasure = []
ipv6 = []
test = []
chacha = []
unstable = []
[dependencies]
atty = "0.2"
@ -82,6 +83,7 @@ bytes = "0.4"
chrono = { version = "0.4.0", features = ["serde"] }
clap = "2.31"
dirs = "1.0.2"
elf = "0.0.10"
env_logger = "0.5.12"
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
getopts = "0.2"
@ -98,6 +100,7 @@ matches = "0.1.6"
nix = "0.11.0"
pnet_datalink = "0.21.0"
rand = "0.5.1"
rbpf = { git = "https://github.com/solana-labs/rbpf" }
rayon = "1.0.0"
reqwest = "0.9.0"
ring = "0.13.2"
@ -114,9 +117,8 @@ tokio-codec = "0.1"
untrusted = "0.6.2"
[dev-dependencies]
noop = { path = "programs/noop" }
print = { path = "programs/print" }
move_funds = { path = "programs/move_funds" }
move_funds = { path = "programs/native/move_funds" }
noop = { path = "programs/native/noop" }
[[bench]]
name = "bank"
@ -141,8 +143,7 @@ name = "chacha"
members = [
".",
"common",
"programs/noop",
"programs/print",
"programs/move_funds",
"programs/native/move_funds",
"programs/native/noop",
"programs/bpf/noop_rust",
]

View File

@ -1,5 +1,6 @@
use std::env;
use std::fs;
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=target/perf-libs");
@ -14,11 +15,33 @@ fn main() {
}
});
let bpf_c = !env::var("CARGO_FEATURE_BPF_C").is_err();
let chacha = !env::var("CARGO_FEATURE_CHACHA").is_err();
let cuda = !env::var("CARGO_FEATURE_CUDA").is_err();
let erasure = !env::var("CARGO_FEATURE_ERASURE").is_err();
let chacha = !env::var("CARGO_FEATURE_CHACHA").is_err();
if cuda || erasure || chacha {
if bpf_c {
let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap();
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:warning=(not a warning) Compiling move_funds_c");
let status = Command::new("programs/bpf/move_funds_c/build.sh")
.arg(&out_dir)
.status()
.expect("Failed to call move_funds_c build script");
assert!(status.success());
println!("cargo:rerun-if-changed=programs/bpf/tictactoe_c/build.sh");
println!("cargo:rerun-if-changed=programs/bpf/tictactoe_c/src/tictactoe.c");
println!("cargo:warning=(not a warning) Compiling tictactoe_c");
let status = Command::new("programs/bpf/tictactoe_c/build.sh")
.arg(&out_dir)
.status()
.expect("Failed to call tictactoe_c build script");
assert!(status.success());
}
if chacha || cuda || erasure {
println!("cargo:rustc-link-search=native=target/perf-libs");
}
if cuda {

View File

@ -1,6 +1,7 @@
use pubkey::Pubkey;
/// An Account with userdata that is stored on chain
#[repr(C)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Account {
/// tokens in the account
@ -22,6 +23,7 @@ impl Account {
}
}
#[repr(C)]
#[derive(Debug)]
pub struct KeyedAccount<'a> {
pub key: &'a Pubkey,

View File

@ -3,6 +3,7 @@ use generic_array::typenum::U32;
use generic_array::GenericArray;
use std::fmt;
#[repr(C)]
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Pubkey(GenericArray<u8, U32>);

View File

@ -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"/move_funds_c.bc -c "$THISDIR"/src/move_funds.c
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/move_funds_c.o "$OUTDIR"/move_funds_c.bc
#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/move_funds_c.o

View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/move_funds_c.o

View File

@ -0,0 +1,130 @@
//#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 -1;
}
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 = *(uint64_t *)src;
src += sizeof(uint64_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 0;
}
// -- 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);
}

View File

@ -0,0 +1,8 @@
[package]
name = "noop_rust"
version = "0.1.0"
authors = ["Jack May <jack@solana.com>"]
[dependencies]
rbpf = { git = "https://github.com/solana-labs/rbpf" }
solana = { path = "../../.." }

10
programs/bpf/noop_rust/build.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash -ex
# TODO building release flavor with rust produces a bunch of output .bc files
INTERDIR=../../../target/release
OUTDIR="${1:-../../../target/debug/}"
mkdir -p "$OUTDIR"
# cargo +nightly rustc --release -- -C panic=abort --emit=llvm-ir
cargo +nightly rustc --release -- -C panic=abort --emit=llvm-bc
cp "$INTERDIR"/deps/noop_rust-*.bc "$OUTDIR"/noop_rust.bc
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -o "$OUTDIR"/noop_rust.o "$OUTDIR"/noop_rust.bc

3
programs/bpf/noop_rust/dump.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble target/release/noop_rust.o

View File

@ -0,0 +1,15 @@
extern crate rbpf;
use std::mem::transmute;
#[no_mangle]
#[link_section = ".text,entrypoint"] // TODO platform independent needed
pub extern "C" fn entrypoint(_raw: *mut u8) {
let bpf_func_trace_printk = unsafe {
transmute::<u64, extern "C" fn(u64, u64, u64, u64, u64)>(
rbpf::helpers::BPF_TRACE_PRINTK_IDX as u64,
)
};
bpf_func_trace_printk(0, 0, 1, 2, 3);
}

View File

@ -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"/tictactoe_c.bc -c "$THISDIR"/src/tictactoe.c
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/tictactoe_c.o "$OUTDIR"/tictactoe_c.bc
# /usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/tictactoe_c.o

View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/tictactoe_c.o

View File

@ -0,0 +1,57 @@
# Notes:
# TODO needs more investigation into what r10 is used for
# -O2 adds changes the generated code from:
# main:
# 0: b7 01 00 00 00 00 00 00 r1 = 0
# 1: 63 1a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r1
# 2: bf 10 00 00 00 00 00 00 r0 = r1
# 3: 95 00 00 00 00 00 00 00 exit
# To:
# main:
# 0: b7 00 00 00 00 00 00 00 r0 = 0
# 1: 95 00 00 00 00 00 00 00 exit
# - building bpf module that includes stdint.h with clang only (no w/ llc) results in an error
# $(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
# => n file included from /usr/include/sys/_types/_intptr_t.h:30:
# => /usr/include/machine/types.h:37:2: error: architecture not supported
# => error architecture not supported
TOOLS_DIR = /usr/local/opt/llvm/bin
test_x86.o: test.c
$(TOOLS_DIR)/clang -O2 -fpie -target x86_64 -o $@ -c $<
test_x86.so: test_x86.o
$(TOOLS_DIR)/ld.lld --shared -o $@ $<
test_x86_dylib.o: test.c
$(TOOLS_DIR)/clang -O2 -fpie -target x86_64-apple-darwin13.0.0 -o $@ -c $<
test_x86.dylib: test_x86_dylib.o
/usr/local/opt/llvm/bin/ld64.lld -dylib -lc -arch x86_64 -o $@ $<
# TODO does not work if pulling in stdlib, claims unsupported architecture
bpf_clang.o: bpf.c
$(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
bpf.o: bpf.c
$(TOOLS_DIR)/clang -O2 -emit-llvm -c $< -o - | $(TOOLS_DIR)/llc -march=bpf -filetype=obj -o $@
bpf_rust.o: bpf_rust.rs
rustc +nightly -C opt-level=2 -C panic=abort --emit llvm-bc $<
$(TOOLS_DIR)/llc -march=bpf -filetype=obj -function-sections -o $@ bpf_rust.bc
dumpall:
$(TOOLS_DIR)/llvm-objdump -color -source -disassemble *.o
cleanall:
rm -f *.o
rm -f *.so
rm -f *.dylib
rm -f *.ll
rm -rf *.bc
all: bpf_clang.o bpf.o

View File

@ -0,0 +1,370 @@
//#include <stdint.h>
//#include <stddef.h>
#if 1
#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 -1;
}
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 = *(uint64_t *)src;
src += sizeof(uint64_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 0;
}
// -- TicTacToe --
// Board Coodinates
// | 0,0 | 1,0 | 2,0 |
// | 0,1 | 1,1 | 2,1 |
// | 0,2 | 1,2 | 2,2 |
typedef enum {
Result_Ok,
Result_Panic,
Result_GameInProgress,
Result_InvalidArguments,
Result_InvalidMove,
Result_InvalidUserdata,
Result_InvalidTimestamp,
Result_NoGame,
Result_NotYourTurn,
Result_PlayerNotFound,
Result_UserdataTooSmall,
} Result;
typedef enum { BoardItem_F, BoardItem_X, BoardItem_O } BoardItem;
typedef enum {
State_Waiting,
State_XMove,
State_OMove,
State_XWon,
State_OWon,
State_Draw,
} State;
typedef struct {
SolPubkey player_x;
SolPubkey player_o;
State state;
BoardItem board[9];
int64_t keep_alive[2];
} Game;
typedef enum {
Command_Init = 0,
Command_Join,
Command_KeepAlive,
Command_Move,
} Command;
SOL_FN_PREFIX void game_dump_board(Game *self) {
sol_print(0, 0, 0x9, 0x9, 0x9);
sol_print(0, 0, self->board[0], self->board[1], self->board[2]);
sol_print(0, 0, self->board[3], self->board[4], self->board[5]);
sol_print(0, 0, self->board[6], self->board[7], self->board[8]);
sol_print(0, 0, 0x9, 0x9, 0x9);
}
SOL_FN_PREFIX void game_create(Game *self, SolPubkey *player_x) {
sol_memcpy(self->player_x.x, player_x, SIZE_PUBKEY);
// TODO self->player_o = 0;
self->state = State_Waiting;
self->keep_alive[0] = 0;
self->keep_alive[1] = 0;
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < 9; i++) {
self->board[i] = BoardItem_F;
}
}
SOL_FN_PREFIX Result game_join(Game *self, SolPubkey *player_o,
int64_t timestamp) {
if (self->state == State_Waiting) {
sol_memcpy(self->player_o.x, player_o, SIZE_PUBKEY);
self->state = State_XMove;
if (timestamp <= self->keep_alive[1]) {
return Result_InvalidTimestamp;
} else {
self->keep_alive[1] = timestamp;
return Result_Ok;
}
}
return Result_GameInProgress;
}
SOL_FN_PREFIX bool game_same(BoardItem x_or_o, BoardItem one, BoardItem two,
BoardItem three) {
if (x_or_o == one && x_or_o == two && x_or_o == three) {
return true;
}
return false;
}
SOL_FN_PREFIX bool game_same_player(SolPubkey *one, SolPubkey *two) {
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < SIZE_PUBKEY; i++) {
if (one->x[i] != two->x[i]) {
return false;
}
}
return true;
}
SOL_FN_PREFIX Result game_next_move(Game *self, SolPubkey *player, int x, int y) {
int board_index = y * 3 + x;
if (board_index >= 9 || self->board[board_index] != BoardItem_F) {
return Result_InvalidMove;
}
BoardItem x_or_o;
State won_state;
switch (self->state) {
case State_XMove:
if (!game_same_player(player, &self->player_x)) {
return Result_PlayerNotFound;
}
self->state = State_OMove;
x_or_o = BoardItem_X;
won_state = State_XWon;
break;
case State_OMove:
if (!game_same_player(player, &self->player_o)) {
return Result_PlayerNotFound;
}
self->state = State_XMove;
x_or_o = BoardItem_O;
won_state = State_OWon;
break;
default:
return Result_NotYourTurn;
}
self->board[board_index] = x_or_o;
// game_dump_board(self);
bool winner =
// Check rows
game_same(x_or_o, self->board[0], self->board[1], self->board[2]) ||
game_same(x_or_o, self->board[3], self->board[4], self->board[5]) ||
game_same(x_or_o, self->board[6], self->board[7], self->board[8]) ||
// Check columns
game_same(x_or_o, self->board[0], self->board[3], self->board[6]) ||
game_same(x_or_o, self->board[1], self->board[4], self->board[7]) ||
game_same(x_or_o, self->board[2], self->board[5], self->board[8]) ||
// Check both diagonals
game_same(x_or_o, self->board[0], self->board[4], self->board[8]) ||
game_same(x_or_o, self->board[2], self->board[4], self->board[6]);
if (winner) {
self->state = won_state;
}
{
int draw = true;
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < 9; i++) {
if (BoardItem_F == self->board[i]) {
draw = false;
break;
}
}
if (draw) {
self->state = State_Draw;
}
}
return Result_Ok;
}
SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player,
int64_t timestamp) {
switch (self->state) {
case State_Waiting:
case State_XMove:
case State_OMove:
if (game_same_player(player, &self->player_x)) {
if (timestamp <= self->keep_alive[0]) {
return Result_InvalidTimestamp;
}
self->keep_alive[0] = timestamp;
} else if (game_same_player(player, &self->player_o)) {
if (timestamp <= self->keep_alive[1]) {
return Result_InvalidTimestamp;
}
self->keep_alive[1] = timestamp;
} else {
return Result_PlayerNotFound;
}
break;
default:
break;
}
return Result_Ok;
}
void entrypoint(uint8_t *buf) {
SolKeyedAccounts ka[3];
uint64_t userdata_len;
uint8_t *userdata;
int err = 0;
if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) {
sol_panic();
}
if (sizeof(Game) > ka[1].userdata_len) {
sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len);
sol_panic();
}
Game game;
sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len);
Command command = *userdata;
sol_print(0, 0, 0, 0, command);
switch (command) {
case Command_Init:
game_create(&game, ka[2].key);
break;
case Command_Join:
err = game_join(&game, ka[0].key, userdata[8]);
break;
case Command_KeepAlive:
err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0);
break;
case Command_Move:
err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]);
break;
default:
sol_panic();
}
sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len);
sol_print(0, 0, 0, err, game.state);
}
// // -- 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);
// }

View File

@ -15,7 +15,7 @@ authors = [
[dependencies]
bincode = "1.0.0"
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
solana_program_interface = { path = "../../common" }
solana_program_interface = { path = "../../../common" }
[lib]
name = "move_funds"

View File

@ -13,7 +13,7 @@ authors = [
]
[dependencies]
solana_program_interface = { path = "../../common" }
solana_program_interface = { path = "../../../common" }
[lib]
name = "noop"

View File

@ -3,4 +3,7 @@ extern crate solana_program_interface;
use solana_program_interface::account::KeyedAccount;
#[no_mangle]
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {}
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
//println!("AccountInfos: {:#?}", _infos);
//println!("data: {:#?}", data);
}

View File

@ -1,21 +0,0 @@
[package]
name = "print"
version = "0.1.0"
authors = [
"Anatoly Yakovenko <anatoly@solana.com>",
"Greg Fitzgerald <greg@solana.com>",
"Stephen Akridge <stephen@solana.com>",
"Michael Vines <mvines@solana.com>",
"Rob Walker <rob@solana.com>",
"Pankaj Garg <pankaj@solana.com>",
"Tyera Eulberg <tyera@solana.com>",
"Jack May <jack@solana.com>",
]
[dependencies]
solana_program_interface = { path = "../../common" }
[lib]
name = "print"
crate-type = ["dylib"]

View File

@ -1,9 +0,0 @@
extern crate solana_program_interface;
use solana_program_interface::account::KeyedAccount;
#[no_mangle]
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
println!("AccountInfos: {:#?}", infos);
//println!("data: {:#?}", data);
}

314
src/bpf_verifier.rs Normal file
View File

@ -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
}

View File

@ -1,38 +1,75 @@
extern crate bincode;
extern crate generic_array;
extern crate elf;
extern crate rbpf;
use libc;
use libloading;
use solana_program_interface::account::KeyedAccount;
use std::io::prelude::*;
use std::mem;
use std::path::PathBuf;
/// Dynamic link library prefix
use bpf_verifier;
use byteorder::{LittleEndian, WriteBytesExt};
use libc;
#[cfg(unix)]
const PLATFORM_FILE_PREFIX: &str = "lib";
/// Dynamic link library prefix
use libloading::os::unix::*;
#[cfg(windows)]
const PLATFORM_FILE_PREFIX: &str = "";
use libloading::os::windows::*;
use solana_program_interface::account::KeyedAccount;
use solana_program_interface::pubkey::Pubkey;
/// Dynamic link library prefixs
const PLATFORM_FILE_PREFIX_BPF: &str = "";
#[cfg(unix)]
const PLATFORM_FILE_PREFIX_NATIVE: &str = "lib";
#[cfg(windows)]
const PLATFORM_FILE_PREFIX_NATIVE: &str = "";
/// Dynamic link library file extension specific to the platform
const PLATFORM_FILE_EXTENSION_BPF: &str = "o";
#[cfg(any(target_os = "macos", target_os = "ios"))]
const PLATFORM_FILE_EXTENSION: &str = "dylib";
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dylib";
/// Dynamic link library file extension specific to the platform
#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
const PLATFORM_FILE_EXTENSION: &str = "so";
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so";
/// Dynamic link library file extension specific to the platform
#[cfg(windows)]
const PLATFORM_FILE_EXTENSION: &str = "dll";
const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll";
/// Section name
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
fn create_library_path(name: &str) -> PathBuf {
pub fn create(&self, name: &str) -> PathBuf {
let mut path = PathBuf::from(env!("OUT_DIR"));
match self {
ProgramPath::Bpf => {
//println!("Bpf");
path.pop();
path.pop();
path.pop();
path.push(PLATFORM_FILE_PREFIX_BPF.to_string() + name);
path.set_extension(PLATFORM_FILE_EXTENSION_BPF);
}
ProgramPath::Native => {
//println!("Native");
path.pop();
path.pop();
path.pop();
path.push("deps");
path.push(PLATFORM_FILE_PREFIX.to_string() + name);
path.set_extension(PLATFORM_FILE_EXTENSION);
path.push(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name);
path.set_extension(PLATFORM_FILE_EXTENSION_NATIVE);
}
}
//println!("Path: {:?}", path);
path
}
}
// All programs export a symbol named process()
const ENTRYPOINT: &str = "process";
@ -44,47 +81,129 @@ pub enum DynamicProgram {
/// * Transaction::keys[0..] - program dependent
/// * name - name of the program, translated to a file path of the program module
/// * userdata - program specific user data
Native {
name: String,
library: libloading::Library,
},
Native { name: String, library: Library },
/// Bpf program
/// * Transaction::keys[0..] - program dependent
/// * TODO BPF specific stuff
/// * userdata - program specific user data
Bpf { userdata: Vec<u8> },
Bpf { name: String, prog: Vec<u8> },
}
impl DynamicProgram {
pub fn new(name: String) -> Self {
// TODO determine what kind of module to load
pub fn new_native(name: String) -> Self {
// create native program
let path = create_library_path(&name);
let path = ProgramPath::Native {}.create(&name);
// TODO linux tls bug can cause crash on dlclose, workaround by never unloading
let os_lib =
libloading::os::unix::Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)
.unwrap();
let library = libloading::Library::from(os_lib);
let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW).unwrap();
DynamicProgram::Native { name, library }
}
pub fn new_bpf_from_file(name: String) -> Self {
// create native program
let path = ProgramPath::Bpf {}.create(&name);
let file = match elf::File::open_path(&path) {
Ok(f) => f,
Err(e) => panic!("Error opening ELF {:?}: {:?}", path, e),
};
let text_section = match file.get_section(PLATFORM_SECTION_RS) {
Some(s) => s,
None => match file.get_section(PLATFORM_SECTION_C) {
Some(s) => s,
None => panic!("Failed to find text section"),
},
};
let prog = text_section.data.clone();
DynamicProgram::Bpf { name, prog }
}
pub fn new_bpf_from_buffer(prog: Vec<u8>) -> Self {
DynamicProgram::Bpf {
name: "from_buffer".to_string(),
prog,
}
}
#[allow(dead_code)]
fn dump_prog(name: &str, prog: &[u8]) {
let mut eight_bytes: Vec<u8> = Vec::new();
println!("BPF Program: {}", name);
for i in prog.iter() {
if eight_bytes.len() >= 7 {
println!("{:02X?}", eight_bytes);
eight_bytes.clear();
} else {
eight_bytes.push(i.clone());
}
}
}
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]) {
match self {
DynamicProgram::Native { name, library } => unsafe {
let entrypoint: libloading::Symbol<Entrypoint> =
match library.get(ENTRYPOINT.as_bytes()) {
let entrypoint: Symbol<Entrypoint> = match library.get(ENTRYPOINT.as_bytes()) {
Ok(s) => s,
Err(e) => panic!(
"{:?} Unable to find {:?} in program {}",
"Unable to find {:?} in program {}: {:?} ",
e, ENTRYPOINT, name
),
};
entrypoint(infos, data);
},
DynamicProgram::Bpf { .. } => {
// TODO BPF
println!{"Bpf program not supported"}
DynamicProgram::Bpf { prog, .. } => {
println!("Instructions: {}", prog.len() / 8);
//DynamicProgram::dump_prog(name, prog);
let mut vm = rbpf::EbpfVmRaw::new(prog, Some(bpf_verifier::verifier));
// TODO register more handlers (memcpy for example)
vm.register_helper(
rbpf::helpers::BPF_TRACE_PRINTK_IDX,
rbpf::helpers::bpf_trace_printf,
);
let mut v = DynamicProgram::serialize(infos, data);
vm.prog_exec(v.as_mut_slice());
DynamicProgram::deserialize(infos, &v);
}
}
}
@ -95,17 +214,71 @@ mod tests {
use super::*;
use std::path::Path;
use solana_program_interface::account::Account;
use solana_program_interface::pubkey::Pubkey;
#[test]
fn test_create_library_path() {
let path = create_library_path("noop");
fn test_path_create_native() {
let path = ProgramPath::Native {}.create("noop");
assert_eq!(true, Path::new(&path).exists());
let path = create_library_path("print");
assert_eq!(true, Path::new(&path).exists());
let path = create_library_path("move_funds");
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);
}
}
#[test]
fn test_bpf_buf_print() {
let prog = vec![
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = 0
0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 = 0
0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // r3 = 1
0xb7, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // r4 = 2
0xb7, 0x05, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // r5 = 3
0x85, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // call 6
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let data: Vec<u8> = vec![0];
let keys = vec![Pubkey::default(); 2];
let mut accounts = vec![Account::default(), Account::default()];
accounts[0].tokens = 100;
accounts[1].tokens = 1;
{
let mut infos: Vec<_> = (&keys)
.into_iter()
.zip(&mut accounts)
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new_bpf_from_buffer(prog);
dp.call(&mut infos, &data);
}
}
// TODO add more tests to validate the Userdata and Account data is
// moving across the boundary correctly
}

View File

@ -22,6 +22,7 @@ pub mod choose_gossip_peer_strategy;
pub mod client;
#[macro_use]
pub mod crdt;
pub mod bpf_verifier;
pub mod budget_program;
pub mod drone;
pub mod dynamic_program;
@ -91,6 +92,7 @@ extern crate log;
extern crate nix;
extern crate pnet_datalink;
extern crate rayon;
extern crate rbpf;
extern crate reqwest;
extern crate ring;
extern crate serde;

View File

@ -88,7 +88,7 @@ impl SystemProgram {
}
SystemProgram::Load { program_id, name } => {
let mut hashmap = loaded_programs.write().unwrap();
hashmap.insert(program_id, DynamicProgram::new(name));
hashmap.insert(program_id, DynamicProgram::new_native(name));
}
}
} else {

View File

@ -55,6 +55,7 @@ impl Default for State {
}
}
#[repr(C)]
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Game {
player_x: Pubkey,
@ -173,7 +174,8 @@ impl Game {
}
#[derive(Debug, Serialize, Deserialize)]
enum Command {
#[repr(C)]
pub enum Command {
Init, // player X initializes a new game
Join(i64), // player O wants to join (seconds since UNIX epoch)
KeepAlive(i64), // player X/O keep alive (seconds since UNIX epoch)

View File

@ -3,12 +3,16 @@ extern crate solana;
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 solana::dynamic_program::DynamicProgram;
#[cfg(feature = "bpf_c")]
use solana::dynamic_program::ProgramPath;
use solana::hash::Hash;
use solana::signature::{Keypair, KeypairUtil};
use solana::system_program::SystemProgram;
@ -17,29 +21,22 @@ use solana::transaction::Transaction;
use solana_program_interface::account::{Account, KeyedAccount};
use solana_program_interface::pubkey::Pubkey;
#[cfg(feature = "bpf_c")]
use solana::tictactoe_program::Command;
#[cfg(feature = "bpf_c")]
#[test]
fn test_native_noop() {
let data: Vec<u8> = vec![0];
let keys = vec![Pubkey::default(); 2];
let mut accounts = vec![Account::default(), Account::default()];
accounts[0].tokens = 100;
accounts[1].tokens = 1;
{
let mut infos: Vec<_> = (&keys)
.into_iter()
.zip(&mut accounts)
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new("noop".to_string());
dp.call(&mut infos, &data);
}
fn test_path_create_bpf() {
let path = ProgramPath::Bpf {}.create("move_funds_c");
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_native_print() {
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()];
@ -53,13 +50,105 @@ fn test_native_print() {
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new("print".to_string());
let dp = DynamicProgram::new_bpf_from_file("noop_rust".to_string());
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());
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());
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]
fn test_native_file_noop() {
let data: Vec<u8> = vec![0];
let keys = vec![Pubkey::default(); 2];
let mut accounts = vec![Account::default(), Account::default()];
accounts[0].tokens = 100;
accounts[1].tokens = 1;
{
let mut infos: Vec<_> = (&keys)
.into_iter()
.zip(&mut accounts)
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new_native("noop".to_string());
dp.call(&mut infos, &data);
}
}
#[test]
fn test_native_move_funds_success() {
fn test_native_file_move_funds_success() {
let tokens: i64 = 100;
let data: Vec<u8> = serialize(&tokens).unwrap();
let keys = vec![Pubkey::default(); 2];
@ -74,7 +163,7 @@ fn test_native_move_funds_success() {
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new("move_funds".to_string());
let dp = DynamicProgram::new_native("move_funds".to_string());
dp.call(&mut infos, &data);
}
assert_eq!(0, accounts[0].tokens);
@ -82,7 +171,7 @@ fn test_native_move_funds_success() {
}
#[test]
fn test_native_move_funds_insufficient_funds() {
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];
@ -97,7 +186,7 @@ fn test_native_move_funds_insufficient_funds() {
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new("move_funds".to_string());
let dp = DynamicProgram::new_native("move_funds".to_string());
dp.call(&mut infos, &data);
}
assert_eq!(10, accounts[0].tokens);
@ -105,7 +194,7 @@ fn test_native_move_funds_insufficient_funds() {
}
#[test]
fn test_native_move_funds_succes_many_threads() {
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();
@ -127,7 +216,7 @@ fn test_native_move_funds_succes_many_threads() {
.map(|(key, account)| KeyedAccount { key, account })
.collect();
let dp = DynamicProgram::new("move_funds".to_string());
let dp = DynamicProgram::new_native("move_funds".to_string());
dp.call(&mut infos, &data);
}
assert_eq!(0, accounts[0].tokens);