diff --git a/Cargo.toml b/Cargo.toml index ca0234c3d..982fa7fef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,9 +109,10 @@ sys-info = "0.5.6" tokio = "0.1" tokio-codec = "0.1" untrusted = "0.6.2" -solua = { path = "programs/native/solua" } move_funds = { path = "programs/native/move_funds" } noop = { path = "programs/native/noop" } +sobpf = { path = "programs/native/sobpf" } +solua = { path = "programs/native/solua" } [[bench]] name = "bank" @@ -136,8 +137,9 @@ name = "chacha" members = [ ".", "common", - "programs/native/solua", "programs/native/move_funds", "programs/native/noop", + "programs/native/sobpf", + "programs/native/solua", "programs/bpf/noop_rust", ] diff --git a/build.rs b/build.rs index 7d22a4a6e..7474160ad 100644 --- a/build.rs +++ b/build.rs @@ -23,6 +23,15 @@ fn main() { if bpf_c { let out_dir = "target/".to_string() + &env::var("PROFILE").unwrap(); + println!("cargo:rerun-if-changed=programs/bpf/noop_c/build.sh"); + println!("cargo:rerun-if-changed=programs/bpf/noop_c/src/noop.c"); + println!("cargo:warning=(not a warning) Compiling noop_c"); + let status = Command::new("programs/bpf/noop_c/build.sh") + .arg(&out_dir) + .status() + .expect("Failed to call noop_c build script"); + assert!(status.success()); + println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/build.sh"); println!("cargo:rerun-if-changed=programs/bpf/move_funds_c/src/move_funds.c"); println!("cargo:warning=(not a warning) Compiling move_funds_c"); diff --git a/common/src/account.rs b/common/src/account.rs index 5abd64200..01ec8fdc5 100644 --- a/common/src/account.rs +++ b/common/src/account.rs @@ -11,14 +11,23 @@ pub struct Account { pub userdata: Vec, /// contract id this contract belongs to pub program_id: Pubkey, + + /// this account contains a program (and is strictly read-only) + pub executable: bool, + + /// the loader for this program (Pubkey::default() for no loader) + pub loader_program_id: Pubkey, } impl Account { + // TODO do we want to add executable and leader_program_id even though they should always be false/default? pub fn new(tokens: i64, space: usize, program_id: Pubkey) -> Account { Account { tokens, userdata: vec![0u8; space], program_id, + executable: false, + loader_program_id: Pubkey::default(), } } } diff --git a/common/src/lib.rs b/common/src/lib.rs index 444fd3c47..b5cd719c0 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod loader_instruction; pub mod pubkey; extern crate bincode; extern crate bs58; diff --git a/common/src/loader_instruction.rs b/common/src/loader_instruction.rs new file mode 100644 index 000000000..99ea5e347 --- /dev/null +++ b/common/src/loader_instruction.rs @@ -0,0 +1,18 @@ +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum LoaderInstruction { + /// Write program data into an account + /// + /// * key[0] - the account to write into. + /// + /// The transaction must be signed by key[0] + Write { offset: u32, bytes: Vec }, + + /// Finalize an account loaded with program data for execution. + /// The exact preparation steps is loader specific but on success the loader must set the executable + /// bit of the Account + /// + /// * key[0] - the account to prepare for execution + /// + /// The transaction must be signed by key[0] + Finalize, +} diff --git a/programs/bpf/move_funds_c/src/move_funds.c b/programs/bpf/move_funds_c/src/move_funds.c index 55b8f283c..565ca03d9 100644 --- a/programs/bpf/move_funds_c/src/move_funds.c +++ b/programs/bpf/move_funds_c/src/move_funds.c @@ -24,7 +24,7 @@ typedef struct { typedef struct { SolPubkey *key; - int64_t tokens; + int64_t* tokens; uint64_t userdata_len; uint8_t *userdata; SolPubkey *program_id; @@ -50,7 +50,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) { SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka, uint8_t **userdata, uint64_t *userdata_len) { if (num_ka != *(uint64_t *)src) { - return -1; + return 0; } src += sizeof(uint64_t); @@ -61,8 +61,8 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount src += SIZE_PUBKEY; // tokens - ka[i].tokens = *(uint64_t *)src; - src += sizeof(uint64_t); + ka[i].tokens = (int64_t *)src; + src += sizeof(int64_t); // account userdata ka[i].userdata_len = *(uint64_t *)src; @@ -79,7 +79,7 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount src += sizeof(uint64_t); *userdata = src; - return 0; + return 1; } @@ -105,7 +105,7 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, print_key(ka[i].key); // tokens - sol_print(0, 0, 0, 0, ka[i].tokens); + sol_print(0, 0, 0, 0, *ka[i].tokens); // account userdata print_userdata(ka[i].userdata, ka[i].userdata_len); @@ -117,14 +117,24 @@ SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, print_userdata(userdata, userdata_len); } -void entrypoint(char *buf) { +uint64_t 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; + 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 new file mode 100755 index 000000000..e9718450c --- /dev/null +++ b/programs/bpf/noop_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"/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 new file mode 100755 index 000000000..7c0becfcd --- /dev/null +++ b/programs/bpf/noop_c/dump.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/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 new file mode 100644 index 000000000..d01220a70 --- /dev/null +++ b/programs/bpf/noop_c/src/noop.c @@ -0,0 +1,133 @@ + +//#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/tictactoe_c/src/tictactoe.c b/programs/bpf/tictactoe_c/src/tictactoe.c index d501fc896..0ea42dd23 100644 --- a/programs/bpf/tictactoe_c/src/tictactoe.c +++ b/programs/bpf/tictactoe_c/src/tictactoe.c @@ -49,7 +49,7 @@ SOL_FN_PREFIX void _sol_panic(uint64_t line) { SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka, uint8_t **userdata, uint64_t *userdata_len) { if (num_ka != *(uint64_t *)src) { - return -1; + return 0; } src += sizeof(uint64_t); @@ -78,9 +78,55 @@ SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccount src += sizeof(uint64_t); *userdata = src; - return 0; + return 1; } +// // -- Debug -- + +SOL_FN_PREFIX void print_key(SolPubkey *key) { + for (int j = 0; j < SIZE_PUBKEY; j++) { + sol_print(0, 0, 0, j, key->x[j]); + } +} + +SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) { + for (int j = 0; j < len; j++) { + sol_print(0, 0, 0, j, data[j]); + } +} + +SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, + uint8_t *userdata, uint64_t userdata_len) { + sol_print(0, 0, 0, 0, num_ka); + for (int i = 0; i < num_ka; i++) { + // key + print_key(ka[i].key); + + // tokens + sol_print(0, 0, 0, 0, ka[i].tokens); + + // account userdata + print_userdata(ka[i].userdata, ka[i].userdata_len); + + // program_id + print_key(ka[i].program_id); + } + // tx userdata + print_userdata(userdata, userdata_len); +} + +// void entrypoint(char *buf) { +// SolKeyedAccounts ka[3]; +// uint64_t userdata_len; +// uint8_t *userdata; + +// if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) { +// return; +// } + +// print_params(3, ka, userdata, userdata_len); +// } + // -- TicTacToe -- // Board Coodinates @@ -279,92 +325,48 @@ SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player, return Result_Ok; } -void entrypoint(uint8_t *buf) { - SolKeyedAccounts ka[3]; +uint64_t entrypoint(uint8_t *buf) { + SolKeyedAccounts ka[4]; uint64_t userdata_len; uint8_t *userdata; int err = 0; - if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) { - sol_panic(); + if (1 != sol_deserialize(buf, 4, ka, &userdata, &userdata_len)) { + return 0; } - if (sizeof(Game) > ka[1].userdata_len) { - sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len); - sol_panic(); + if (sizeof(Game) > ka[2].userdata_len) { + sol_print(0, 0, 0xFF, sizeof(Game), ka[2].userdata_len); + return 0; } Game game; - sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len); + sol_memcpy(&game, ka[2].userdata, ka[2].userdata_len); Command command = *userdata; - sol_print(0, 0, 0, 0, command); + //sol_print(0, 0, 0, 0, command); switch (command) { case Command_Init: - game_create(&game, ka[2].key); + game_create(&game, ka[3].key); break; case Command_Join: - err = game_join(&game, ka[0].key, userdata[8]); + err = game_join(&game, ka[3].key, userdata[8]); break; case Command_KeepAlive: - err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0); + err = game_keep_alive(&game, ka[3].key, /*TODO*/ 0); break; case Command_Move: - err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]); + err = game_next_move(&game, ka[3].key, userdata[8], userdata[9]); break; default: - sol_panic(); + return 0; } - sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len); + sol_memcpy(ka[2].userdata, &game, ka[2].userdata_len); sol_print(0, 0, 0, err, game.state); + return 1; } -// // -- Debug -- - -// SOL_FN_PREFIX void print_key(SolPubkey *key) { -// for (int j = 0; j < SIZE_PUBKEY; j++) { -// sol_print(0, 0, 0, j, key->x[j]); -// } -// } - -// SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) { -// for (int j = 0; j < len; j++) { -// sol_print(0, 0, 0, j, data[j]); -// } -// } - -// SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka, -// uint8_t *userdata, uint64_t userdata_len) { -// sol_print(0, 0, 0, 0, num_ka); -// for (int i = 0; i < num_ka; i++) { -// // key -// print_key(ka[i].key); - -// // tokens -// sol_print(0, 0, 0, 0, ka[i].tokens); - -// // account userdata -// print_userdata(ka[i].userdata, ka[i].userdata_len); - -// // program_id -// print_key(ka[i].program_id); -// } -// // tx userdata -// print_userdata(userdata, userdata_len); -// } - -// void entrypoint(char *buf) { -// SolKeyedAccounts ka[3]; -// uint64_t userdata_len; -// uint8_t *userdata; - -// if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata_len, &userdata)) { -// return; -// } - -// print_params(3, ka, userdata, userdata_len); -// } diff --git a/programs/native/move_funds/src/lib.rs b/programs/native/move_funds/src/lib.rs index 1f7c9dd25..bbc0716e9 100644 --- a/programs/native/move_funds/src/lib.rs +++ b/programs/native/move_funds/src/lib.rs @@ -5,16 +5,16 @@ use bincode::deserialize; use solana_program_interface::account::KeyedAccount; #[no_mangle] -pub extern "C" fn process(infos: &mut Vec, data: &[u8]) -> bool { +pub extern "C" fn process(keyed_accounts: &mut Vec, data: &[u8]) -> bool { let tokens: i64 = deserialize(data).unwrap(); - if infos[0].account.tokens >= tokens { - infos[0].account.tokens -= tokens; - infos[1].account.tokens += tokens; + if keyed_accounts[0].account.tokens >= tokens { + keyed_accounts[0].account.tokens -= tokens; + keyed_accounts[1].account.tokens += tokens; true } else { println!( "Insufficient funds, asked {}, only had {}", - tokens, infos[0].account.tokens + tokens, keyed_accounts[0].account.tokens ); false } @@ -37,12 +37,12 @@ mod tests { accounts[1].tokens = 1; { - let mut infos: Vec = Vec::new(); + let mut keyed_accounts: Vec = Vec::new(); for (key, account) in keys.iter().zip(&mut accounts).collect::>() { infos.push(KeyedAccount { key, account }); } - process(&mut infos, &data); + process(&mut keyed_accounts, &data); } assert_eq!(0, accounts[0].tokens); assert_eq!(101, accounts[1].tokens); diff --git a/programs/native/noop/src/lib.rs b/programs/native/noop/src/lib.rs index 4d06acbbe..7ae37a4ed 100644 --- a/programs/native/noop/src/lib.rs +++ b/programs/native/noop/src/lib.rs @@ -3,8 +3,8 @@ extern crate solana_program_interface; use solana_program_interface::account::KeyedAccount; #[no_mangle] -pub extern "C" fn process(infos: &mut Vec, data: &[u8]) -> bool { - println!("noop: AccountInfos: {:#?}", infos); - println!("noop: data: {:#?}", data); +pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> bool { + println!("noop: keyed_accounts: {:#?}", keyed_accounts); + println!("noop: data: {:?}", data); true } diff --git a/programs/native/sobpf/Cargo.toml b/programs/native/sobpf/Cargo.toml new file mode 100644 index 000000000..149885075 --- /dev/null +++ b/programs/native/sobpf/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "sobpf" +version = "0.1.0" +authors = ["Solana Maintainers "] + +[dependencies] +bincode = "1.0.0" +byteorder = "1.2.1" +elf = "0.0.10" +libc = "0.2.43" +log = "0.4.2" +rbpf = { git = "https://github.com/solana-labs/rbpf" } +serde = "1.0.27" +serde_derive = "1.0.27" +solana_program_interface = { path = "../../../common" } + +[lib] +name = "sobpf" +crate-type = ["cdylib"] + diff --git a/programs/native/sobpf/src/bpf_verifier.rs b/programs/native/sobpf/src/bpf_verifier.rs new file mode 100644 index 000000000..ca0045834 --- /dev/null +++ b/programs/native/sobpf/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/programs/native/sobpf/src/lib.rs b/programs/native/sobpf/src/lib.rs new file mode 100644 index 000000000..c34aa85f6 --- /dev/null +++ b/programs/native/sobpf/src/lib.rs @@ -0,0 +1,195 @@ +extern crate bincode; +extern crate byteorder; +extern crate elf; +extern crate libc; +extern crate rbpf; +extern crate solana_program_interface; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate log; + +pub mod bpf_verifier; + +use bincode::{deserialize, serialize}; +use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; +use solana_program_interface::account::KeyedAccount; +use solana_program_interface::loader_instruction::LoaderInstruction; +use solana_program_interface::pubkey::Pubkey; +use std::env; +use std::io::prelude::*; +use std::mem; +use std::path::PathBuf; +use std::str; + +/// Dynamic link library prefixs +const PLATFORM_FILE_PREFIX_BPF: &str = ""; + +/// Dynamic link library file extension specific to the platform +const PLATFORM_FILE_EXTENSION_BPF: &str = "o"; + +/// Section name +pub const PLATFORM_SECTION_RS: &str = ".text,entrypoint"; +pub const PLATFORM_SECTION_C: &str = ".text.entrypoint"; + +fn create_path(name: &str) -> PathBuf { + let pathbuf = { + let current_exe = env::current_exe().unwrap(); + PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) + }; + + pathbuf.join( + PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name) + .with_extension(PLATFORM_FILE_EXTENSION_BPF), + ) +} + +#[allow(dead_code)] +fn dump_prog(name: &str, prog: &[u8]) { + let mut eight_bytes: Vec = Vec::new(); + println!("BPF Program: {}", name); + for i in prog.iter() { + if eight_bytes.len() >= 7 { + println!("{:02X?}", eight_bytes); + eight_bytes.clear(); + } else { + eight_bytes.push(i.clone()); + } + } +} + +fn serialize_state(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> Vec { + assert_eq!(32, mem::size_of::()); + + let mut v: Vec = Vec::new(); + v.write_u64::(keyed_accounts.len() as u64) + .unwrap(); + for info in keyed_accounts.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(); + } + v.write_u64::(data.len() as u64).unwrap(); + v.write_all(data).unwrap(); + v +} + +fn deserialize_state(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) { + assert_eq!(32, mem::size_of::()); + + let mut start = mem::size_of::(); + for info in keyed_accounts.iter_mut() { + start += mem::size_of::(); // skip pubkey + info.account.tokens = LittleEndian::read_i64(&buffer[start..]); + + start += mem::size_of::() // skip tokens + + mem::size_of::(); // skip length tag + let end = start + info.account.userdata.len(); + info.account.userdata.clone_from_slice(&buffer[start..end]); + + start += info.account.userdata.len() // skip userdata + + mem::size_of::(); // skip program_id + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum BpfLoader { + File { name: String }, + Bytes { bytes: Vec }, +} + +#[no_mangle] +pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool { + if keyed_accounts[0].account.executable { + let prog: Vec; + if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) { + match program { + BpfLoader::File { name } => { + trace!("Call Bpf with file {:?}", name); + let path = create_path(&name); + let file = match elf::File::open_path(&path) { + Ok(f) => f, + Err(e) => { + warn!("Error opening ELF {:?}: {:?}", path, e); + return false; + } + }; + + let text_section = match file.get_section(PLATFORM_SECTION_RS) { + Some(s) => s, + None => match file.get_section(PLATFORM_SECTION_C) { + Some(s) => s, + None => { + warn!("Failed to find elf section {:?}", PLATFORM_SECTION_C); + return false; + } + }, + }; + prog = text_section.data.clone(); + } + BpfLoader::Bytes { bytes } => { + trace!("Call Bpf with bytes"); + prog = bytes; + } + } + } else { + warn!("deserialize failed: {:?}", tx_data); + return false; + } + trace!("Call BPF, {} Instructions", prog.len() / 8); + + let mut vm = rbpf::EbpfVmRaw::new(&prog, Some(bpf_verifier::verifier)); + vm.register_helper( + rbpf::helpers::BPF_TRACE_PRINTK_IDX, + rbpf::helpers::bpf_trace_printf, + ); + + let mut v = serialize_state(&mut keyed_accounts[1..], &tx_data); + if 0 == vm.prog_exec(v.as_mut_slice()) { + warn!("BPF program failed"); + return false; + } + deserialize_state(&mut keyed_accounts[1..], &v); + } else if let Ok(instruction) = deserialize(tx_data) { + match instruction { + LoaderInstruction::Write { offset, bytes } => { + trace!("BPFLoader::Write offset {} bytes {:?}", offset, bytes); + let offset = offset as usize; + if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() { + return false; + } + let name = match str::from_utf8(&bytes) { + Ok(s) => s.to_string(), + Err(e) => { + println!("Invalid UTF-8 sequence: {}", e); + return false; + } + }; + trace!("name: {:?}", name); + let s = serialize(&BpfLoader::File { name }).unwrap(); + keyed_accounts[0] + .account + .userdata + .splice(0..s.len(), s.iter().cloned()); + } + + LoaderInstruction::Finalize => { + keyed_accounts[0].account.executable = true; + keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id; + keyed_accounts[0].account.program_id = *keyed_accounts[0].key; + trace!( + "BPFLoader::Finalize prog: {:?} loader {:?}", + keyed_accounts[0].account.program_id, + keyed_accounts[0].account.loader_program_id + ); + } + } + } else { + warn!("Invalid program transaction: {:?}", tx_data); + return false; + } + true +} diff --git a/programs/native/solua/Cargo.toml b/programs/native/solua/Cargo.toml index 2c2cdda19..ddc88b035 100644 --- a/programs/native/solua/Cargo.toml +++ b/programs/native/solua/Cargo.toml @@ -4,7 +4,11 @@ version = "0.1.0" authors = ["Solana Maintainers "] [dependencies] +bincode = "1.0.0" +log = "0.4.2" rlua = "0.15.2" +serde = "1.0.27" +serde_derive = "1.0.27" solana_program_interface = { path = "../../../common" } [dev-dependencies] diff --git a/programs/native/solua/src/lib.rs b/programs/native/solua/src/lib.rs index 13b8a3375..fbe89fe6f 100644 --- a/programs/native/solua/src/lib.rs +++ b/programs/native/solua/src/lib.rs @@ -1,10 +1,23 @@ +extern crate bincode; extern crate rlua; extern crate solana_program_interface; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate log; +use bincode::{deserialize, serialize}; use rlua::{Lua, Result, Table}; use solana_program_interface::account::KeyedAccount; +use solana_program_interface::loader_instruction::LoaderInstruction; use std::str; +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum LuaLoader { + File { name: String }, + Bytes { bytes: Vec }, +} + /// Make KeyAccount values available to Lua. fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Result<()> { let accounts = lua.create_table()?; @@ -21,7 +34,7 @@ fn set_accounts(lua: &Lua, name: &str, keyed_accounts: &[KeyedAccount]) -> Resul } /// Commit the new KeyedAccount values. -fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec) -> Result<()> { +fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut [KeyedAccount]) -> Result<()> { let globals = lua.globals(); let accounts: Table = globals.get(name)?; for (i, keyed_account) in keyed_accounts.into_iter().enumerate() { @@ -33,7 +46,7 @@ fn update_accounts(lua: &Lua, name: &str, keyed_accounts: &mut Vec Ok(()) } -fn run_lua(keyed_accounts: &mut Vec, code: &str, data: &[u8]) -> Result<()> { +fn run_lua(keyed_accounts: &mut [KeyedAccount], code: &str, data: &[u8]) -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); let data_str = lua.create_string(data)?; @@ -45,10 +58,67 @@ fn run_lua(keyed_accounts: &mut Vec, code: &str, data: &[u8]) -> R } #[no_mangle] -pub extern "C" fn process(keyed_accounts: &mut Vec, data: &[u8]) -> bool { - let code_data = keyed_accounts[0].account.userdata.clone(); - let code = str::from_utf8(&code_data).unwrap(); - run_lua(keyed_accounts, &code, data).unwrap(); +pub extern "C" fn process(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool { + if keyed_accounts[0].account.executable { + let prog: Vec; + if let Ok(program) = deserialize(&keyed_accounts[0].account.userdata) { + match program { + LuaLoader::File { name } => { + trace!("Call Lua with file {:?}", name); + panic!("Not supported"); + } + LuaLoader::Bytes { bytes } => { + trace!("Call Lua with bytes, code size {}", bytes.len()); + prog = bytes; + } + } + } else { + warn!("deserialize failed: {:?}", tx_data); + return false; + } + + let code = str::from_utf8(&prog).unwrap(); + match run_lua(&mut keyed_accounts[1..], &code, tx_data) { + Ok(()) => { + trace!("Lua success"); + return true; + } + Err(e) => { + warn!("Lua Error: {:#?}", e); + return false; + } + } + } else if let Ok(instruction) = deserialize(tx_data) { + match instruction { + LoaderInstruction::Write { offset, bytes } => { + trace!("LuaLoader::Write offset {} bytes {:?}", offset, bytes); + let offset = offset as usize; + if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() { + warn!("program overflow offset {} len {}", offset, bytes.len()); + return false; + } + let s = serialize(&LuaLoader::Bytes { bytes }).unwrap(); + keyed_accounts[0] + .account + .userdata + .splice(0..s.len(), s.iter().cloned()); + } + + LoaderInstruction::Finalize => { + keyed_accounts[0].account.executable = true; + keyed_accounts[0].account.loader_program_id = keyed_accounts[0].account.program_id; + keyed_accounts[0].account.program_id = *keyed_accounts[0].key; + trace!( + "LuaLoader::Finalize prog: {:?} loader {:?}", + keyed_accounts[0].account.program_id, + keyed_accounts[0].account.loader_program_id + ); + } + } + } else { + warn!("Invalid program transaction: {:?}", tx_data); + return false; + } true } @@ -98,12 +168,13 @@ mod tests { #[test] fn test_move_funds_with_lua_via_process() { - let userdata = r#" + let bytes = r#" local tokens, _ = string.unpack("I", data) accounts[1].tokens = accounts[1].tokens - tokens accounts[2].tokens = accounts[2].tokens + tokens "#.as_bytes() .to_vec(); + let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap(); let alice_pubkey = Pubkey::default(); let bob_pubkey = Pubkey::default(); @@ -111,89 +182,26 @@ mod tests { let mut accounts = [ ( - alice_pubkey, + Pubkey::default(), Account { - tokens: 100, + tokens: 1, userdata, program_id, + executable: true, + loader_program_id: Pubkey::default(), }, ), + (alice_pubkey, Account::new(100, 0, program_id)), (bob_pubkey, Account::new(1, 0, program_id)), ]; let data = serialize(&10u64).unwrap(); process(&mut create_keyed_accounts(&mut accounts), &data); - assert_eq!(accounts[0].1.tokens, 90); - assert_eq!(accounts[1].1.tokens, 11); + assert_eq!(accounts[1].1.tokens, 90); + assert_eq!(accounts[2].1.tokens, 11); process(&mut create_keyed_accounts(&mut accounts), &data); - assert_eq!(accounts[0].1.tokens, 80); - assert_eq!(accounts[1].1.tokens, 21); - } - - #[test] - fn test_move_funds_with_lua_via_process_and_terminate() { - let userdata = r#" - local tokens, _ = string.unpack("I", data) - accounts[1].tokens = accounts[1].tokens - tokens - accounts[2].tokens = accounts[2].tokens + tokens - accounts[1].userdata = "" - "#.as_bytes() - .to_vec(); - - let alice_pubkey = Pubkey::default(); - let bob_pubkey = Pubkey::default(); - let program_id = Pubkey::default(); - - let mut accounts = [ - ( - alice_pubkey, - Account { - tokens: 100, - userdata, - program_id, - }, - ), - (bob_pubkey, Account::new(1, 0, program_id)), - ]; - let data = serialize(&10).unwrap(); - process(&mut create_keyed_accounts(&mut accounts), &data); - assert_eq!(accounts[0].1.tokens, 90); - assert_eq!(accounts[1].1.tokens, 11); - - // Verify the program modified itself to a no-op. - process(&mut create_keyed_accounts(&mut accounts), &data); - assert_eq!(accounts[0].1.tokens, 90); - assert_eq!(accounts[1].1.tokens, 11); - } - - #[test] - fn test_abort_tx_with_lua() { - let userdata = r#" - if data == accounts[1].key then - accounts[1].userdata = "" - end - "#.as_bytes() - .to_vec(); - - let alice_pubkey = Pubkey::default(); - let program_id = Pubkey::default(); - - let mut accounts = [( - alice_pubkey, - Account { - tokens: 100, - userdata, - program_id, - }, - )]; - - // Abort - let data = alice_pubkey.to_string().as_bytes().to_vec(); - - assert_ne!(accounts[0].1.userdata, vec![]); - process(&mut create_keyed_accounts(&mut accounts), &data); - assert_eq!(accounts[0].1.tokens, 100); - assert_eq!(accounts[0].1.userdata, vec![]); + assert_eq!(accounts[1].1.tokens, 80); + assert_eq!(accounts[2].1.tokens, 21); } fn read_test_file(name: &str) -> Vec { @@ -207,27 +215,35 @@ mod tests { #[test] fn test_load_lua_library() { - let userdata = r#" + let bytes = r#" local serialize = load(accounts[2].userdata)().serialize accounts[3].userdata = serialize({a=1, b=2, c=3}, nil, "s") "#.as_bytes() .to_vec(); + let userdata = serialize(&LuaLoader::Bytes { bytes }).unwrap(); let program_id = Pubkey::default(); - let alice_account = Account { - tokens: 100, + let program_account = Account { + tokens: 1, userdata, program_id, + executable: true, + loader_program_id: Pubkey::default(), }; + let alice_account = Account::new(100, 0, program_id); + let serialize_account = Account { tokens: 100, userdata: read_test_file("serialize.lua"), program_id, + executable: false, + loader_program_id: Pubkey::default(), }; let mut accounts = [ + (Pubkey::default(), program_account), (Pubkey::default(), alice_account), (Pubkey::default(), serialize_account), (Pubkey::default(), Account::new(1, 0, program_id)), @@ -238,7 +254,7 @@ mod tests { // Verify deterministic ordering of a serialized Lua table. assert_eq!( - str::from_utf8(&keyed_accounts[2].account.userdata).unwrap(), + str::from_utf8(&keyed_accounts[3].account.userdata).unwrap(), "{a=1,b=2,c=3}" ); } @@ -255,20 +271,36 @@ mod tests { let dan_pubkey = Pubkey::new(&[5; 32]); let erin_pubkey = Pubkey::new(&[6; 32]); + let userdata = serialize(&LuaLoader::Bytes { + bytes: read_test_file("multisig.lua"), + }).unwrap(); + let program_account = Account { + tokens: 1, + userdata, + program_id, + executable: true, + loader_program_id: Pubkey::default(), + }; + let alice_account = Account { tokens: 100, - userdata: read_test_file("multisig.lua"), + userdata: Vec::new(), program_id, + executable: true, + loader_program_id: Pubkey::default(), }; let serialize_account = Account { tokens: 100, userdata: read_test_file("serialize.lua"), program_id, + executable: true, + loader_program_id: Pubkey::default(), }; let mut accounts = [ - (alice_pubkey, alice_account), // The payer and where the program is stored. + (Pubkey::default(), program_account), // Account holding the program + (alice_pubkey, alice_account), // The payer (serialize_pubkey, serialize_account), // Where the serialize library is stored. (state_pubkey, Account::new(1, 0, program_id)), // Where program state is stored. (bob_pubkey, Account::new(1, 0, program_id)), // The payee once M signatures are collected. @@ -282,18 +314,18 @@ mod tests { .to_vec(); process(&mut keyed_accounts, &data); - assert_eq!(keyed_accounts[3].account.tokens, 1); + assert_eq!(keyed_accounts[4].account.tokens, 1); let data = format!(r#""{}""#, carol_pubkey).into_bytes(); process(&mut keyed_accounts, &data); - assert_eq!(keyed_accounts[3].account.tokens, 1); + assert_eq!(keyed_accounts[4].account.tokens, 1); let data = format!(r#""{}""#, dan_pubkey).into_bytes(); process(&mut keyed_accounts, &data); - assert_eq!(keyed_accounts[3].account.tokens, 101); // Pay day! + assert_eq!(keyed_accounts[4].account.tokens, 101); // Pay day! let data = format!(r#""{}""#, erin_pubkey).into_bytes(); process(&mut keyed_accounts, &data); - assert_eq!(keyed_accounts[3].account.tokens, 101); // No change! + assert_eq!(keyed_accounts[4].account.tokens, 101); // No change! } } diff --git a/src/bank.rs b/src/bank.rs index 42b50ca53..f3c9a7b4d 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -8,7 +8,7 @@ use bincode::serialize; use budget_program::BudgetState; use budget_transaction::BudgetTransaction; use counter::Counter; -use dynamic_program::DynamicProgram; +use dynamic_program; use entry::Entry; use hash::{hash, Hash}; use itertools::Itertools; @@ -100,6 +100,9 @@ pub enum BankError { /// Recoding into PoH failed RecordFailure, + + /// Loader call chain too deep + CallChainTooDeep, } pub type Result = result::Result; @@ -148,9 +151,6 @@ pub struct Bank { // The latest finality time for the network finality_time: AtomicUsize, - // loaded contracts hashed by program_id - loaded_contracts: RwLock>, - // Mapping of account ids to Subscriber ids and sinks to notify on userdata update account_subscriptions: RwLock>>>, @@ -176,7 +176,6 @@ impl Default for Bank { transaction_count: AtomicUsize::new(0), is_leader: true, finality_time: AtomicUsize::new(std::usize::MAX), - loaded_contracts: RwLock::new(HashMap::new()), account_subscriptions: RwLock::new(HashMap::new()), signature_subscriptions: RwLock::new(HashMap::new()), } @@ -431,6 +430,7 @@ impl Bank { error_counters.duplicate_signature += 1; } err?; + let mut called_accounts: Vec = tx .account_keys .iter() @@ -502,7 +502,7 @@ impl Bank { && SystemProgram::check_id(&pre_program_id))) { //TODO, this maybe redundant bpf should be able to guarantee this property - return Err(BankError::ModifiedContractId(instruction_index as u8)); + // return Err(BankError::ModifiedContractId(instruction_index as u8)); } // For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. if *tx_program_id != account.program_id && pre_tokens > account.tokens { @@ -516,32 +516,6 @@ impl Bank { Ok(()) } - fn loaded_contract( - &self, - tx_program_id: &Pubkey, - tx: &Transaction, - instruction_index: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - let loaded_contracts = self.loaded_contracts.write().unwrap(); - match loaded_contracts.get(&tx_program_id) { - Some(dc) => { - let mut infos: Vec<_> = (&tx.account_keys) - .into_iter() - .zip(accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - - if dc.call(&mut infos, tx.userdata(instruction_index)) { - Ok(()) - } else { - Err(BankError::ProgramRuntimeError(instruction_index as u8)) - } - } - None => Err(BankError::UnknownContractId(instruction_index as u8)), - } - } - /// Execute a function with a subset of accounts as writable references. /// Since the subset can point to the same references, in any order there is no way /// for the borrow checker to track them with regards to the original set. @@ -592,12 +566,7 @@ impl Bank { // Call the contract method // It's up to the contract to implement its own rules on moving funds if SystemProgram::check_id(&tx_program_id) { - if SystemProgram::process_transaction( - &tx, - instruction_index, - program_accounts, - &self.loaded_contracts, - ).is_err() + if SystemProgram::process_transaction(&tx, instruction_index, program_accounts).is_err() { return Err(BankError::ProgramRuntimeError(instruction_index as u8)); } @@ -632,7 +601,57 @@ impl Bank { return Err(BankError::ProgramRuntimeError(instruction_index as u8)); } } else { - self.loaded_contract(tx_program_id, tx, instruction_index, program_accounts)?; + let mut depth = 0; + let mut keys = Vec::new(); + let mut accounts = Vec::new(); + + let mut program_id = tx.program_ids[instruction_index]; + loop { + if dynamic_program::check_id(&program_id) { + // at the root of the chain, ready to dispatch + break; + } + + if depth >= 5 { + return Err(BankError::CallChainTooDeep); + } + depth += 1; + + let program = match self.get_account(&program_id) { + Some(program) => program, + None => return Err(BankError::AccountNotFound), + }; + if !program.executable || program.loader_program_id == Pubkey::default() { + return Err(BankError::AccountNotFound); + } + + // add loader to chain + keys.insert(0, program_id); + accounts.insert(0, program.clone()); + + program_id = program.loader_program_id; + } + + let mut keyed_accounts: Vec<_> = (&keys) + .into_iter() + .zip(accounts.iter_mut()) + .map(|(key, account)| KeyedAccount { key, account }) + .collect(); + let mut keyed_accounts2: Vec<_> = (&tx.instructions[instruction_index].accounts) + .into_iter() + .zip(program_accounts.iter_mut()) + .map(|(index, account)| KeyedAccount { + key: &tx.account_keys[*index as usize], + account, + }).collect(); + keyed_accounts.append(&mut keyed_accounts2); + + if !dynamic_program::process_transaction( + &mut keyed_accounts, + &tx.instructions[instruction_index].userdata, + ) { + return Err(BankError::ProgramRuntimeError(instruction_index as u8)); + } } // Verify the transaction @@ -666,8 +685,8 @@ impl Bank { /// This method calls each instruction in the transaction over the set of loaded Accounts /// The accounts are committed back to the bank only if every instruction succeeds fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> { - for (instruction_index, prog) in tx.instructions.iter().enumerate() { - Self::with_subset(tx_accounts, &prog.accounts, |program_accounts| { + for (instruction_index, instruction) in tx.instructions.iter().enumerate() { + Self::with_subset(tx_accounts, &instruction.accounts, |program_accounts| { self.execute_instruction(tx, instruction_index, program_accounts) })?; } @@ -713,7 +732,7 @@ impl Bank { let now = Instant::now(); // Use a shorter maximum age when adding transactions into the pipeline. This will reduce // the likelyhood of any single thread getting starved and processing old ids. - // TODO: Banking stage threads should be prioratized to complete faster then this queue + // TODO: Banking stage threads should be prioritized to complete faster then this queue // expires. let results = self.execute_and_commit_transactions(txs, locked_accounts, MAX_ENTRY_IDS / 2); let process_time = now.elapsed(); @@ -1306,6 +1325,7 @@ mod tests { Some(Err(BankError::ResultWithNegativeTokens(1))) ); } + #[test] fn test_one_tx_two_out_atomic_pass() { let mint = Mint::new(2); @@ -1803,7 +1823,7 @@ mod tests { let string = transport_receiver.poll(); assert!(string.is_ok()); if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"program_id":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#); + let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"executable":false,"loader_program_id":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"program_id":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},"subscription":0}}}}"#); assert_eq!(expected, response); } diff --git a/src/dynamic_program.rs b/src/dynamic_program.rs index 4b3a5abc5..71d57a20d 100644 --- a/src/dynamic_program.rs +++ b/src/dynamic_program.rs @@ -1,32 +1,23 @@ -extern crate elf; -extern crate rbpf; - -use std::env; -use std::io::prelude::*; -use std::mem; -use std::path::PathBuf; - -use bpf_verifier; -use byteorder::{LittleEndian, WriteBytesExt}; +use bincode::deserialize; use libc; #[cfg(unix)] use libloading::os::unix::*; #[cfg(windows)] use libloading::os::windows::*; -use result::Result; - use solana_program_interface::account::KeyedAccount; +use solana_program_interface::loader_instruction::LoaderInstruction; use solana_program_interface::pubkey::Pubkey; +use std::env; +use std::path::PathBuf; +use std::str; /// 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_NATIVE: &str = "dylib"; /// Dynamic link library file extension specific to the platform @@ -36,241 +27,92 @@ const PLATFORM_FILE_EXTENSION_NATIVE: &str = "so"; #[cfg(windows)] const PLATFORM_FILE_EXTENSION_NATIVE: &str = "dll"; -/// Section name -const PLATFORM_SECTION_RS: &str = ".text,entrypoint"; -const PLATFORM_SECTION_C: &str = ".text.entrypoint"; +fn create_path(name: &str) -> PathBuf { + let pathbuf = { + let current_exe = env::current_exe().unwrap(); + PathBuf::from(current_exe.parent().unwrap()) + }; -pub enum ProgramPath { - Bpf, - Native, + pathbuf.join( + PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name) + .with_extension(PLATFORM_FILE_EXTENSION_NATIVE), + ) } -impl ProgramPath { - /// Creates a platform-specific file path - pub fn create(&self, name: &str) -> PathBuf { - let pathbuf = { - let current_exe = env::current_exe().unwrap(); - PathBuf::from(current_exe.parent().unwrap()) - }; +pub const NATIVE_PROGRAM_ID: [u8; 32] = [2u8; 32]; - pathbuf.join(match self { - ProgramPath::Bpf => PathBuf::from(PLATFORM_FILE_PREFIX_BPF.to_string() + name) - .with_extension(PLATFORM_FILE_EXTENSION_BPF), - ProgramPath::Native => PathBuf::from(PLATFORM_FILE_PREFIX_NATIVE.to_string() + name) - .with_extension(PLATFORM_FILE_EXTENSION_NATIVE), - }) - } -} - -// All programs export a symbol named process() +// All native programs export a symbol named process() const ENTRYPOINT: &str = "process"; -type Entrypoint = unsafe extern "C" fn(infos: &mut Vec, data: &[u8]) -> bool; +type Entrypoint = unsafe extern "C" fn(keyed_accounts: &mut [KeyedAccount], data: &[u8]) -> bool; -#[derive(Debug)] -pub enum DynamicProgram { - /// Native program - /// * Transaction::keys[0..] - program dependent - /// * name - name of the program, translated to a file path of the program module - /// * userdata - program specific user data - Native { name: String, library: Library }, - /// Bpf program - /// * Transaction::keys[0..] - program dependent - /// * TODO BPF specific stuff - /// * userdata - program specific user data - Bpf { name: String, prog: Vec }, +pub fn check_id(program_id: &Pubkey) -> bool { + program_id.as_ref() == NATIVE_PROGRAM_ID } -impl DynamicProgram { - pub fn new_native(name: String) -> Result { - // create native program - let path = ProgramPath::Native {}.create(&name); - // TODO linux tls bug can cause crash on dlclose, workaround by never unloading - let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW)?; - Ok(DynamicProgram::Native { name, library }) - } +pub fn id() -> Pubkey { + Pubkey::new(&NATIVE_PROGRAM_ID) +} - 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()); +pub fn process_transaction(keyed_accounts: &mut [KeyedAccount], tx_data: &[u8]) -> bool { + if keyed_accounts[0].account.executable { + // dispatch it + let name = keyed_accounts[0].account.userdata.clone(); + let name = match str::from_utf8(&name) { + Ok(v) => v, + Err(e) => { + warn!("Invalid UTF-8 sequence: {}", e); + return false; } - } - } - - 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]) -> bool { - match self { - DynamicProgram::Native { name, library } => unsafe { + }; + trace!("Call native {:?}", name); + { + // create native program + let path = create_path(&name); + // TODO linux tls bug can cause crash on dlclose(), workaround by never unloading + let library = Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW).unwrap(); + unsafe { let entrypoint: Symbol = match library.get(ENTRYPOINT.as_bytes()) { Ok(s) => s, - Err(e) => panic!( - "Unable to find {:?} in program {}: {:?} ", - e, ENTRYPOINT, name - ), + Err(e) => { + warn!("{:?}: Unable to find {:?} in program", e, ENTRYPOINT); + return false; + } }; - entrypoint(infos, data) - }, - 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); - true // TODO: return false on Bpf program failure + return entrypoint(&mut keyed_accounts[1..], tx_data); } } - } -} + } else if let Ok(instruction) = deserialize(tx_data) { + match instruction { + LoaderInstruction::Write { offset, bytes } => { + trace!("NativeLoader::Write offset {} bytes {:?}", offset, bytes); + let offset = offset as usize; + if keyed_accounts[0].account.userdata.len() <= offset + bytes.len() { + warn!( + "Error: Overflow, {} > {}", + offset + bytes.len(), + keyed_accounts[0].account.userdata.len() + ); + return false; + } + // native loader takes a name and we assume it all comes in at once + keyed_accounts[0].account.userdata = bytes; + return true; + } -#[cfg(test)] -mod tests { - use super::*; - use std::path::Path; - - use solana_program_interface::account::Account; - use solana_program_interface::pubkey::Pubkey; - - #[test] - fn test_path_create_native() { - let path = ProgramPath::Native {}.create("noop"); - assert_eq!(true, Path::new(&path).exists()); - let path = ProgramPath::Native {}.create("move_funds"); - assert_eq!(true, Path::new(&path).exists()); - } - - #[test] - fn test_bpf_buf_noop() { - let prog = vec![ - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit - ]; - - let data: Vec = 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); + LoaderInstruction::Finalize => { + keyed_accounts[0].account.executable = true; + keyed_accounts[0].account.loader_program_id = id(); + keyed_accounts[0].account.program_id = *keyed_accounts[0].key; + trace!( + "NativeLoader::Finalize prog: {:?} loader {:?}", + keyed_accounts[0].account.program_id, + keyed_accounts[0].account.loader_program_id + ); + return true; + } } + } else { + warn!("Invalid program transaction: {:?}", tx_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 + false } diff --git a/src/lib.rs b/src/lib.rs index 705d59521..00756bc7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub mod fullnode; pub mod hash; pub mod leader_scheduler; pub mod ledger; +pub mod loader_transaction; pub mod logger; pub mod metrics; pub mod mint; diff --git a/src/loader_transaction.rs b/src/loader_transaction.rs new file mode 100644 index 000000000..20a897ada --- /dev/null +++ b/src/loader_transaction.rs @@ -0,0 +1,52 @@ +//! The `dynamic_transaction` module provides functionality for loading and calling a program + +use bincode::serialize; +use hash::Hash; +use signature::{Keypair, KeypairUtil}; +use solana_program_interface::loader_instruction::LoaderInstruction; +use solana_program_interface::pubkey::Pubkey; +use transaction::Transaction; + +pub trait LoaderTransaction { + fn write( + from_keypair: &Keypair, + loader: Pubkey, + offset: u32, + bytes: Vec, + last_id: Hash, + fee: i64, + ) -> Self; + + fn finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: i64) -> Self; +} + +impl LoaderTransaction for Transaction { + fn write( + from_keypair: &Keypair, + loader: Pubkey, + offset: u32, + bytes: Vec, + last_id: Hash, + fee: i64, + ) -> Self { + trace!( + "LoaderTransaction::Write() program {:?} offset {} length {}", + from_keypair.pubkey(), + offset, + bytes.len() + ); + let instruction = LoaderInstruction::Write { offset, bytes }; + let userdata = serialize(&instruction).unwrap(); + Transaction::new(from_keypair, &[], loader, userdata, last_id, fee) + } + + fn finalize(from_keypair: &Keypair, loader: Pubkey, last_id: Hash, fee: i64) -> Self { + trace!( + "LoaderTransaction::Finalize() program {:?}", + from_keypair.pubkey(), + ); + let instruction = LoaderInstruction::Finalize; + let userdata = serialize(&instruction).unwrap(); + Transaction::new(from_keypair, &[], loader, userdata, last_id, fee) + } +} diff --git a/src/rpc.rs b/src/rpc.rs index b8de2ac1f..4d3705993 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -423,7 +423,9 @@ mod tests { "result":{ "program_id": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "tokens": 20, - "userdata": [] + "userdata": [], + "executable": false, + "loader_program_id": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] }, "id":1} "#; diff --git a/src/rpc_pubsub.rs b/src/rpc_pubsub.rs index 48529d092..02c5213c9 100644 --- a/src/rpc_pubsub.rs +++ b/src/rpc_pubsub.rs @@ -390,6 +390,8 @@ mod tests { let contract_funds = Keypair::new(); let contract_state = Keypair::new(); let budget_program_id = BudgetState::id(); + let loader_program_id = Pubkey::default(); // TODO + let executable = false; // TODO let bank = Bank::new(&alice); let arc_bank = Arc::new(bank); let last_id = arc_bank.last_id(); @@ -474,7 +476,10 @@ mod tests { "result": { "program_id": budget_program_id, "tokens": 1, - "userdata": expected_userdata + "userdata": expected_userdata, + "executable": executable, + "loader_program_id": loader_program_id, + }, "subscription": 0, } @@ -512,7 +517,9 @@ mod tests { "result": { "program_id": budget_program_id, "tokens": 51, - "userdata": expected_userdata + "userdata": expected_userdata, + "executable": executable, + "loader_program_id": loader_program_id, }, "subscription": 0, } @@ -549,7 +556,9 @@ mod tests { "result": { "program_id": budget_program_id, "tokens": 1, - "userdata": expected_userdata + "userdata": expected_userdata, + "executable": executable, + "loader_program_id": loader_program_id, }, "subscription": 0, } diff --git a/src/system_program.rs b/src/system_program.rs index d33b62109..96d2b64bb 100644 --- a/src/system_program.rs +++ b/src/system_program.rs @@ -1,12 +1,9 @@ //! system program use bincode::deserialize; -use dynamic_program::DynamicProgram; use solana_program_interface::account::Account; use solana_program_interface::pubkey::Pubkey; use std; -use std::collections::HashMap; -use std::sync::RwLock; use transaction::Transaction; #[derive(Debug)] @@ -42,10 +39,6 @@ pub enum SystemProgram { /// * Transaction::keys[0] - source /// * Transaction::keys[1] - destination Move { tokens: i64 }, - /// Load a program - /// program_id - id to associate this program - /// nanme - file path of the program to load - Load { program_id: Pubkey, name: String }, } pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; @@ -65,7 +58,6 @@ impl SystemProgram { tx: &Transaction, pix: usize, accounts: &mut [&mut Account], - loaded_programs: &RwLock>, ) -> Result<()> { if let Ok(syscall) = deserialize(tx.userdata(pix)) { trace!("process_transaction: {:?}", syscall); @@ -88,6 +80,8 @@ impl SystemProgram { accounts[1].tokens += tokens; accounts[1].program_id = program_id; accounts[1].userdata = vec![0; space as usize]; + accounts[1].executable = false; + accounts[1].loader_program_id = Pubkey::default(); } SystemProgram::Assign { program_id } => { if !Self::check_id(&accounts[0].program_id) { @@ -100,16 +94,6 @@ impl SystemProgram { accounts[0].tokens -= tokens; accounts[1].tokens += tokens; } - SystemProgram::Load { program_id, name } => { - let mut hashmap = loaded_programs.write().unwrap(); - hashmap.insert( - program_id, - DynamicProgram::new_native(name).map_err(|err| { - warn!("SystemProgram::Load failure: {:?}", err); - Error::InvalidArgument - })?, - ); - } } Ok(()) } else { @@ -125,19 +109,13 @@ mod test { use signature::{Keypair, KeypairUtil}; use solana_program_interface::account::Account; use solana_program_interface::pubkey::Pubkey; - use std::collections::HashMap; - use std::sync::RwLock; use system_program::SystemProgram; use system_transaction::SystemTransaction; use transaction::Transaction; - fn process_transaction( - tx: &Transaction, - accounts: &mut [Account], - loaded_programs: &RwLock>, - ) -> Result<()> { + fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> { let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); - SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs) + SystemProgram::process_transaction(&tx, 0, &mut refs[..]) } #[test] @@ -146,8 +124,7 @@ mod test { let to = Keypair::new(); let mut accounts = vec![Account::default(), Account::default()]; let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default()); - let hash = RwLock::new(HashMap::new()); - process_transaction(&tx, &mut accounts, &hash).unwrap(); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[0].tokens, 0); assert_eq!(accounts[1].tokens, 0); } @@ -158,12 +135,13 @@ mod test { let mut accounts = vec![Account::default(), Account::default()]; accounts[0].tokens = 1; let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - let hash = RwLock::new(HashMap::new()); - process_transaction(&tx, &mut accounts, &hash).unwrap(); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[0].tokens, 0); assert_eq!(accounts[1].tokens, 1); } + #[test] + #[ignore] fn test_create_spend_wrong_source() { let from = Keypair::new(); let to = Keypair::new(); @@ -171,8 +149,7 @@ mod test { accounts[0].tokens = 1; accounts[0].program_id = from.pubkey(); let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - let hash = RwLock::new(HashMap::new()); - assert!(process_transaction(&tx, &mut accounts, &hash).is_err()); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[0].tokens, 1); assert_eq!(accounts[1].tokens, 0); } @@ -183,8 +160,7 @@ mod test { let mut accounts = vec![Account::default(), Account::default()]; let tx = Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0); - let hash = RwLock::new(HashMap::new()); - process_transaction(&tx, &mut accounts, &hash).unwrap(); + process_transaction(&tx, &mut accounts).unwrap(); assert!(accounts[0].userdata.is_empty()); assert_eq!(accounts[1].userdata.len(), 1); assert_eq!(accounts[1].program_id, to.pubkey()); @@ -204,8 +180,7 @@ mod test { Pubkey::default(), 0, ); - let hash = RwLock::new(HashMap::new()); - assert!(process_transaction(&tx, &mut accounts, &hash).is_err()); + assert!(process_transaction(&tx, &mut accounts).is_err()); assert!(accounts[1].userdata.is_empty()); } #[test] @@ -223,8 +198,7 @@ mod test { Pubkey::default(), 0, ); - let hash = RwLock::new(HashMap::new()); - assert!(process_transaction(&tx, &mut accounts, &hash).is_err()); + assert!(process_transaction(&tx, &mut accounts).is_err()); assert!(accounts[1].userdata.is_empty()); } #[test] @@ -242,8 +216,7 @@ mod test { Pubkey::default(), 0, ); - let hash = RwLock::new(HashMap::new()); - assert!(process_transaction(&tx, &mut accounts, &hash).is_err()); + assert!(process_transaction(&tx, &mut accounts).is_err()); assert_eq!(accounts[1].userdata.len(), 3); } #[test] @@ -252,8 +225,7 @@ mod test { let program = Keypair::new(); let mut accounts = vec![Account::default()]; let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0); - let hash = RwLock::new(HashMap::new()); - process_transaction(&tx, &mut accounts, &hash).unwrap(); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[0].program_id, program.pubkey()); } #[test] @@ -263,8 +235,7 @@ mod test { let mut accounts = vec![Account::default(), Account::default()]; accounts[0].tokens = 1; let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); - let hash = RwLock::new(HashMap::new()); - process_transaction(&tx, &mut accounts, &hash).unwrap(); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[0].tokens, 0); assert_eq!(accounts[1].tokens, 1); } diff --git a/src/system_transaction.rs b/src/system_transaction.rs index ead09195a..993eb7c26 100644 --- a/src/system_transaction.rs +++ b/src/system_transaction.rs @@ -30,13 +30,6 @@ pub trait SystemTransaction { fee: i64, ) -> Self; - fn system_load( - from_keypair: &Keypair, - last_id: Hash, - fee: i64, - program_id: Pubkey, - name: String, - ) -> Self; fn system_move_many( from_keypair: &Keypair, moves: &[(Pubkey, i64)], @@ -107,25 +100,7 @@ impl SystemTransaction for Transaction { fee, ) } - /// Create and sign new SystemProgram::Load transaction - fn system_load( - from_keypair: &Keypair, - last_id: Hash, - fee: i64, - program_id: Pubkey, - name: String, - ) -> Self { - let load = SystemProgram::Load { program_id, name }; - let userdata = serialize(&load).unwrap(); - Transaction::new( - from_keypair, - &[], - SystemProgram::id(), - userdata, - last_id, - fee, - ) - } + fn system_move_many(from: &Keypair, moves: &[(Pubkey, i64)], last_id: Hash, fee: i64) -> Self { let instructions: Vec<_> = moves .iter() diff --git a/tests/programs.rs b/tests/programs.rs index 2777c0473..dd8eab7b4 100644 --- a/tests/programs.rs +++ b/tests/programs.rs @@ -1,347 +1,252 @@ extern crate bincode; +extern crate elf; 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::bank::Bank; +use solana::dynamic_program; +use solana::loader_transaction::LoaderTransaction; +use solana::logger; +use solana::mint::Mint; use solana::signature::{Keypair, KeypairUtil}; -use solana::system_program::SystemProgram; use solana::system_transaction::SystemTransaction; 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; +// TODO test modified user data +// TODO test failure if account tokens decrease but not assigned to program -#[cfg(feature = "bpf_c")] -#[test] -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()); +fn check_tx_results(bank: &Bank, tx: &Transaction, result: Vec>) { + assert_eq!(result.len(), 1); + assert_eq!(result[0], Ok(())); + assert_eq!(bank.get_signature(&tx.last_id, &tx.signature), Some(Ok(()))); } -#[cfg(feature = "bpf_c")] #[test] -#[ignore] -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()]; - accounts[0].tokens = 100; - accounts[1].tokens = 1; +fn test_transaction_load_native() { + logger::setup(); - { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); + let mint = Mint::new(50); + // TODO in a test like this how should the last_id be incremented, as used here it is always the same + // which leads to duplicate tx signature errors + let bank = Bank::new(&mint); + let program = Keypair::new(); - let dp = DynamicProgram::new_bpf_from_file("noop_rust".to_string()); - assert!(dp.call(&mut infos, &data)); - } -} + // allocate, populate, finalize user program -#[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()); - assert!(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()); - assert!(dp.call(&mut infos, &data)); - } -} - -#[cfg(feature = "bpf_c")] -#[test] -fn test_bpf_file_tictactoe_c() { - let game_size = 0x78; // corresponds to the C structure size - let mut accounts = vec![ - Account::new(0, 0, Pubkey::default()), - Account::new(0, game_size, Pubkey::default()), - Account::new(0, 0, Pubkey::default()), - ]; - - tictactoe_command(Command::Init, &mut accounts, Pubkey::new(&[0xA; 32])); - tictactoe_command( - Command::Join(0xAABBCCDD), - &mut accounts, - Pubkey::new(&[0xA; 32]), + let tx = Transaction::system_create( + &mint.keypair(), + program.pubkey(), + mint.last_id(), + 1, + 56, // TODO How does the user know how much space to allocate, this is really an internally known size + dynamic_program::id(), + 0, ); - 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])); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); - // validate test + println!("id: {:?}", dynamic_program::id()); + let name = String::from("noop"); + let tx = Transaction::write( + &program, + dynamic_program::id(), + 0, + name.as_bytes().to_vec(), + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + println!("id after: {:?}", dynamic_program::id()); + + let tx = Transaction::finalize(&program, dynamic_program::id(), mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + // Call user program + + let tx = Transaction::new( + &mint.keypair(), // TODO + &[], + program.pubkey(), + vec![1u8], + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); } #[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; +fn test_transaction_load_lua() { + logger::setup(); - { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); + let mint = Mint::new(50); + // TODO in a test like this how should the last_id be incremented, as used here it is always the same + // which leads to duplicate tx signature errors + let bank = Bank::new(&mint); + let loader = Keypair::new(); + let program = Keypair::new(); + let from = Keypair::new(); + let to = Keypair::new().pubkey(); - let dp = DynamicProgram::new_native("noop".to_string()).unwrap(); - assert!(dp.call(&mut infos, &data)); - } + // allocate, populate, and finalize Lua loader + + let tx = Transaction::system_create( + &mint.keypair(), + loader.pubkey(), + mint.last_id(), + 1, + 56, // TODO How does the user know how much space to allocate for what should be an internally known size + dynamic_program::id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let name = String::from("solua"); + let tx = Transaction::write( + &loader, + dynamic_program::id(), + 0, + name.as_bytes().to_vec(), + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::finalize(&loader, dynamic_program::id(), mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + // allocate, populate, and finalize user program + + let bytes = r#" + print("Lua Script!") + local tokens, _ = string.unpack("I", data) + accounts[1].tokens = accounts[1].tokens - tokens + accounts[2].tokens = accounts[2].tokens + tokens + "#.as_bytes() + .to_vec(); + + let tx = Transaction::system_create( + &mint.keypair(), + program.pubkey(), + mint.last_id(), + 1, + 300, // TODO How does the user know how much space to allocate for what should be an internally known size + loader.pubkey(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::write(&program, loader.pubkey(), 0, bytes, mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::finalize(&program, loader.pubkey(), mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + // Call user program with two accounts + + let tx = Transaction::system_create( + &mint.keypair(), + from.pubkey(), + mint.last_id(), + 10, + 0, + program.pubkey(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::system_create( + &mint.keypair(), + to, + mint.last_id(), + 1, + 0, + program.pubkey(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let data = serialize(&10).unwrap(); + let tx = Transaction::new(&from, &[to], program.pubkey(), data, mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + assert_eq!(bank.get_balance(&from.pubkey()), 0); + assert_eq!(bank.get_balance(&to), 11); } +#[cfg(feature = "bpf_c")] #[test] -fn test_native_file_move_funds_success() { - let tokens: i64 = 100; - let data: Vec = serialize(&tokens).unwrap(); - let keys = vec![Pubkey::default(); 2]; - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 100; - accounts[1].tokens = 1; +fn test_transaction_load_bpf() { + logger::setup(); - { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); + let mint = Mint::new(50); + // TODO in a test like this how should the last_id be incremented, as used here it is always the same + // which leads to duplicate tx signature errors + let bank = Bank::new(&mint); + let loader = Keypair::new(); + let program = Keypair::new(); - let dp = DynamicProgram::new_native("move_funds".to_string()).unwrap(); - assert!(dp.call(&mut infos, &data)); - } - assert_eq!(0, accounts[0].tokens); - assert_eq!(101, accounts[1].tokens); -} - -#[test] -fn test_native_file_move_funds_insufficient_funds() { - let tokens: i64 = 100; - let data: Vec = serialize(&tokens).unwrap(); - let keys = vec![Pubkey::default(); 2]; - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 10; - accounts[1].tokens = 1; - - { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - - let dp = DynamicProgram::new_native("move_funds".to_string()).unwrap(); - assert!(!dp.call(&mut infos, &data)); - } - assert_eq!(10, accounts[0].tokens); - assert_eq!(1, accounts[1].tokens); -} - -#[test] -fn test_program_native_move_funds_succes_many_threads() { - let num_threads = 42; // number of threads to spawn - let num_iters = 100; // number of iterations of test in each thread - let mut threads = Vec::new(); - for _t in 0..num_threads { - threads.push(thread::spawn(move || { - for _i in 0..num_iters { - { - let tokens: i64 = 100; - let data: Vec = serialize(&tokens).unwrap(); - let keys = vec![Pubkey::default(); 2]; - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 100; - accounts[1].tokens = 1; - - { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - - let dp = DynamicProgram::new_native("move_funds".to_string()).unwrap(); - assert!(dp.call(&mut infos, &data)); - } - assert_eq!(0, accounts[0].tokens); - assert_eq!(101, accounts[1].tokens); - } - } - })); - } - - for thread in threads { - thread.join().unwrap(); - } -} -fn process_transaction( - tx: &Transaction, - accounts: &mut [Account], - loaded_programs: &RwLock>, -) { - let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); - SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs).unwrap(); -} - -#[test] -fn test_system_program_load_call() { - // first load the program - let loaded_programs = RwLock::new(HashMap::new()); - { - let from = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - let program_id = Pubkey::default(); // same program id for both - let tx = Transaction::system_load( - &from, - Hash::default(), - 0, - program_id, - "move_funds".to_string(), - ); - - process_transaction(&tx, &mut accounts, &loaded_programs); - } - // then call the program - { - let program_id = Pubkey::default(); // same program id for both - let keys = vec![Pubkey::default(), Pubkey::default()]; - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 100; - accounts[1].tokens = 1; - let tokens: i64 = 100; - let data: Vec = serialize(&tokens).unwrap(); - { - let hash = loaded_programs.write().unwrap(); - match hash.get(&program_id) { - Some(dp) => { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - - assert!(dp.call(&mut infos, &data)); - } - None => panic!("failed to find program in hash"), - } - } - assert_eq!(0, accounts[0].tokens); - assert_eq!(101, accounts[1].tokens); - } -} -#[test] -fn test_system_program_load_call_many_threads() { - let num_threads = 42; - let num_iters = 100; - let mut threads = Vec::new(); - for _t in 0..num_threads { - threads.push(thread::spawn(move || { - let _tid = thread::current().id(); - for _i in 0..num_iters { - // first load the program - let loaded_programs = RwLock::new(HashMap::new()); - { - let from = Keypair::new(); - let mut accounts = vec![Account::default(), Account::default()]; - let program_id = Pubkey::default(); // same program id for both - let tx = Transaction::system_load( - &from, - Hash::default(), - 0, - program_id, - "move_funds".to_string(), - ); - - process_transaction(&tx, &mut accounts, &loaded_programs); - } - // then call the program - { - let program_id = Pubkey::default(); // same program id for both - let keys = vec![Pubkey::default(), Pubkey::default()]; - let mut accounts = vec![Account::default(), Account::default()]; - accounts[0].tokens = 100; - accounts[1].tokens = 1; - let tokens: i64 = 100; - let data: Vec = serialize(&tokens).unwrap(); - { - let hash = loaded_programs.write().unwrap(); - match hash.get(&program_id) { - Some(dp) => { - let mut infos: Vec<_> = (&keys) - .into_iter() - .zip(&mut accounts) - .map(|(key, account)| KeyedAccount { key, account }) - .collect(); - - assert!(dp.call(&mut infos, &data)); - } - None => panic!("failed to find program in hash"), - } - } - assert_eq!(0, accounts[0].tokens); - assert_eq!(101, accounts[1].tokens); - } - } - })); - } - - for thread in threads { - thread.join().unwrap(); - } + // allocate, populate, finalize BPF loader + + let tx = Transaction::system_create( + &mint.keypair(), + loader.pubkey(), + mint.last_id(), + 1, + 56, // TODO How does the user know how much space to allocate for what should be an internally known size + dynamic_program::id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let name = String::from("sobpf"); + let tx = Transaction::write( + &loader, + dynamic_program::id(), + 0, + name.as_bytes().to_vec(), + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::finalize(&loader, dynamic_program::id(), mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + // allocate, populate, and finalize user program + + let tx = Transaction::system_create( + &mint.keypair(), + program.pubkey(), + mint.last_id(), + 1, + 56, // TODO How does the user know how much space to allocate for what should be an internally known size + loader.pubkey(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let name = String::from("noop_c"); + let tx = Transaction::write( + &program, + loader.pubkey(), + 0, + name.as_bytes().to_vec(), + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + let tx = Transaction::finalize(&program, loader.pubkey(), mint.last_id(), 0); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); + + // Call user program + + let tx = Transaction::new( + &mint.keypair(), // TODO + &[], + program.pubkey(), + vec![1u8], + mint.last_id(), + 0, + ); + check_tx_results(&bank, &tx, bank.process_transactions(&vec![tx.clone()])); }