Add BpfLoader (#34)

feat: Add BpfLoader
This commit is contained in:
jackcmay
2018-10-23 20:56:54 -07:00
committed by Michael Vines
parent b8d586c67e
commit 02787df7b9
14 changed files with 3552 additions and 3189 deletions

4
web3.js/test/bin/build.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash -ex
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o noop_c.bc -c noop.c
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o noop_c.o noop_c.bc

133
web3.js/test/bin/noop.c Normal file
View File

@ -0,0 +1,133 @@
//#include <stdint.h>
//#include <stddef.h>
#if 1
// one way to define a helper function is with index as a fixed value
#define BPF_TRACE_PRINTK_IDX 6
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
#else
// relocation is another option
extern int sol_print(int, int, int, int, int);
#endif
typedef long long unsigned int uint64_t;
typedef long long int int64_t;
typedef unsigned char uint8_t;
typedef enum { false = 0, true } bool;
#define SIZE_PUBKEY 32
typedef struct {
uint8_t x[SIZE_PUBKEY];
} SolPubkey;
typedef struct {
SolPubkey *key;
int64_t* tokens;
uint64_t userdata_len;
uint8_t *userdata;
SolPubkey *program_id;
} SolKeyedAccounts;
// TODO support BPF function calls rather then forcing everything to be inlined
#define SOL_FN_PREFIX __attribute__((always_inline)) static
// TODO move this to a registered helper
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
for (int i = 0; i < len; i++) {
*((uint8_t *)dst + i) = *((uint8_t *)src + i);
}
}
#define sol_panic() _sol_panic(__LINE__)
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
sol_print(0, 0, 0xFF, 0xFF, line);
char *pv = (char *)1;
*pv = 1;
}
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
uint8_t **userdata, uint64_t *userdata_len) {
if (num_ka != *(uint64_t *)src) {
return 0;
}
src += sizeof(uint64_t);
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
// key
ka[i].key = (SolPubkey *)src;
src += SIZE_PUBKEY;
// tokens
ka[i].tokens = (int64_t *)src;
src += sizeof(int64_t);
// account userdata
ka[i].userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
ka[i].userdata = src;
src += ka[i].userdata_len;
// program_id
ka[i].program_id = (SolPubkey *)src;
src += SIZE_PUBKEY;
}
// tx userdata
*userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
*userdata = src;
return 1;
}
// -- Debug --
SOL_FN_PREFIX void print_key(SolPubkey *key) {
for (int j = 0; j < SIZE_PUBKEY; j++) {
sol_print(0, 0, 0, j, key->x[j]);
}
}
SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
for (int j = 0; j < len; j++) {
sol_print(0, 0, 0, j, data[j]);
}
}
SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
uint8_t *userdata, uint64_t userdata_len) {
sol_print(0, 0, 0, 0, num_ka);
for (int i = 0; i < num_ka; i++) {
// key
print_key(ka[i].key);
// tokens
sol_print(0, 0, 0, 0, *ka[i].tokens);
// account userdata
print_userdata(ka[i].userdata, ka[i].userdata_len);
// program_id
print_key(ka[i].program_id);
}
// tx userdata
print_userdata(userdata, userdata_len);
}
// -- Program entrypoint --
uint64_t entrypoint(char *buf) {
SolKeyedAccounts ka[1];
uint64_t userdata_len;
uint8_t *userdata;
if (1 != sol_deserialize((uint8_t *)buf, 1, ka, &userdata, &userdata_len)) {
return 0;
}
print_params(1, ka, userdata, userdata_len);
return 1;
}

BIN
web3.js/test/bin/noop_c.o Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
The Solana SDK Test Binary
One of the functions that the SDK tests exercises is the loading and calling of Berkley Packet Filter programs (BPF).
The test file noop_c.o is an ELF object file containing a small BPF program contained in the ELF section named ".text.entrypoint".
The C source file for noop_c.o is noop_c.c and it can be rebuilt using the build.sh script.
The build.sh script depends on LLVM 6.0 to be installed.

View File

@ -0,0 +1,33 @@
// @flow
import {
Connection,
BpfLoader,
Transaction,
sendAndConfirmTransaction,
} from '../src';
import {mockRpcEnabled} from './__mocks__/node-fetch';
import {url} from './url';
import {newAccountWithTokens} from './new-account-with-tokens';
if (!mockRpcEnabled) {
// The default of 5 seconds is too slow for live testing sometimes
jest.setTimeout(10000);
}
test('load BPF program', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
const connection = new Connection(url);
const from = await newAccountWithTokens(connection);
const programId = await BpfLoader.load(connection, from, 'test/bin/noop_c.o');
const transaction = new Transaction().add({
keys: [from.publicKey],
programId,
});
await sendAndConfirmTransaction(connection, from, transaction);
});

View File

@ -15,8 +15,7 @@ if (!mockRpcEnabled) {
jest.setTimeout(10000);
}
test('load noop program', async () => {
test('load native program', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
@ -24,12 +23,12 @@ test('load noop program', async () => {
const connection = new Connection(url);
const from = await newAccountWithTokens(connection);
const noopProgramId = await NativeLoader.load(connection, from, 'noop');
const noopTransaction = new Transaction().add({
const programId = await NativeLoader.load(connection, from, 'noop');
const transaction = new Transaction().add({
keys: [from.publicKey],
programId: noopProgramId,
programId,
});
await expect(sendAndConfirmTransaction(connection, from, noopTransaction)).resolves.toBeUndefined();
await sendAndConfirmTransaction(connection, from, transaction);
});