diff --git a/Cargo.toml b/Cargo.toml index f414f1dd1..29c662811 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,5 +139,5 @@ members = [ "programs/native/noop", "programs/native/bpf_loader", "programs/native/lua_loader", - "programs/bpf/noop_rust", + "programs/bpf/rust/noop", ] diff --git a/build.rs b/build.rs index 8cf377604..abe8cc170 100644 --- a/build.rs +++ b/build.rs @@ -20,44 +20,22 @@ fn main() { let erasure = !env::var("CARGO_FEATURE_ERASURE").is_err(); if bpf_c { - let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap(); + let out_dir = "OUT_DIR=../../../target/".to_string() + + &env::var("PROFILE").unwrap() + + &"/bpf".to_string(); - println!("cargo:rerun-if-changed=programs/bpf/noop_c/build.sh"); - println!("cargo:rerun-if-changed=programs/bpf/noop_c/src/noop.c"); - println!("cargo:warning=(not a warning) Compiling noop_c"); - let status = Command::new("programs/bpf/noop_c/build.sh") + println!("cargo:rerun-if-changed=programs/bpf/c/makefile"); + println!("cargo:rerun-if-changed=programs/bpf/c/src/move_funds.c"); + println!("cargo:rerun-if-changed=programs/bpf/c/src/noop.c"); + println!("cargo:rerun-if-changed=programs/bpf/c/src/tictactoe.c"); + println!("cargo:rerun-if-changed=programs/bpf/c/src/tictactoe_dashboard.c"); + println!("cargo:warning=(not a warning) Compiling C-based BPF programs"); + let status = Command::new("make") + .current_dir("programs/bpf/c") + .arg("all") .arg(&out_dir) .status() - .expect("Failed to call noop_c build script"); - assert!(status.success()); - - println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/build.sh"); - println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/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()); - - println!("cargo:rerun-if-changed=programs/bpf/tictactoe_dashboard_c/build.sh"); - println!( - "cargo:rerun-if-changed=programs/bpf/tictactoe_dashboard_c/src/tictactoe_dashboard.c" - ); - println!("cargo:warning=(not a warning) Compiling tictactoe_dashboard_c"); - let status = Command::new("programs/bpf/tictactoe_dashboard_c/build.sh") - .arg(&out_dir) - .status() - .expect("Failed to call tictactoe_dashboard_c build script"); + .expect("Failed to build C-based BPF programs"); assert!(status.success()); } if chacha || cuda || erasure { diff --git a/programs/bpf/c/README.md b/programs/bpf/c/README.md new file mode 100644 index 000000000..e4ec119b1 --- /dev/null +++ b/programs/bpf/c/README.md @@ -0,0 +1 @@ +Place holder, will hold instructions on how to setup and build C-based BPF programs diff --git a/programs/bpf/c/makefile b/programs/bpf/c/makefile new file mode 100644 index 000000000..dda9645a6 --- /dev/null +++ b/programs/bpf/c/makefile @@ -0,0 +1,96 @@ + +_@ :=@ +INC_DIRS := -I. +SRC_DIR := ./src +OUT_DIR := ./out +LLVM_DIR := /usr/local/opt/llvm/bin + +CC = $(LLVM_DIR)/clang +CC_FLAGS = \ + -Werror \ + -target \ + bpf -O2 \ + -emit-llvm \ + -fno-builtin + +LD = $(LLVM_DIR)/llc +LD_FLAGS = \ + -march=bpf \ + -filetype=obj \ + -function-sections + +OBJ_DUMP = $(LLVM_DIR)/llvm-objdump +OBJ_DUMP_FLAGS = \ + -color \ + -source \ + -disassemble + +help: + @echo 'BPF Program makefile' + @echo '' + @echo 'This makefile will build BPF Programs from C source files into ELFs' + @echo '' + @echo 'Assumptions:' + @echo ' - Programs are a single .c source file (may include headers)' + @echo ' - Programs are located in the source directory: $(SRC_DIR)' + @echo ' - Programs are named by their basename (eg. file name:foo.c -> program name:foo)' + @echo ' - Output files will be placed in the directory: $(OUT_DIR)' + @echo '' + @echo 'User settings' + @echo ' - The following setting are overridable on the command line, default values shown' + @echo ' - Verbosity, for verbose: '_@=':' + @echo ' - _@=$(_@)' + @echo ' - List of include dirs:' + @echo ' INC_DIRS=$(INC_DIRS)' + @echo ' - Location of source files:' + @echo ' SRC_DIR=$(SRC_DIR)' + @echo ' - Location to place output files:' + @echo ' OUT_DIR=$(OUT_DIR)' + @echo ' - Location of LLVM:' + @echo ' LLVM_DIR=$(LLVM_DIR)' + @echo '' + @echo 'Usage:' + @echo ' - make help - This help message' + @echo ' - make all - Builds all the programs in the directory: $(SRC_DIR)' + @echo ' - make clean - Cleans all programs' + @echo ' - make dump_ - Dumps the contents of the program to stdout' + @echo ' - make - Build a single program by name' + @echo '' + @echo 'Available programs:' + $(foreach name, $(PROGRAM_NAMES), @echo ' - $(name)'$(\n)) + @echo '' + @echo 'Example:' + @echo ' - Assuming a programed named foo (src/foo.c)' + @echo ' - make foo INC_DIRS='-I. -Isrc'' + @echo ' - make dump_foo' + +.PRECIOUS: $(OUT_DIR)/%.bc +$(OUT_DIR)/%.bc: $(SRC_DIR)/%.c + $(_@)mkdir -p $(OUT_DIR) + $(_@)$(CC) $(CC_FLAGS) $(INC_DIRS) -o $@ -c $< -MMD -MF $(@:.bc=.d) + +.PRECIOUS: $(OUT_DIR)/%.o +$(OUT_DIR)/%.o: $(OUT_DIR)/%.bc + $(_@)$(LD) $(LD_FLAGS) -o $@ $< + +-include $(wildcard $(OUT_DIR)/*.d) + +PROGRAM_NAMES := $(notdir $(basename $(wildcard src/*.c))) + +define \n + + +endef + +%: $(addprefix $(OUT_DIR)/, %.o) + @echo $@ up to date + +.PHONY: help all dump clean + +all: $(PROGRAM_NAMES) + +dump_%: % + $(_@)$(OBJ_DUMP) $(OBJ_DUMP_FLAGS) $(addprefix $(OUT_DIR)/, $(addsuffix .o, $<)) + +clean: + $(_@)rm -rf $(OUT_DIR) diff --git a/programs/bpf/c/sol_bpf_c.h b/programs/bpf/c/sol_bpf_c.h new file mode 100644 index 000000000..9f5cbb29a --- /dev/null +++ b/programs/bpf/c/sol_bpf_c.h @@ -0,0 +1,261 @@ +#ifndef SOL_BPF_C_H +#define SOL_BPF_C_H +/** + * @brief Solana C-based BPF program utility functions and types + */ + +/** + * Numberic types + */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed int int16_t; +typedef unsigned int uint16_t; +typedef signed long int int32_t; +typedef unsigned long int uint32_t; +typedef signed long long int int64_t; +typedef unsigned long long int uint64_t; + +/** + * Boolean type + */ +typedef enum { false = 0, true } bool; + +/** + * Built-in helper functions + * @{ + * The BPF VM makes a limited number of helper functions available to BPF + * programs. They are resolved at run-time and identified by a function index. + * Calling any of these functions results in `Call` instruction out of the + * user's BPF program. + * + * The helper functions all follow the same signature: + * + * int helper(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t) + * + * The meaning of each argument and return value is dependent on the particular + * helper function being called. + */ + +/** + * Helper function that prints to stdout + * + * Prints the hexadecimal representation of each parameter + */ +#define BPF_TRACE_PRINTK_IDX 6 +static int (*sol_print)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t) = (void *)BPF_TRACE_PRINTK_IDX; + +/**@}*/ + +/** + * Prefix for all BPF functions + * + * This prefix should be used for functions in order to facilitate + * interopability with BPF representation + */ +#define SOL_FN_PREFIX __attribute__((always_inline)) static + +/** + * Size of Public key in bytes + */ +#define SIZE_PUBKEY 32 + +/** + * Public key + */ +typedef struct { + uint8_t x[SIZE_PUBKEY]; +} SolPubkey; + +/** + * Compares two public keys + * + * @param one First public key + * @param two Second public key + * @return True if the same + */ +SOL_FN_PREFIX bool SolPubkey_same(SolPubkey *one, SolPubkey *two) { + for (int i = 0; i < SIZE_PUBKEY; i++) { + if (one->x[i] != two->x[i]) { + return false; + } + } + return true; +} + +/** + * Keyed Accounts + */ +typedef struct { + SolPubkey *key; /** Public Key of the account owner */ + int64_t *tokens; /** Numer of tokens owned by this account */ + uint64_t userdata_len; /** Length of userdata in bytes */ + uint8_t *userdata; /** On-chain data owned by this account */ + SolPubkey *program_id; /** Program that owns this account */ +} SolKeyedAccounts; + +/** + * Copies memory + */ +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); + } +} + +/** + * Panics + * + * Prints the line number where the panic occurred and then causes + * the BPF VM to immediately halt execution. No accounts' userdata are updated + */ +#define sol_panic() _sol_panic(__LINE__) +SOL_FN_PREFIX void _sol_panic(uint64_t line) { + sol_print(0xFF, 0xFF, 0xFF, 0xFF, line); + uint8_t *pv = (uint8_t *)1; + *pv = 1; +} + +/** + * Asserts + */ +#define sol_assert(expr) \ + if (!(expr)) { \ + _sol_panic(__LINE__); \ + } + +/** + * De-serializes the input parameters into usable types + * + * Use this function to deserialize the buffer passed to the program entrypoint + * into usable types. This function does not perform copy deserialization, + * instead it populates the pointers and lengths in SolKeyedAccounts and data so + * that any modification to tokens or account data take place on the original + * buffer. Doing so also eliminates the need to serialize back into the buffer + * at program end. + * + * @param input Source buffer containing serialized input parameters + * @param num_ka Numer of SolKeyedAccounts to fill + * @param ka Pointer to an array of SolKeyedAccounts to deserialize into + * @param data On return, a pointer to the instruction data + * @param data_len On return, the length in bytes of the instruction data + * @return Boolan True if successful + */ +SOL_FN_PREFIX bool sol_deserialize(uint8_t *input, uint64_t num_ka, + SolKeyedAccounts *ka, uint8_t **data, + uint64_t *data_len) { + if (num_ka != *(uint64_t *)input) { + return false; + } + input += sizeof(uint64_t); + + for (int i = 0; i < num_ka; i++) { + // key + ka[i].key = (SolPubkey *)input; + input += SIZE_PUBKEY; + + // tokens + ka[i].tokens = (int64_t *)input; + input += sizeof(int64_t); + + // account userdata + ka[i].userdata_len = *(uint64_t *)input; + input += sizeof(uint64_t); + ka[i].userdata = input; + input += ka[i].userdata_len; + + // program_id + ka[i].program_id = (SolPubkey *)input; + input += SIZE_PUBKEY; + } + // instruction data + *data_len = *(uint64_t *)input; + input += sizeof(uint64_t); + *data = input; + + return true; +} + +/** + * Debugging utilities + * @{ + */ + +/** + * Prints the hexadecimal representation of a public key + * + * @param key The public key to print + */ +SOL_FN_PREFIX void sol_print_key(SolPubkey *key) { + for (int j = 0; j < SIZE_PUBKEY; j++) { + sol_print(0, 0, 0, j, key->x[j]); + } +} + +/** + * Prints the hexadecimal representation of an array + * + * @param array The array to print + */ +SOL_FN_PREFIX void sol_print_array(uint8_t *array, int len) { + for (int j = 0; j < len; j++) { + sol_print(0, 0, 0, j, array[j]); + } +} + +/** + * Prints the hexadecimal representation of the program's input parameters + * + * @param num_ka Numer of SolKeyedAccounts to print + * @param ka A pointer to an array of SolKeyedAccounts to print + * @param data A pointer to the instruction data to print + * @param data_len The length in bytes of the instruction data + */ +SOL_FN_PREFIX void sol_print_params(uint64_t num_ka, SolKeyedAccounts *ka, + uint8_t *data, uint64_t data_len) { + sol_print(0, 0, 0, 0, num_ka); + for (int i = 0; i < num_ka; i++) { + sol_print_key(ka[i].key); + sol_print(0, 0, 0, 0, *ka[i].tokens); + sol_print_array(ka[i].userdata, ka[i].userdata_len); + sol_print_key(ka[i].program_id); + } + sol_print_array(data, data_len); +} + +/**@}*/ + +/** + * Program entrypoint + * @{ + * + * The following is An example of a simple program that prints the input + * parameters it received: + * + * #define NUM_KA 1 + * + * bool entrypoint(uint8_t *input) { + * SolKeyedAccounts ka[NUM_KA]; + * uint8_t *data; + * uint64_t data_len; + * + * if (1 != sol_deserialize((uint8_t *)buf, NUM_KA, ka, &data, &data_len)) { + * return false; + * } + * print_params(1, ka, data, data_len); + * return true; + * } + */ + +/** + * Program entrypoint signature + * + * @param input An array containing serialized input parameters + * @return True if successful + */ +extern bool entrypoint(uint8_t *input); + +/**@}*/ + +#endif // SOL_BPF_C_H diff --git a/programs/bpf/c/src/move_funds.c b/programs/bpf/c/src/move_funds.c new file mode 100644 index 000000000..a46f78cd9 --- /dev/null +++ b/programs/bpf/c/src/move_funds.c @@ -0,0 +1,32 @@ +/** + * @brief Example C-based BPF program that moves funds from one account to + * another + */ + +#include "sol_bpf_c.h" + +/** + * Numer of SolKeyedAccounts expected. The program should bail if an + * unexpected number of accounts are passed to the program's entrypoint + */ +#define NUM_KA 3 + +extern bool entrypoint(uint8_t *input) { + SolKeyedAccounts ka[NUM_KA]; + uint8_t *data; + uint64_t data_len; + + if (!sol_deserialize((uint8_t *)input, NUM_KA, ka, &data, &data_len)) { + return false; + } + + int64_t tokens = *(int64_t *)data; + if (*ka[0].tokens >= tokens) { + *ka[0].tokens -= tokens; + *ka[2].tokens += tokens; + // sol_print(0, 0, *ka[0].tokens, *ka[2].tokens, tokens); + } else { + // sol_print(0, 0, 0xFF, *ka[0].tokens, tokens); + } + return true; +} diff --git a/programs/bpf/c/src/noop.c b/programs/bpf/c/src/noop.c new file mode 100644 index 000000000..b887f5be0 --- /dev/null +++ b/programs/bpf/c/src/noop.c @@ -0,0 +1,24 @@ +/** + * @brief Example C-based BPF program that prints out the parameters + * passed to it + */ + +#include "sol_bpf_c.h" + +/** + * Numer of SolKeyedAccounts expected. The program should bail if an + * unexpected number of accounts are passed to the program's entrypoint + */ +#define NUM_KA 1 + +extern bool entrypoint(uint8_t *input) { + SolKeyedAccounts ka[NUM_KA]; + uint8_t *data; + uint64_t data_len; + + if (!sol_deserialize((uint8_t *)input, NUM_KA, ka, &data, &data_len)) { + return false; + } + sol_print_params(1, ka, data, data_len); + return true; +} diff --git a/programs/bpf/tictactoe_c/src/tictactoe.c b/programs/bpf/c/src/tictactoe.c similarity index 53% rename from programs/bpf/tictactoe_c/src/tictactoe.c rename to programs/bpf/c/src/tictactoe.c index 25b3a7f9a..00b1c8c40 100644 --- a/programs/bpf/tictactoe_c/src/tictactoe.c +++ b/programs/bpf/c/src/tictactoe.c @@ -1,128 +1,10 @@ -//#include -//#include +/** + * @brief TicTacToe Dashboard C-based BPF program + */ -#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 +#include "sol_bpf_c.h" -typedef long long unsigned int uint64_t; -typedef long long int int64_t; -typedef unsigned char uint8_t; - -typedef enum { false = 0, true } bool; - -// 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_trace() sol_print(0, 0, 0xFF, 0xFF, (__LINE__)); -#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; -} - -#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; - -SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, - SolKeyedAccounts *ka, uint8_t **tx_data, - uint64_t *tx_data_len) { - if (num_ka != *(uint64_t *)src) { - return 0; - } - src += sizeof(uint64_t); - - // TODO fixed iteration loops ok? unrolled? - for (int i = 0; i < num_ka; - i++) { // TODO this should end up unrolled, confirm - // key - ka[i].key = (SolPubkey *)src; - src += SIZE_PUBKEY; - - // tokens - ka[i].tokens = *(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 - *tx_data_len = *(uint64_t *)src; - src += sizeof(uint64_t); - *tx_data = src; - - return 1; -} - -// // -- Debug -- - -SOL_FN_PREFIX void print_key(SolPubkey *key) { - for (int j = 0; j < SIZE_PUBKEY; j++) { - sol_print(0, 0, 0, j, key->x[j]); - } -} - -SOL_FN_PREFIX void print_data(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 *tx_data, uint64_t tx_data_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_data(ka[i].userdata, ka[i].userdata_len); - - // program_id - print_key(ka[i].program_id); - } - // tx userdata - print_data(tx_data, tx_data_len); -} - -// -- TicTacToe -- - -// Board Coodinates -// | 0,0 | 1,0 | 2,0 | -// | 0,1 | 1,1 | 2,1 | -// | 0,2 | 1,2 | 2,2 | +#include "tictactoe.h" typedef enum { Result_Ok, @@ -138,30 +20,6 @@ typedef enum { 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 { - // Player who initialized the game - SolPubkey player_x; - // Player who joined the game - SolPubkey player_o; - // Current state of the game - State state; - // Tracks the player moves - BoardItem board[9]; - // Keep Alive for each player - int64_t keep_alive[2]; -} Game; - typedef enum { Command_Init = 0, Command_Join, @@ -170,21 +28,17 @@ typedef enum { } Command; SOL_FN_PREFIX void game_dump_board(Game *self) { - sol_print(0, 0, 0x9, 0x9, 0x9); + sol_print(0x9, 0x9, 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_print(0x9, 0x9, 0x9, 0x9, 0x9); } SOL_FN_PREFIX void game_create(Game *self, SolPubkey *player_x) { + // account memory is zero-initialized 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; } @@ -215,7 +69,6 @@ SOL_FN_PREFIX bool game_same(BoardItem x_or_o, BoardItem one, BoardItem two, } 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; @@ -280,7 +133,6 @@ SOL_FN_PREFIX Result game_next_move(Game *self, SolPubkey *player, int x, { int draw = true; - // TODO fixed iteration loops ok? unrolled? for (int i = 0; i < 9; i++) { if (BoardItem_F == self->board[i]) { draw = false; @@ -321,17 +173,24 @@ SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player, return Result_Ok; } -// accounts[0] On Init must be player X, after that doesn't matter, -// anybody can cause a dashboard update -// accounts[1] must be a TicTacToe state account -// accounts[2] must be account of current player, only Pubkey is used -uint64_t entrypoint(uint8_t *buf) { - SolKeyedAccounts ka[3]; - uint64_t tx_data_len; - uint8_t *tx_data; +/** + * Numer of SolKeyedAccounts expected. The program should bail if an + * unexpected number of accounts are passed to the program's entrypoint + * + * accounts[0] On Init must be player X, after that doesn't matter, + * anybody can cause a dashboard update + * accounts[1] must be a TicTacToe state account + * accounts[2] must be account of current player, only Pubkey is used + */ +#define NUM_KA 3 + +extern bool entrypoint(uint8_t *input) { + SolKeyedAccounts ka[NUM_KA]; + uint8_t *data; + uint64_t data_len; int err = 0; - if (1 != sol_deserialize(buf, 3, ka, &tx_data, &tx_data_len)) { + if (!sol_deserialize((uint8_t *)input, NUM_KA, ka, &data, &data_len)) { return false; } @@ -342,14 +201,14 @@ uint64_t entrypoint(uint8_t *buf) { Game game; sol_memcpy(&game, ka[1].userdata, sizeof(game)); - Command command = *tx_data; + Command command = *data; switch (command) { case Command_Init: game_create(&game, ka[2].key); break; case Command_Join: - err = game_join(&game, ka[2].key, *((int64_t *)(tx_data + 4))); + err = game_join(&game, ka[2].key, *((int64_t *)(data + 4))); break; case Command_KeepAlive: @@ -357,7 +216,7 @@ uint64_t entrypoint(uint8_t *buf) { break; case Command_Move: - err = game_next_move(&game, ka[2].key, tx_data[4], tx_data[5]); + err = game_next_move(&game, ka[2].key, data[4], data[5]); break; default: diff --git a/programs/bpf/c/src/tictactoe.h b/programs/bpf/c/src/tictactoe.h new file mode 100644 index 000000000..6035bd60f --- /dev/null +++ b/programs/bpf/c/src/tictactoe.h @@ -0,0 +1,36 @@ +#ifndef TICTACTOE_H +#define TICTACTOE_H +/** + * @brief Definitions common to tictactoe and tictactoe_dashboard + */ + +typedef enum { + State_Waiting, + State_XMove, + State_OMove, + State_XWon, + State_OWon, + State_Draw, +} State; + +typedef enum { BoardItem_F, BoardItem_X, BoardItem_O } BoardItem; + +/** + * Game state + * + * This structure is stored in the owner's account userdata + * + * Board Coordinates + * | 0,0 | 1,0 | 2,0 | + * | 0,1 | 1,1 | 2,1 | + * | 0,2 | 1,2 | 2,2 | + */ +typedef struct { + SolPubkey player_x; /** Player who initialized the game */ + SolPubkey player_o; /** Player who joined the game */ + State state; /** Current state of the game */ + BoardItem board[9]; /** Tracks the player moves */ + int64_t keep_alive[2]; /** Keep Alive for each player */ +} Game; + +#endif // TICTACTOE_H diff --git a/programs/bpf/c/src/tictactoe_dashboard.c b/programs/bpf/c/src/tictactoe_dashboard.c new file mode 100644 index 000000000..9eee71a0e --- /dev/null +++ b/programs/bpf/c/src/tictactoe_dashboard.c @@ -0,0 +1,99 @@ +/** + * @brief TicTacToe C-based BPF program + */ + +#include "sol_bpf_c.h" + +#include "tictactoe.h" + +#define MAX_GAMES_TRACKED 5 + +/** + * Dashboard state + * + * This structure is stored in the owner's account userdata + */ +typedef struct { + SolPubkey pending; /** Latest pending game */ + SolPubkey completed[MAX_GAMES_TRACKED]; /** Last N completed games (0 is the + latest) */ + uint32_t latest_game; /** Index into completed pointing to latest game completed */ + uint32_t total; /** Total number of completed games */ +} Dashboard; + +SOL_FN_PREFIX bool update(Dashboard *self, Game *game, SolPubkey *game_pubkey) { + switch (game->state) { + case State_Waiting: + sol_memcpy(&self->pending, game_pubkey, SIZE_PUBKEY); + break; + case State_XMove: + case State_OMove: + // Nothing to do. In progress games are not managed by the dashboard + break; + case State_XWon: + case State_OWon: + case State_Draw: + for (int i = 0; i < MAX_GAMES_TRACKED; i++) { + if (SolPubkey_same(&self->completed[i], game_pubkey)) { + // TODO: Once the PoH height is exposed to programs, it could be used + // to ensure + // that old games are not being re-added and causing total to + // increment incorrectly. + return false; + } + } + self->total += 1; + self->latest_game = (self->latest_game + 1) % MAX_GAMES_TRACKED; + sol_memcpy(self->completed[self->latest_game].x, game_pubkey, + SIZE_PUBKEY); + break; + + default: + break; + } + return true; +} + +/** + * Numer of SolKeyedAccounts expected. The program should bail if an + * unexpected number of accounts are passed to the program's entrypoint + * + * accounts[0] doesn't matter, anybody can cause a dashboard update + * accounts[1] must be a Dashboard account + * accounts[2] must be a Game account + */ +#define NUM_KA 3 + +extern bool entrypoint(uint8_t *input) { + SolKeyedAccounts ka[NUM_KA]; + uint8_t *data; + uint64_t data_len; + int err = 0; + + if (!sol_deserialize((uint8_t *)input, NUM_KA, ka, &data, &data_len)) { + return false; + } + + // TODO check dashboard and game program ids (how to check now that they are + // not known values) + // TODO check validity of dashboard and game structures contents + if (sizeof(Dashboard) > ka[1].userdata_len) { + sol_print(0, 0, 0xFF, sizeof(Dashboard), ka[2].userdata_len); + return false; + } + Dashboard dashboard; + sol_memcpy(&dashboard, ka[1].userdata, sizeof(dashboard)); + + if (sizeof(Game) > ka[2].userdata_len) { + sol_print(0, 0, 0xFF, sizeof(Game), ka[2].userdata_len); + return false; + } + Game game; + sol_memcpy(&game, ka[2].userdata, sizeof(game)); + if (true != update(&dashboard, &game, ka[2].key)) { + return false; + } + + sol_memcpy(ka[1].userdata, &dashboard, sizeof(dashboard)); + return true; +} diff --git a/programs/bpf/move_funds_c/build.sh b/programs/bpf/move_funds_c/build.sh deleted file mode 100755 index ede12deb7..000000000 --- a/programs/bpf/move_funds_c/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/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 deleted file mode 100755 index af61e414f..000000000 --- a/programs/bpf/move_funds_c/dump.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/debug/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 deleted file mode 100644 index 565ca03d9..000000000 --- a/programs/bpf/move_funds_c/src/move_funds.c +++ /dev/null @@ -1,140 +0,0 @@ - -//#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 0; - } - src += sizeof(uint64_t); - - // TODO fixed iteration loops ok? unrolled? - for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm - // key - ka[i].key = (SolPubkey *)src; - src += SIZE_PUBKEY; - - // tokens - ka[i].tokens = (int64_t *)src; - src += sizeof(int64_t); - - // account userdata - ka[i].userdata_len = *(uint64_t *)src; - src += sizeof(uint64_t); - ka[i].userdata = src; - src += ka[i].userdata_len; - - // program_id - ka[i].program_id = (SolPubkey *)src; - src += SIZE_PUBKEY; - } - // tx userdata - *userdata_len = *(uint64_t *)src; - src += sizeof(uint64_t); - *userdata = src; - - return 1; -} - - -// -- Debug -- - -SOL_FN_PREFIX void print_key(SolPubkey *key) { - for (int j = 0; j < SIZE_PUBKEY; j++) { - sol_print(0, 0, 0, j, key->x[j]); - } -} - -SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) { - for (int j = 0; j < len; j++) { - sol_print(0, 0, 0, j, data[j]); - } -} - -SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, - uint8_t *userdata, uint64_t userdata_len) { - sol_print(0, 0, 0, 0, num_ka); - for (int i = 0; i < num_ka; i++) { - // key - print_key(ka[i].key); - - // tokens - sol_print(0, 0, 0, 0, *ka[i].tokens); - - // account userdata - print_userdata(ka[i].userdata, ka[i].userdata_len); - - // program_id - print_key(ka[i].program_id); - } - // tx userdata - print_userdata(userdata, userdata_len); -} - -uint64_t entrypoint(char *buf) { - SolKeyedAccounts ka[3]; - uint64_t userdata_len; - uint8_t *userdata; - - if (1 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) { - return 1; - } - print_params(3, ka, userdata, userdata_len); - - int64_t tokens = *(int64_t*)userdata; - if (*ka[0].tokens >= tokens) { - *ka[0].tokens -= tokens; - *ka[2].tokens += tokens; - //sol_print(0, 0, *ka[0].tokens, *ka[2].tokens, tokens); - } else { - //sol_print(0, 0, 0xFF, *ka[0].tokens, tokens); - } - return 0; -} - diff --git a/programs/bpf/noop_c/build.sh b/programs/bpf/noop_c/build.sh deleted file mode 100755 index e9718450c..000000000 --- a/programs/bpf/noop_c/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -ex - -OUTDIR="${1:-../../../target/release/}" -THISDIR=$(dirname "$0") -mkdir -p "$OUTDIR" -/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/noop_c.bc -c "$THISDIR"/src/noop.c -/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/noop_c.o "$OUTDIR"/noop_c.bc - -#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/noop_c.o \ No newline at end of file diff --git a/programs/bpf/noop_c/dump.sh b/programs/bpf/noop_c/dump.sh deleted file mode 100755 index 10f5f9e20..000000000 --- a/programs/bpf/noop_c/dump.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/debug/noop_c.o \ No newline at end of file diff --git a/programs/bpf/noop_c/src/noop.c b/programs/bpf/noop_c/src/noop.c deleted file mode 100644 index d01220a70..000000000 --- a/programs/bpf/noop_c/src/noop.c +++ /dev/null @@ -1,133 +0,0 @@ - -//#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 0; - } - src += sizeof(uint64_t); - - // TODO fixed iteration loops ok? unrolled? - for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm - // key - ka[i].key = (SolPubkey *)src; - src += SIZE_PUBKEY; - - // tokens - ka[i].tokens = (int64_t *)src; - src += sizeof(int64_t); - - // account userdata - ka[i].userdata_len = *(uint64_t *)src; - src += sizeof(uint64_t); - ka[i].userdata = src; - src += ka[i].userdata_len; - - // program_id - ka[i].program_id = (SolPubkey *)src; - src += SIZE_PUBKEY; - } - // tx userdata - *userdata_len = *(uint64_t *)src; - src += sizeof(uint64_t); - *userdata = src; - - return 1; -} - - -// -- Debug -- - -SOL_FN_PREFIX void print_key(SolPubkey *key) { - for (int j = 0; j < SIZE_PUBKEY; j++) { - sol_print(0, 0, 0, j, key->x[j]); - } -} - -SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) { - for (int j = 0; j < len; j++) { - sol_print(0, 0, 0, j, data[j]); - } -} - -SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, - uint8_t *userdata, uint64_t userdata_len) { - sol_print(0, 0, 0, 0, num_ka); - for (int i = 0; i < num_ka; i++) { - // key - print_key(ka[i].key); - - // tokens - sol_print(0, 0, 0, 0, *ka[i].tokens); - - // account userdata - print_userdata(ka[i].userdata, ka[i].userdata_len); - - // program_id - print_key(ka[i].program_id); - } - // tx userdata - print_userdata(userdata, userdata_len); -} - -// -- Program entrypoint -- - -uint64_t entrypoint(char *buf) { - SolKeyedAccounts ka[1]; - uint64_t userdata_len; - uint8_t *userdata; - - if (1 != sol_deserialize((uint8_t *)buf, 1, ka, &userdata, &userdata_len)) { - return 0; - } - print_params(1, ka, userdata, userdata_len); - return 1; -} - diff --git a/programs/bpf/noop_rust/Cargo.toml b/programs/bpf/rust/noop/Cargo.toml similarity index 80% rename from programs/bpf/noop_rust/Cargo.toml rename to programs/bpf/rust/noop/Cargo.toml index 5490478d1..721d5e69a 100644 --- a/programs/bpf/noop_rust/Cargo.toml +++ b/programs/bpf/rust/noop/Cargo.toml @@ -8,4 +8,4 @@ license = "Apache-2.0" [dependencies] rbpf = "0.1.0" -solana-sdk = { path = "../../../sdk", version = "0.11.0" } +solana-sdk = { path = "../../../../sdk", version = "0.11.0" } diff --git a/programs/bpf/noop_rust/build.sh b/programs/bpf/rust/noop/build.sh similarity index 100% rename from programs/bpf/noop_rust/build.sh rename to programs/bpf/rust/noop/build.sh diff --git a/programs/bpf/noop_rust/dump.sh b/programs/bpf/rust/noop/dump.sh similarity index 100% rename from programs/bpf/noop_rust/dump.sh rename to programs/bpf/rust/noop/dump.sh diff --git a/programs/bpf/noop_rust/src/lib.rs b/programs/bpf/rust/noop/src/lib.rs similarity index 100% rename from programs/bpf/noop_rust/src/lib.rs rename to programs/bpf/rust/noop/src/lib.rs diff --git a/programs/bpf/tictactoe_c/build.sh b/programs/bpf/tictactoe_c/build.sh deleted file mode 100755 index b0160fe06..000000000 --- a/programs/bpf/tictactoe_c/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/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 deleted file mode 100755 index 28b770bb0..000000000 --- a/programs/bpf/tictactoe_c/dump.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/debug/tictactoe_c.o \ No newline at end of file diff --git a/programs/bpf/tictactoe_dashboard_c/build.sh b/programs/bpf/tictactoe_dashboard_c/build.sh deleted file mode 100755 index 8920049e1..000000000 --- a/programs/bpf/tictactoe_dashboard_c/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/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_dashboard_c.bc -c "$THISDIR"/src/tictactoe_dashboard.c -/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/tictactoe_dashboard_c.o "$OUTDIR"/tictactoe_dashboard_c.bc - -# /usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/tictactoe_dashboard_c.o \ No newline at end of file diff --git a/programs/bpf/tictactoe_dashboard_c/dump.sh b/programs/bpf/tictactoe_dashboard_c/dump.sh deleted file mode 100755 index 237921d0f..000000000 --- a/programs/bpf/tictactoe_dashboard_c/dump.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/debug/tictactoe_dashboard_c.o \ No newline at end of file diff --git a/programs/bpf/tictactoe_dashboard_c/src/tictactoe_dashboard.c b/programs/bpf/tictactoe_dashboard_c/src/tictactoe_dashboard.c deleted file mode 100644 index 2c9d439bd..000000000 --- a/programs/bpf/tictactoe_dashboard_c/src/tictactoe_dashboard.c +++ /dev/null @@ -1,236 +0,0 @@ -//#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 long unsigned int uint32_t; -typedef long int int32_t; -typedef unsigned char uint8_t; - -typedef enum { false = 0, true } bool; - -// 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_trace() sol_print(0, 0, 0xFF, 0xFF, (__LINE__)); -#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; -} - -#define SIZE_PUBKEY 32 -typedef struct { - uint8_t x[SIZE_PUBKEY]; -} SolPubkey; - -SOL_FN_PREFIX bool SolPubkey_same(SolPubkey *one, SolPubkey *two) { - for (int i = 0; i < SIZE_PUBKEY; i++) { - if (one->x[i] != two->x[i]) { - return false; - } - } - return true; -} - -typedef struct { - SolPubkey *key; - int64_t tokens; - uint64_t userdata_len; - uint8_t *userdata; - SolPubkey *program_id; -} SolKeyedAccounts; - -SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, - SolKeyedAccounts *ka, uint8_t **tx_data, - uint64_t *tx_data_len) { - if (num_ka != *(uint64_t *)src) { - return 0; - } - src += sizeof(uint64_t); - - // TODO fixed iteration loops ok? unrolled? - for (int i = 0; i < num_ka; - i++) { // TODO this should end up unrolled, confirm - // key - ka[i].key = (SolPubkey *)src; - src += SIZE_PUBKEY; - - // tokens - ka[i].tokens = *(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 - *tx_data_len = *(uint64_t *)src; - src += sizeof(uint64_t); - *tx_data = src; - - return 1; -} - -// -- Debug -- - -SOL_FN_PREFIX void print_key(SolPubkey *key) { - for (int j = 0; j < SIZE_PUBKEY; j++) { - sol_print(0, 0, 0, j, key->x[j]); - } -} - -SOL_FN_PREFIX void print_data(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 *tx_data, uint64_t tx_data_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_data(ka[i].userdata, ka[i].userdata_len); - - // program_id - print_key(ka[i].program_id); - } - // tx userdata - print_data(tx_data, tx_data_len); -} - -// -- TicTacToe Dashboard -- - -// TODO put this in a common place for both tictactoe and tictactoe_dashboard -typedef enum { - State_Waiting, - State_XMove, - State_OMove, - State_XWon, - State_OWon, - State_Draw, -} State; - -// TODO put this in a common place for both tictactoe and tictactoe_dashboard -typedef enum { BoardItem_F, BoardItem_X, BoardItem_O } BoardItem; - -// TODO put this in a common place for both tictactoe and tictactoe_dashboard -typedef struct { - SolPubkey player_x; - SolPubkey player_o; - State state; - BoardItem board[9]; - int64_t keep_alive[2]; -} Game; - -#define MAX_GAMES_TRACKED 5 - -typedef struct { - // Latest pending game - SolPubkey pending; - // Last N completed games (0 is the latest) - SolPubkey completed[MAX_GAMES_TRACKED]; - // Index into completed pointing to latest game completed - uint32_t latest_game; - // Total number of completed games - uint32_t total; -} Dashboard; - -SOL_FN_PREFIX bool update(Dashboard *self, Game *game, SolPubkey *game_pubkey) { - switch (game->state) { - case State_Waiting: - sol_memcpy(&self->pending, game_pubkey, SIZE_PUBKEY); - break; - case State_XMove: - case State_OMove: - // Nothing to do. In progress games are not managed by the dashboard - break; - case State_XWon: - case State_OWon: - case State_Draw: - for (int i = 0; i < MAX_GAMES_TRACKED; i++) { - if (SolPubkey_same(&self->completed[i], game_pubkey)) { - // TODO: Once the PoH height is exposed to programs, it could be used - // to ensure - // that old games are not being re-added and causing total to - // increment incorrectly. - return false; - } - } - self->total += 1; - self->latest_game = (self->latest_game + 1) % MAX_GAMES_TRACKED; - sol_memcpy(self->completed[self->latest_game].x, game_pubkey, - SIZE_PUBKEY); - break; - - default: - break; - } - return true; -} - -// accounts[0] doesn't matter, anybody can cause a dashboard update -// accounts[1] must be a Dashboard account -// accounts[2] must be a Game account -uint64_t entrypoint(uint8_t *buf) { - SolKeyedAccounts ka[3]; - uint64_t tx_data_len; - uint8_t *tx_data; - int err = 0; - - if (1 != sol_deserialize(buf, 3, ka, &tx_data, &tx_data_len)) { - return false; - } - - // TODO check dashboard and game program ids (how to check now that they are - // not know values) - // TODO check validity of dashboard and game structures contents - if (sizeof(Dashboard) > ka[1].userdata_len) { - sol_print(0, 0, 0xFF, sizeof(Dashboard), ka[2].userdata_len); - return false; - } - Dashboard dashboard; - sol_memcpy(&dashboard, ka[1].userdata, sizeof(dashboard)); - - if (sizeof(Game) > ka[2].userdata_len) { - sol_print(0, 0, 0xFF, sizeof(Game), ka[2].userdata_len); - return false; - } - Game game; - sol_memcpy(&game, ka[2].userdata, sizeof(game)); - if (true != update(&dashboard, &game, ka[2].key)) { - return false; - } - - sol_memcpy(ka[1].userdata, &dashboard, sizeof(dashboard)); - return true; -} diff --git a/programs/native/bpf_loader/src/lib.rs b/programs/native/bpf_loader/src/lib.rs index 81870d17b..1c736d0fa 100644 --- a/programs/native/bpf_loader/src/lib.rs +++ b/programs/native/bpf_loader/src/lib.rs @@ -18,17 +18,6 @@ use std::io::Error; use std::mem; use std::sync::{Once, ONCE_INIT}; -fn create_vm(prog: &[u8]) -> Result { - let mut vm = rbpf::EbpfVmRaw::new(None)?; - vm.set_verifier(bpf_verifier::check)?; - vm.set_program(&prog)?; - vm.register_helper( - rbpf::helpers::BPF_TRACE_PRINTK_IDX, - rbpf::helpers::bpf_trace_printf, - )?; - Ok(vm) -} - #[allow(dead_code)] fn dump_program(key: &Pubkey, prog: &[u8]) { let mut eight_bytes: Vec = Vec::new(); @@ -43,6 +32,34 @@ fn dump_program(key: &Pubkey, prog: &[u8]) { } } +pub fn helper_printf(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 { + println!( + "bpf_trace_printf: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}", + arg1, arg2, arg3, arg4, arg5 + ); + let size_arg = |x| { + if x == 0 { + 1 + } else { + (x as f64).log(16.0).floor() as u64 + 1 + } + }; + "bpf_trace_printf: 0x, 0x, 0x, 0x, 0x\n".len() as u64 + + size_arg(arg1) + + size_arg(arg2) + + size_arg(arg3) + + size_arg(arg4) + + size_arg(arg5) +} + +fn create_vm(prog: &[u8]) -> Result { + let mut vm = rbpf::EbpfVmRaw::new(None)?; + vm.set_verifier(bpf_verifier::check)?; + vm.set_program(&prog)?; + vm.register_helper(rbpf::helpers::BPF_TRACE_PRINTK_IDX, helper_printf)?; + Ok(vm) +} + fn serialize_parameters(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> Vec { assert_eq!(32, mem::size_of::()); diff --git a/tests/programs.rs b/tests/programs.rs index 380f53513..88ece0efe 100644 --- a/tests/programs.rs +++ b/tests/programs.rs @@ -22,9 +22,6 @@ use std::env; #[cfg(feature = "bpf_c")] use std::path::PathBuf; -/// BPF program file prefixes -#[cfg(feature = "bpf_c")] -const PLATFORM_FILE_PREFIX_BPF: &str = ""; /// BPF program file extension #[cfg(feature = "bpf_c")] const PLATFORM_FILE_EXTENSION_BPF: &str = "o"; @@ -34,14 +31,14 @@ pub const PLATFORM_SECTION_C: &str = ".text.entrypoint"; /// Create a BPF program file name #[cfg(feature = "bpf_c")] fn create_bpf_path(name: &str) -> PathBuf { - let pathbuf = { + let mut pathbuf = { let current_exe = env::current_exe().unwrap(); PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) }; - pathbuf.join( - PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name) - .with_extension(PLATFORM_FILE_EXTENSION_BPF), - ) + pathbuf.push("bpf/"); + pathbuf.push(name); + pathbuf.set_extension(PLATFORM_FILE_EXTENSION_BPF); + pathbuf } fn check_tx_results(bank: &Bank, tx: &Transaction, result: Vec>) { @@ -278,7 +275,7 @@ fn test_program_builtin_bpf_noop() { let loader = Loader::new_bpf(); let program = Program::new( &loader, - elf::File::open_path(&create_bpf_path("noop_c")) + elf::File::open_path(&create_bpf_path("noop")) .unwrap() .get_section(PLATFORM_SECTION_C) .unwrap() @@ -310,7 +307,7 @@ fn test_program_bpf_noop_c() { let loader = Loader::new_dynamic("bpf_loader"); let program = Program::new( &loader, - elf::File::open_path(&create_bpf_path("noop_c")) + elf::File::open_path(&create_bpf_path("noop")) .unwrap() .get_section(PLATFORM_SECTION_C) .unwrap() @@ -522,7 +519,7 @@ fn test_program_bpf_tictactoe_c() { let loader = Loader::new_dynamic("bpf_loader"); let program = Program::new( &loader, - elf::File::open_path(&create_bpf_path("tictactoe_c")) + elf::File::open_path(&create_bpf_path("tictactoe")) .unwrap() .get_section(PLATFORM_SECTION_C) .unwrap() @@ -555,7 +552,7 @@ fn test_program_bpf_tictactoe_dashboard_c() { let loader = Loader::new_dynamic("bpf_loader"); let ttt_program = Program::new( &loader, - elf::File::open_path(&create_bpf_path("tictactoe_c")) + elf::File::open_path(&create_bpf_path("tictactoe")) .unwrap() .get_section(PLATFORM_SECTION_C) .unwrap() @@ -590,7 +587,7 @@ fn test_program_bpf_tictactoe_dashboard_c() { let dashboard_program = Program::new( &loader, - elf::File::open_path(&create_bpf_path("tictactoe_dashboard_c")) + elf::File::open_path(&create_bpf_path("tictactoe_dashboard")) .unwrap() .get_section(PLATFORM_SECTION_C) .unwrap()