diff --git a/Cargo.toml b/Cargo.toml index 06e09f688..56a3cdbbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", ] - diff --git a/build.rs b/build.rs index 0516dd42c..7d22a4a6e 100644 --- a/build.rs +++ b/build.rs @@ -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 { diff --git a/common/src/account.rs b/common/src/account.rs index e39753dbd..a818e85a6 100644 --- a/common/src/account.rs +++ b/common/src/account.rs @@ -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, diff --git a/common/src/pubkey.rs b/common/src/pubkey.rs index b69eb98b1..e0189c318 100644 --- a/common/src/pubkey.rs +++ b/common/src/pubkey.rs @@ -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); diff --git a/programs/bpf/move_funds_c/build.sh b/programs/bpf/move_funds_c/build.sh new file mode 100755 index 000000000..ede12deb7 --- /dev/null +++ b/programs/bpf/move_funds_c/build.sh @@ -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 \ No newline at end of file diff --git a/programs/bpf/move_funds_c/dump.sh b/programs/bpf/move_funds_c/dump.sh new file mode 100755 index 000000000..a45cdeed6 --- /dev/null +++ b/programs/bpf/move_funds_c/dump.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/move_funds_c.o \ No newline at end of file diff --git a/programs/bpf/move_funds_c/src/move_funds.c b/programs/bpf/move_funds_c/src/move_funds.c new file mode 100644 index 000000000..55b8f283c --- /dev/null +++ b/programs/bpf/move_funds_c/src/move_funds.c @@ -0,0 +1,130 @@ + +//#include +//#include + +#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); +} diff --git a/programs/bpf/noop_rust/Cargo.toml b/programs/bpf/noop_rust/Cargo.toml new file mode 100644 index 000000000..282cdd5a2 --- /dev/null +++ b/programs/bpf/noop_rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "noop_rust" +version = "0.1.0" +authors = ["Jack May "] + +[dependencies] +rbpf = { git = "https://github.com/solana-labs/rbpf" } +solana = { path = "../../.." } \ No newline at end of file diff --git a/programs/bpf/noop_rust/build.sh b/programs/bpf/noop_rust/build.sh new file mode 100755 index 000000000..4d84de1ee --- /dev/null +++ b/programs/bpf/noop_rust/build.sh @@ -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 \ No newline at end of file diff --git a/programs/bpf/noop_rust/dump.sh b/programs/bpf/noop_rust/dump.sh new file mode 100755 index 000000000..47180ced4 --- /dev/null +++ b/programs/bpf/noop_rust/dump.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble target/release/noop_rust.o \ No newline at end of file diff --git a/programs/bpf/noop_rust/src/lib.rs b/programs/bpf/noop_rust/src/lib.rs new file mode 100644 index 000000000..945493d98 --- /dev/null +++ b/programs/bpf/noop_rust/src/lib.rs @@ -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::( + rbpf::helpers::BPF_TRACE_PRINTK_IDX as u64, + ) + }; + + bpf_func_trace_printk(0, 0, 1, 2, 3); +} diff --git a/programs/bpf/tictactoe_c/build.sh b/programs/bpf/tictactoe_c/build.sh new file mode 100755 index 000000000..b0160fe06 --- /dev/null +++ b/programs/bpf/tictactoe_c/build.sh @@ -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 \ No newline at end of file diff --git a/programs/bpf/tictactoe_c/dump.sh b/programs/bpf/tictactoe_c/dump.sh new file mode 100755 index 000000000..e088c4047 --- /dev/null +++ b/programs/bpf/tictactoe_c/dump.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/tictactoe_c.o \ No newline at end of file diff --git a/programs/bpf/tictactoe_c/makefile b/programs/bpf/tictactoe_c/makefile new file mode 100755 index 000000000..481164a2b --- /dev/null +++ b/programs/bpf/tictactoe_c/makefile @@ -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 + diff --git a/programs/bpf/tictactoe_c/src/tictactoe.c b/programs/bpf/tictactoe_c/src/tictactoe.c new file mode 100644 index 000000000..d501fc896 --- /dev/null +++ b/programs/bpf/tictactoe_c/src/tictactoe.c @@ -0,0 +1,370 @@ + +//#include +//#include + +#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); +// } diff --git a/programs/move_funds/Cargo.toml b/programs/native/move_funds/Cargo.toml similarity index 90% rename from programs/move_funds/Cargo.toml rename to programs/native/move_funds/Cargo.toml index a0b472c41..072762d15 100644 --- a/programs/move_funds/Cargo.toml +++ b/programs/native/move_funds/Cargo.toml @@ -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" diff --git a/programs/move_funds/src/lib.rs b/programs/native/move_funds/src/lib.rs similarity index 100% rename from programs/move_funds/src/lib.rs rename to programs/native/move_funds/src/lib.rs diff --git a/programs/noop/Cargo.toml b/programs/native/noop/Cargo.toml similarity index 88% rename from programs/noop/Cargo.toml rename to programs/native/noop/Cargo.toml index fb2ebc479..20ae6906b 100644 --- a/programs/noop/Cargo.toml +++ b/programs/native/noop/Cargo.toml @@ -13,7 +13,7 @@ authors = [ ] [dependencies] -solana_program_interface = { path = "../../common" } +solana_program_interface = { path = "../../../common" } [lib] name = "noop" diff --git a/programs/noop/src/lib.rs b/programs/native/noop/src/lib.rs similarity index 64% rename from programs/noop/src/lib.rs rename to programs/native/noop/src/lib.rs index e69f6f846..6137e80bc 100644 --- a/programs/noop/src/lib.rs +++ b/programs/native/noop/src/lib.rs @@ -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, _data: &[u8]) {} +pub extern "C" fn process(_infos: &mut Vec, _data: &[u8]) { + //println!("AccountInfos: {:#?}", _infos); + //println!("data: {:#?}", data); +} diff --git a/programs/print/Cargo.toml b/programs/print/Cargo.toml deleted file mode 100644 index 15fa4dec1..000000000 --- a/programs/print/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "print" -version = "0.1.0" -authors = [ - "Anatoly Yakovenko ", - "Greg Fitzgerald ", - "Stephen Akridge ", - "Michael Vines ", - "Rob Walker ", - "Pankaj Garg ", - "Tyera Eulberg ", - "Jack May ", -] - -[dependencies] -solana_program_interface = { path = "../../common" } - -[lib] -name = "print" -crate-type = ["dylib"] - diff --git a/programs/print/src/lib.rs b/programs/print/src/lib.rs deleted file mode 100644 index 2a4cb81fb..000000000 --- a/programs/print/src/lib.rs +++ /dev/null @@ -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, _data: &[u8]) { - println!("AccountInfos: {:#?}", infos); - //println!("data: {:#?}", data); -} diff --git a/src/bpf_verifier.rs b/src/bpf_verifier.rs new file mode 100644 index 000000000..ca0045834 --- /dev/null +++ b/src/bpf_verifier.rs @@ -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 +} diff --git a/src/dynamic_program.rs b/src/dynamic_program.rs index 2d35c1ee3..9f6299ee4 100644 --- a/src/dynamic_program.rs +++ b/src/dynamic_program.rs @@ -1,37 +1,74 @@ -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"; -/// Creates a platform-specific file path -fn create_library_path(name: &str) -> PathBuf { - let mut path = PathBuf::from(env!("OUT_DIR")); - path.pop(); - path.pop(); - path.pop(); - path.push("deps"); - path.push(PLATFORM_FILE_PREFIX.to_string() + name); - path.set_extension(PLATFORM_FILE_EXTENSION); - path +/// 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 + 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_NATIVE.to_string() + name); + path.set_extension(PLATFORM_FILE_EXTENSION_NATIVE); + } + } + //println!("Path: {:?}", path); + path + } } // All programs export a symbol named 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 }, + Bpf { name: String, prog: Vec }, } 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) -> Self { + DynamicProgram::Bpf { + name: "from_buffer".to_string(), + prog, + } + } + + #[allow(dead_code)] + fn dump_prog(name: &str, prog: &[u8]) { + let mut eight_bytes: Vec = 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, data: &[u8]) -> Vec { + assert_eq!(32, mem::size_of::()); + + let mut v: Vec = Vec::new(); + v.write_u64::(infos.len() as u64).unwrap(); + for info in infos.iter_mut() { + v.write_all(info.key.as_ref()).unwrap(); + v.write_i64::(info.account.tokens).unwrap(); + v.write_u64::(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::(data.len() as u64).unwrap(); + v.write_all(data).unwrap(); + v + } + + fn deserialize(infos: &mut Vec, buffer: &[u8]) { + assert_eq!(32, mem::size_of::()); + + let mut start = mem::size_of::(); + for info in infos.iter_mut() { + start += mem::size_of::() // pubkey + + mem::size_of::() // tokens + + mem::size_of::(); // 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::(); // program_id + //println!("userdata: {:?}", infos[i].account.userdata); + } + } + pub fn call(&self, infos: &mut Vec, data: &[u8]) { match self { DynamicProgram::Native { name, library } => unsafe { - let entrypoint: libloading::Symbol = - match library.get(ENTRYPOINT.as_bytes()) { - Ok(s) => s, - Err(e) => panic!( - "{:?} Unable to find {:?} in program {}", - e, ENTRYPOINT, name - ), - }; + let entrypoint: Symbol = match library.get(ENTRYPOINT.as_bytes()) { + Ok(s) => s, + Err(e) => panic!( + "Unable to find {:?} in program {}: {:?} ", + e, ENTRYPOINT, name + ), + }; entrypoint(infos, data); }, - DynamicProgram::Bpf { .. } => { - // TODO BPF - println!{"Bpf program not supported"} + 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 = 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 = 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 - } diff --git a/src/lib.rs b/src/lib.rs index 114518503..aaca38ad0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/system_program.rs b/src/system_program.rs index 46b22b794..f7176d741 100644 --- a/src/system_program.rs +++ b/src/system_program.rs @@ -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 { diff --git a/src/tictactoe_program.rs b/src/tictactoe_program.rs index 3e802a2e1..422f77830 100644 --- a/src/tictactoe_program.rs +++ b/src/tictactoe_program.rs @@ -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) diff --git a/tests/programs.rs b/tests/programs.rs index 9017e9e1f..e5e62bbc4 100644 --- a/tests/programs.rs +++ b/tests/programs.rs @@ -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 = 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 = 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 = 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, 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::()) }; + + // 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 = 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 = 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 = 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);