diff --git a/src/flamenco/runtime/tests/Local.mk b/src/flamenco/runtime/tests/Local.mk new file mode 100644 index 0000000000..aef6e04b26 --- /dev/null +++ b/src/flamenco/runtime/tests/Local.mk @@ -0,0 +1,9 @@ +$(call add-hdrs,fd_exec_test.pb.h) +$(call add-objs,fd_exec_test.pb,fd_flamenco) + +ifdef FD_HAS_INT128 +$(call add-hdrs,fd_exec_instr_test.h) +$(call add-objs,fd_exec_instr_test,fd_flamenco) + +$(call make-unit-test,test_exec_instr,test_exec_instr,fd_flamenco fd_funk fd_ballet fd_util) +endif diff --git a/src/flamenco/runtime/tests/Makefile b/src/flamenco/runtime/tests/Makefile new file mode 100644 index 0000000000..6ffeef522d --- /dev/null +++ b/src/flamenco/runtime/tests/Makefile @@ -0,0 +1,2 @@ +fd_exec_test.pb.h fd_exec_test.pb.c: fd_exec_test.proto + nanopb_generator.py -I ../.. -I . -L "" -C fd_exec_test.proto diff --git a/src/flamenco/runtime/tests/fd_exec_instr_test.c b/src/flamenco/runtime/tests/fd_exec_instr_test.c new file mode 100644 index 0000000000..c66a58629a --- /dev/null +++ b/src/flamenco/runtime/tests/fd_exec_instr_test.c @@ -0,0 +1,302 @@ +#define FD_SCRATCH_USE_HANDHOLDING 1 +#include "fd_exec_instr_test.h" +#include "../fd_acc_mgr.h" +#include "../fd_executor.h" +#include "../context/fd_exec_epoch_ctx.h" +#include "../context/fd_exec_slot_ctx.h" +#include "../context/fd_exec_txn_ctx.h" +#include "../../../funk/fd_funk.h" +#include + +struct __attribute__((aligned(32UL))) fd_exec_instr_test_runner_private { + fd_funk_t * funk; +}; + +ulong +fd_exec_instr_test_runner_align( void ) { + return alignof(fd_exec_instr_test_runner_t); +} + +ulong +fd_exec_instr_test_runner_footprint( void ) { + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND( l, alignof(fd_exec_instr_test_runner_t), sizeof(fd_exec_instr_test_runner_t) ); + l = FD_LAYOUT_APPEND( l, fd_funk_align(), fd_funk_footprint() ); + return l; +} + +fd_exec_instr_test_runner_t * +fd_exec_instr_test_runner_new( void * mem, + ulong wksp_tag ) { + FD_SCRATCH_ALLOC_INIT( l, mem ); + void * runner_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_instr_test_runner_t), sizeof(fd_exec_instr_test_runner_t) ); + void * funk_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_funk_align(), fd_funk_footprint() ); + FD_SCRATCH_ALLOC_FINI( l, alignof(fd_exec_instr_test_runner_t) ); + + ulong txn_max = 4+fd_tile_cnt(); + ulong rec_max = 1024UL; + fd_funk_t * funk = fd_funk_join( fd_funk_new( funk_mem, wksp_tag, (ulong)fd_tickcount(), txn_max, rec_max ) ); + if( FD_UNLIKELY( !funk ) ) { + FD_LOG_WARNING(( "fd_funk_new() failed" )); + return NULL; + } + + fd_exec_instr_test_runner_t * runner = runner_mem; + runner->funk = funk; + return runner; +} + +void * +fd_exec_instr_test_runner_delete( fd_exec_instr_test_runner_t * runner ) { + if( FD_UNLIKELY( !runner ) ) return NULL; + fd_funk_delete( fd_funk_leave( runner->funk ) ); + runner->funk = NULL; + return runner; +} + +static void +_load_account( fd_borrowed_account_t * acc, + fd_acc_mgr_t * acc_mgr, + fd_funk_txn_t * funk_txn, + fd_exec_test_acct_state_t const * state ) { + fd_borrowed_account_init( acc ); + + fd_pubkey_t pubkey[1]; memcpy( pubkey, state->address, sizeof(fd_pubkey_t) ); + + int err = fd_acc_mgr_modify( /* acc_mgr */ acc_mgr, + /* txn */ funk_txn, + /* pubkey */ pubkey, + /* do_create */ 1, + /* min_data_sz */ state->data->size, + acc ); + assert( err==FD_ACC_MGR_SUCCESS ); + + fd_memcpy( acc->data, state->data->bytes, state->data->size ); + + acc->meta->info.lamports = state->lamports; + acc->meta->info.executable = state->executable; + acc->meta->info.rent_epoch = state->rent_epoch; + acc->meta->dlen = state->data->size; + memcpy( acc->meta->info.owner, state->owner, sizeof(fd_pubkey_t) ); +} + +static int +_context_create( fd_exec_instr_test_runner_t * runner, + fd_exec_instr_ctx_t * ctx, + fd_exec_test_instr_context_t const * test_ctx ) { + + memset( ctx, 0, sizeof(fd_exec_instr_ctx_t) ); + + fd_funk_t * funk = runner->funk; + + /* Generate unique ID for funk txn */ + + static FD_TL ulong xid_seq = 0UL; + + fd_funk_txn_xid_t xid[1] = {0}; + xid->ul[0] = fd_log_app_id(); + xid->ul[1] = fd_log_thread_id(); + xid->ul[2] = xid_seq++; + xid->ul[3] = (ulong)fd_tickcount(); + + /* Create temporary funk transaction and scratch contexts */ + + fd_funk_txn_t * funk_txn = fd_funk_txn_prepare( funk, NULL, xid, 1 ); + fd_scratch_push(); + + /* Allocate contexts */ + + uchar * epoch_ctx_mem = fd_scratch_alloc( FD_EXEC_EPOCH_CTX_ALIGN, FD_EXEC_EPOCH_CTX_FOOTPRINT ); + uchar * slot_ctx_mem = fd_scratch_alloc( FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT ); + uchar * txn_ctx_mem = fd_scratch_alloc( FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT ); + + fd_exec_epoch_ctx_t * epoch_ctx = fd_exec_epoch_ctx_join( fd_exec_epoch_ctx_new( epoch_ctx_mem ) ); + fd_exec_slot_ctx_t * slot_ctx = fd_exec_slot_ctx_join ( fd_exec_slot_ctx_new ( slot_ctx_mem ) ); + fd_exec_txn_ctx_t * txn_ctx = fd_exec_txn_ctx_join ( fd_exec_txn_ctx_new ( txn_ctx_mem ) ); + + epoch_ctx->valloc = fd_scratch_virtual(); + + assert( epoch_ctx ); + assert( slot_ctx ); + + /* Set up epoch context */ + + epoch_ctx->epoch_bank.rent.lamports_per_uint8_year = 3480; + epoch_ctx->epoch_bank.rent.exemption_threshold = 2; + epoch_ctx->epoch_bank.rent.burn_percent = 50; + + /* Restore feature flags */ + + fd_features_disable_all( &epoch_ctx->features ); + for( ulong j=0UL; j < test_ctx->feature_set.features_count; j++ ) { + ulong prefix = test_ctx->feature_set.features[j]; + fd_feature_id_t const * id = fd_feature_id_query( prefix ); + if( FD_UNLIKELY( !id ) ) { + FD_LOG_WARNING(( "Unsupported feature ID 0x%16lx", prefix )); + return 0; + } + /* Enabled since genesis */ + fd_features_set( &epoch_ctx->features, id, 0UL ); + } + + /* Create account manager */ + + fd_acc_mgr_t * acc_mgr = fd_acc_mgr_new( fd_scratch_alloc( FD_ACC_MGR_ALIGN, FD_ACC_MGR_FOOTPRINT ), funk ); + + /* Set up slot context */ + + slot_ctx->epoch_ctx = epoch_ctx; + slot_ctx->funk_txn = funk_txn; + slot_ctx->acc_mgr = acc_mgr; + slot_ctx->valloc = fd_scratch_virtual(); + + /* TODO: Restore slot_bank */ + + /* Set up txn context */ + + txn_ctx->epoch_ctx = epoch_ctx; + txn_ctx->slot_ctx = slot_ctx; + txn_ctx->funk_txn = funk_txn; + txn_ctx->acc_mgr = acc_mgr; + + /* Set up instruction context */ + + fd_instr_info_t * info = fd_scratch_alloc( alignof(fd_instr_info_t), sizeof(fd_instr_info_t) ); + assert( info ); + memset( info, 0, sizeof(fd_instr_info_t) ); + + if( test_ctx->data ) { + info->data_sz = (ushort)test_ctx->data->size; + info->data = test_ctx->data->bytes; + } + + memcpy( info->program_id_pubkey.uc, test_ctx->program_id, sizeof(fd_pubkey_t) ); + + /* Prepare borrowed account table (correctly handles aliasing) */ + + fd_borrowed_account_t * borrowed_accts = + fd_scratch_alloc( alignof(fd_borrowed_account_t), test_ctx->accounts_count * sizeof(fd_borrowed_account_t) ); + fd_memset( borrowed_accts, 0, test_ctx->accounts_count * sizeof(fd_borrowed_account_t) ); + + /* Load accounts into database */ + + for( ulong j=0UL; j < test_ctx->accounts_count; j++ ) + _load_account( &borrowed_accts[j], acc_mgr, funk_txn, &test_ctx->accounts[j] ); + + /* Load instruction accounts */ + + if( FD_UNLIKELY( test_ctx->instr_accounts_count > 128 ) ) { + /* TODO remove this hardcoded constant */ + FD_LOG_WARNING(( "Too many instruction accounts" )); + return 0; + } + for( ulong j=0UL; j < test_ctx->instr_accounts_count; j++ ) { + uint index = test_ctx->instr_accounts[j].index; + if( index >= test_ctx->accounts_count ) { + FD_LOG_WARNING(( "Instruction account index out of range" )); + return 0; + } + + fd_borrowed_account_t * acc = &borrowed_accts[ index ]; + uint flags = 0; + flags |= test_ctx->instr_accounts[j].is_writable ? FD_INSTR_ACCT_FLAGS_IS_WRITABLE : 0; + flags |= test_ctx->instr_accounts[j].is_signer ? FD_INSTR_ACCT_FLAGS_IS_SIGNER : 0; + + info->borrowed_accounts[j] = acc; + info->acct_flags [j] = (uchar)flags; + memcpy( info->acct_pubkeys[j].uc, acc->pubkey, sizeof(fd_pubkey_t) ); + } + info->acct_cnt = (uchar)test_ctx->instr_accounts_count; + + ctx->epoch_ctx = epoch_ctx; + ctx->slot_ctx = slot_ctx; + ctx->txn_ctx = txn_ctx; + ctx->funk_txn = funk_txn; + ctx->acc_mgr = acc_mgr; + ctx->valloc = fd_scratch_virtual(); + ctx->instr = info; + + return 1; +} + +static void +_context_destroy( fd_exec_instr_test_runner_t * runner, + fd_exec_instr_ctx_t * ctx ) { + fd_exec_slot_ctx_t * slot_ctx = ctx->slot_ctx; + fd_exec_epoch_ctx_t * epoch_ctx = slot_ctx->epoch_ctx; + fd_acc_mgr_t * acc_mgr = slot_ctx->acc_mgr; + fd_funk_txn_t * funk_txn = slot_ctx->funk_txn; + + fd_exec_slot_ctx_delete ( fd_exec_slot_ctx_leave ( slot_ctx ) ); + fd_exec_epoch_ctx_delete( fd_exec_epoch_ctx_leave( epoch_ctx ) ); + fd_acc_mgr_delete( acc_mgr ); + fd_scratch_pop(); + fd_funk_txn_cancel( runner->funk, funk_txn, 1 ); +} + +static int +_diff_effects( fd_exec_instr_ctx_t * ctx, + fd_exec_test_instr_effects_t const * expected, + int exec_result ) { + + if( expected->result != exec_result ) { + FD_LOG_WARNING(( "Expected result (%d-%s), got (%d-%s)", + expected->result, fd_executor_instr_strerror( expected->result ), + exec_result, fd_executor_instr_strerror( exec_result ) )); + return 0; + } + + if( ( exec_result==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) & + ( expected->custom_err != ctx->txn_ctx->custom_err ) ) { + FD_LOG_WARNING(( "Expected custom error %d, got %d", + expected->custom_err, ctx->txn_ctx->custom_err )); + return 0; + } + + + /* TODO detect accounts that were modified locally but not in fixture */ + + return 1; +} + +int +fd_exec_instr_fixture_run( fd_exec_instr_test_runner_t * runner, + fd_exec_test_instr_fixture_t const * test ) { + + fd_exec_instr_ctx_t ctx[1]; + if( FD_UNLIKELY( !_context_create( runner, ctx, &test->input ) ) ) + return 0; + + fd_pubkey_t program_id[1]; memcpy( program_id, test->input.program_id, sizeof(fd_pubkey_t) ); + fd_exec_instr_fn_t native_prog_fn = fd_executor_lookup_native_program( program_id ); + + if( FD_UNLIKELY( !native_prog_fn ) ) { + FD_LOG_WARNING(( "TODO: User deployed programs not yet supported" )); + _context_destroy( runner, ctx ); + return 0; + } + + int exec_result = native_prog_fn( *ctx ); + + int ok = _diff_effects( ctx, &test->output, exec_result ); + + _context_destroy( runner, ctx ); + return ok; +} + +ulong +fd_exec_instr_test_run( fd_exec_instr_test_runner_t * runner, + fd_exec_test_instr_context_t const * input, + fd_exec_test_instr_effects_t ** output, + void * output_buf, + ulong output_bufsz ) { + + fd_exec_instr_ctx_t ctx[1]; + _context_create( runner, ctx, input ); + + FD_LOG_WARNING(( "TODO" )); + (void)output; (void)output_buf; (void)output_bufsz; + + _context_destroy( runner, ctx ); + return 0UL; +} diff --git a/src/flamenco/runtime/tests/fd_exec_instr_test.h b/src/flamenco/runtime/tests/fd_exec_instr_test.h new file mode 100644 index 0000000000..37fda480ae --- /dev/null +++ b/src/flamenco/runtime/tests/fd_exec_instr_test.h @@ -0,0 +1,74 @@ +#ifndef HEADER_fd_src_flamenco_runtime_tests_fd_exec_instr_test_h +#define HEADER_fd_src_flamenco_runtime_tests_fd_exec_instr_test_h + +/* fd_exec_instr_test.h provides APIs for running instruction processor + tests. */ + +#include "fd_exec_test.pb.h" +#include "../../../funk/fd_funk.h" + +/* fd_exec_instr_test_runner_t provides fake fd_exec_instr_ctx_t to + test processing of individual instructions. */ + +struct fd_exec_instr_test_runner_private; +typedef struct fd_exec_instr_test_runner_private fd_exec_instr_test_runner_t; + +FD_PROTOTYPES_BEGIN + +/* Constructors */ + +ulong +fd_exec_instr_test_runner_align( void ); + +ulong +fd_exec_instr_test_runner_footprint( void ); + +/* fd_exec_instr_test_runner_new formats a memory region for use as an + instruction test runner. mem must be part of an fd_wksp. Does + additional wksp allocs. wksp_tag is the tag used for wksp allocs + managed by the runner. Returns newly created runner on success. On + failure, returns NULL and logs reason for error. */ + +fd_exec_instr_test_runner_t * +fd_exec_instr_test_runner_new( void * mem, + ulong wksp_tag ); + +/* fd_exec_instr_test_runner_delete frees wksp allocations managed by + runner and returns the memory region backing runner itself back to + the caller. */ + +void * +fd_exec_instr_test_runner_delete( fd_exec_instr_test_runner_t * runner ); + +/* User API */ + +/* fd_exec_instr_fixture_run executes the given instruction processing + fixture and validates that the actual result matches the expected. + Returns 1 on success. On failure, returns 0 and logs reason for + error to warning log. Uses fd_scratch. */ + +int +fd_exec_instr_fixture_run( fd_exec_instr_test_runner_t * runner, + fd_exec_test_instr_fixture_t const * test ); + +/* fd_exec_instr_test_run executes a given instruction context (input) + and returns the effects of executing that instruction to the caller. + output_buf points to a memory region of output_bufsz bytes where the + result is allocated into. On successful execution, *output points + to a newly created instruction effects object, and returns the number + of bytes allocated at output_buf. (The caller can use this to shrink + the output buffer) Note that an instruction that errored (in the + runtime) is also considered a successful execution. On failure to + execute, returns 0UL and leaves *object undefined and logs reason + for failure. Reasons for failure include insufficient output_bufsz. */ + +ulong +fd_exec_instr_test_run( fd_exec_instr_test_runner_t * runner, + fd_exec_test_instr_context_t const * input, + fd_exec_test_instr_effects_t ** output, + void * output_buf, + ulong output_bufsz ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_runtime_tests_fd_exec_instr_test_h */ diff --git a/src/flamenco/runtime/tests/fd_exec_test.pb.c b/src/flamenco/runtime/tests/fd_exec_test.pb.c new file mode 100644 index 0000000000..3f81f8693e --- /dev/null +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.c @@ -0,0 +1,27 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8-dev */ + +#include "fd_exec_test.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(FD_EXEC_TEST_FEATURE_SET, fd_exec_test_feature_set_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_ACCT_STATE, fd_exec_test_acct_state_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_INSTR_ACCT, fd_exec_test_instr_acct_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_INSTR_CONTEXT, fd_exec_test_instr_context_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_INSTR_EFFECTS, fd_exec_test_instr_effects_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_INSTR_FIXTURE, fd_exec_test_instr_fixture_t, AUTO) + + + diff --git a/src/flamenco/runtime/tests/fd_exec_test.pb.h b/src/flamenco/runtime/tests/fd_exec_test.pb.h new file mode 100644 index 0000000000..ab3ee43821 --- /dev/null +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.h @@ -0,0 +1,214 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8-dev */ + +#ifndef PB_IO_FIREDANCER_RUNTIME_TEST_FD_EXEC_TEST_PB_H_INCLUDED +#define PB_IO_FIREDANCER_RUNTIME_TEST_FD_EXEC_TEST_PB_H_INCLUDED + +#include "../../nanopb/pb_firedancer.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* A set of feature flags. */ +typedef struct fd_exec_test_feature_set { + /* Every item in this list marks an enabled feature. The value of + each item is the first 8 bytes of the feature ID as a little- + endian integer. */ + pb_size_t features_count; + uint64_t *features; +} fd_exec_test_feature_set_t; + +/* The complete state of an account excluding its public key. */ +typedef struct fd_exec_test_acct_state { + /* The account key. Can be ommitted if obvious from the context. */ + bool has_address; + pb_byte_t address[32]; + uint64_t lamports; + pb_bytes_array_t *data; + bool executable; + uint64_t rent_epoch; + pb_byte_t owner[32]; +} fd_exec_test_acct_state_t; + +typedef struct fd_exec_test_instr_acct { + /* Selects an account in an external list */ + uint32_t index; + bool is_writable; + bool is_signer; +} fd_exec_test_instr_acct_t; + +/* The execution context of an instruction. Contains all required + information to independently replay an instruction. */ +typedef struct fd_exec_test_instr_context { + /* The program invoked. */ + pb_byte_t program_id[32]; + /* The BPF loader ID if the program_id is a user deployed program. */ + bool has_loader_id; + pb_byte_t loader_id[32]; + fd_exec_test_feature_set_t feature_set; + /* Account state accessed by the instruction. */ + pb_size_t accounts_count; + struct fd_exec_test_acct_state *accounts; + /* Account access list for this instruction (refers to above accounts list) */ + pb_size_t instr_accounts_count; + struct fd_exec_test_instr_acct *instr_accounts; + /* The input data passed to program execution. */ + pb_bytes_array_t *data; +} fd_exec_test_instr_context_t; + +/* The results of executing an InstrContext. */ +typedef struct fd_exec_test_instr_effects { + /* result is zero if the instruction executed succesfully. + Otherwise, a non-zero error code. Error codes are implementation + defined. */ + int32_t result; + /* Some error cases additionally have a custom error code. Unlike + the expected_result, this is stable across clients. */ + bool has_custom_err; + uint32_t custom_err; + /* Copies of accounts that were changed. May be in an arbitrary + order. The pubkey of each account is unique in this list. Each + account address modified here must also be in the + InstrContext. */ + pb_size_t modified_accounts_count; + struct fd_exec_test_acct_state *modified_accounts; +} fd_exec_test_instr_effects_t; + +/* An instruction processing test fixture. */ +typedef struct fd_exec_test_instr_fixture { + fd_exec_test_instr_context_t input; + fd_exec_test_instr_effects_t output; +} fd_exec_test_instr_fixture_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define FD_EXEC_TEST_FEATURE_SET_INIT_DEFAULT {0, NULL} +#define FD_EXEC_TEST_ACCT_STATE_INIT_DEFAULT {false, {0}, 0, NULL, 0, 0, {0}} +#define FD_EXEC_TEST_INSTR_ACCT_INIT_DEFAULT {0, 0, 0} +#define FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT {{0}, false, {0}, FD_EXEC_TEST_FEATURE_SET_INIT_DEFAULT, 0, NULL, 0, NULL, NULL} +#define FD_EXEC_TEST_INSTR_EFFECTS_INIT_DEFAULT {0, false, 0, 0, NULL} +#define FD_EXEC_TEST_INSTR_FIXTURE_INIT_DEFAULT {FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT, FD_EXEC_TEST_INSTR_EFFECTS_INIT_DEFAULT} +#define FD_EXEC_TEST_FEATURE_SET_INIT_ZERO {0, NULL} +#define FD_EXEC_TEST_ACCT_STATE_INIT_ZERO {false, {0}, 0, NULL, 0, 0, {0}} +#define FD_EXEC_TEST_INSTR_ACCT_INIT_ZERO {0, 0, 0} +#define FD_EXEC_TEST_INSTR_CONTEXT_INIT_ZERO {{0}, false, {0}, FD_EXEC_TEST_FEATURE_SET_INIT_ZERO, 0, NULL, 0, NULL, NULL} +#define FD_EXEC_TEST_INSTR_EFFECTS_INIT_ZERO {0, false, 0, 0, NULL} +#define FD_EXEC_TEST_INSTR_FIXTURE_INIT_ZERO {FD_EXEC_TEST_INSTR_CONTEXT_INIT_ZERO, FD_EXEC_TEST_INSTR_EFFECTS_INIT_ZERO} + +/* Field tags (for use in manual encoding/decoding) */ +#define FD_EXEC_TEST_FEATURE_SET_FEATURES_TAG 1 +#define FD_EXEC_TEST_ACCT_STATE_ADDRESS_TAG 1 +#define FD_EXEC_TEST_ACCT_STATE_LAMPORTS_TAG 2 +#define FD_EXEC_TEST_ACCT_STATE_DATA_TAG 3 +#define FD_EXEC_TEST_ACCT_STATE_EXECUTABLE_TAG 4 +#define FD_EXEC_TEST_ACCT_STATE_RENT_EPOCH_TAG 5 +#define FD_EXEC_TEST_ACCT_STATE_OWNER_TAG 6 +#define FD_EXEC_TEST_INSTR_ACCT_INDEX_TAG 1 +#define FD_EXEC_TEST_INSTR_ACCT_IS_WRITABLE_TAG 2 +#define FD_EXEC_TEST_INSTR_ACCT_IS_SIGNER_TAG 3 +#define FD_EXEC_TEST_INSTR_CONTEXT_PROGRAM_ID_TAG 1 +#define FD_EXEC_TEST_INSTR_CONTEXT_LOADER_ID_TAG 2 +#define FD_EXEC_TEST_INSTR_CONTEXT_FEATURE_SET_TAG 3 +#define FD_EXEC_TEST_INSTR_CONTEXT_ACCOUNTS_TAG 4 +#define FD_EXEC_TEST_INSTR_CONTEXT_INSTR_ACCOUNTS_TAG 5 +#define FD_EXEC_TEST_INSTR_CONTEXT_DATA_TAG 6 +#define FD_EXEC_TEST_INSTR_EFFECTS_RESULT_TAG 1 +#define FD_EXEC_TEST_INSTR_EFFECTS_CUSTOM_ERR_TAG 2 +#define FD_EXEC_TEST_INSTR_EFFECTS_MODIFIED_ACCOUNTS_TAG 3 +#define FD_EXEC_TEST_INSTR_FIXTURE_INPUT_TAG 1 +#define FD_EXEC_TEST_INSTR_FIXTURE_OUTPUT_TAG 2 + +/* Struct field encoding specification for nanopb */ +#define FD_EXEC_TEST_FEATURE_SET_FIELDLIST(X, a) \ +X(a, POINTER, REPEATED, FIXED64, features, 1) +#define FD_EXEC_TEST_FEATURE_SET_CALLBACK NULL +#define FD_EXEC_TEST_FEATURE_SET_DEFAULT NULL + +#define FD_EXEC_TEST_ACCT_STATE_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, FIXED_LENGTH_BYTES, address, 1) \ +X(a, STATIC, REQUIRED, UINT64, lamports, 2) \ +X(a, POINTER, REQUIRED, BYTES, data, 3) \ +X(a, STATIC, REQUIRED, BOOL, executable, 4) \ +X(a, STATIC, REQUIRED, UINT64, rent_epoch, 5) \ +X(a, STATIC, REQUIRED, FIXED_LENGTH_BYTES, owner, 6) +#define FD_EXEC_TEST_ACCT_STATE_CALLBACK NULL +#define FD_EXEC_TEST_ACCT_STATE_DEFAULT NULL + +#define FD_EXEC_TEST_INSTR_ACCT_FIELDLIST(X, a) \ +X(a, STATIC, REQUIRED, UINT32, index, 1) \ +X(a, STATIC, REQUIRED, BOOL, is_writable, 2) \ +X(a, STATIC, REQUIRED, BOOL, is_signer, 3) +#define FD_EXEC_TEST_INSTR_ACCT_CALLBACK NULL +#define FD_EXEC_TEST_INSTR_ACCT_DEFAULT NULL + +#define FD_EXEC_TEST_INSTR_CONTEXT_FIELDLIST(X, a) \ +X(a, STATIC, REQUIRED, FIXED_LENGTH_BYTES, program_id, 1) \ +X(a, STATIC, OPTIONAL, FIXED_LENGTH_BYTES, loader_id, 2) \ +X(a, STATIC, REQUIRED, MESSAGE, feature_set, 3) \ +X(a, POINTER, REPEATED, MESSAGE, accounts, 4) \ +X(a, POINTER, REPEATED, MESSAGE, instr_accounts, 5) \ +X(a, POINTER, REQUIRED, BYTES, data, 6) +#define FD_EXEC_TEST_INSTR_CONTEXT_CALLBACK NULL +#define FD_EXEC_TEST_INSTR_CONTEXT_DEFAULT NULL +#define fd_exec_test_instr_context_t_feature_set_MSGTYPE fd_exec_test_feature_set_t +#define fd_exec_test_instr_context_t_accounts_MSGTYPE fd_exec_test_acct_state_t +#define fd_exec_test_instr_context_t_instr_accounts_MSGTYPE fd_exec_test_instr_acct_t + +#define FD_EXEC_TEST_INSTR_EFFECTS_FIELDLIST(X, a) \ +X(a, STATIC, REQUIRED, INT32, result, 1) \ +X(a, STATIC, OPTIONAL, UINT32, custom_err, 2) \ +X(a, POINTER, REPEATED, MESSAGE, modified_accounts, 3) +#define FD_EXEC_TEST_INSTR_EFFECTS_CALLBACK NULL +#define FD_EXEC_TEST_INSTR_EFFECTS_DEFAULT NULL +#define fd_exec_test_instr_effects_t_modified_accounts_MSGTYPE fd_exec_test_acct_state_t + +#define FD_EXEC_TEST_INSTR_FIXTURE_FIELDLIST(X, a) \ +X(a, STATIC, REQUIRED, MESSAGE, input, 1) \ +X(a, STATIC, REQUIRED, MESSAGE, output, 2) +#define FD_EXEC_TEST_INSTR_FIXTURE_CALLBACK NULL +#define FD_EXEC_TEST_INSTR_FIXTURE_DEFAULT NULL +#define fd_exec_test_instr_fixture_t_input_MSGTYPE fd_exec_test_instr_context_t +#define fd_exec_test_instr_fixture_t_output_MSGTYPE fd_exec_test_instr_effects_t + +extern const pb_msgdesc_t fd_exec_test_feature_set_t_msg; +extern const pb_msgdesc_t fd_exec_test_acct_state_t_msg; +extern const pb_msgdesc_t fd_exec_test_instr_acct_t_msg; +extern const pb_msgdesc_t fd_exec_test_instr_context_t_msg; +extern const pb_msgdesc_t fd_exec_test_instr_effects_t_msg; +extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define FD_EXEC_TEST_FEATURE_SET_FIELDS &fd_exec_test_feature_set_t_msg +#define FD_EXEC_TEST_ACCT_STATE_FIELDS &fd_exec_test_acct_state_t_msg +#define FD_EXEC_TEST_INSTR_ACCT_FIELDS &fd_exec_test_instr_acct_t_msg +#define FD_EXEC_TEST_INSTR_CONTEXT_FIELDS &fd_exec_test_instr_context_t_msg +#define FD_EXEC_TEST_INSTR_EFFECTS_FIELDS &fd_exec_test_instr_effects_t_msg +#define FD_EXEC_TEST_INSTR_FIXTURE_FIELDS &fd_exec_test_instr_fixture_t_msg + +/* Maximum encoded size of messages (where known) */ +/* fd_exec_test_FeatureSet_size depends on runtime parameters */ +/* fd_exec_test_AcctState_size depends on runtime parameters */ +/* fd_exec_test_InstrContext_size depends on runtime parameters */ +/* fd_exec_test_InstrEffects_size depends on runtime parameters */ +/* fd_exec_test_InstrFixture_size depends on runtime parameters */ +#define FD_EXEC_TEST_INSTR_ACCT_SIZE 10 + +/* Mapping from canonical names (mangle_names or overridden package name) */ +#define io_firedancer_runtime_test_FeatureSet fd_exec_test_FeatureSet +#define io_firedancer_runtime_test_AcctState fd_exec_test_AcctState +#define io_firedancer_runtime_test_InstrAcct fd_exec_test_InstrAcct +#define io_firedancer_runtime_test_InstrContext fd_exec_test_InstrContext +#define io_firedancer_runtime_test_InstrEffects fd_exec_test_InstrEffects +#define io_firedancer_runtime_test_InstrFixture fd_exec_test_InstrFixture + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/flamenco/runtime/tests/fd_exec_test.proto b/src/flamenco/runtime/tests/fd_exec_test.proto new file mode 100644 index 0000000000..1590a8e2de --- /dev/null +++ b/src/flamenco/runtime/tests/fd_exec_test.proto @@ -0,0 +1,92 @@ +syntax = "proto2"; +package io.firedancer.runtime.test; + +// Regenerate with: +// +// nanopb_generator.py -I ../.. -I . -L "" -C fd_exec_test.proto + +import "nanopb.proto"; +option (nanopb_fileopt).package = "fd_exec_test"; +option (nanopb_fileopt).include = "../../nanopb/pb_firedancer.h"; + +// A set of feature flags. +message FeatureSet { + // Every item in this list marks an enabled feature. The value of + // each item is the first 8 bytes of the feature ID as a little- + // endian integer. + repeated fixed64 features = 1 + [(nanopb).type = FT_POINTER]; +} + +// The complete state of an account excluding its public key. +message AcctState { + // The account key. Can be ommitted if obvious from the context. + optional bytes address = 1 + [(nanopb).max_size = 32, (nanopb).fixed_length = true]; + + required uint64 lamports = 2; + required bytes data = 3 + [(nanopb).type = FT_POINTER]; + required bool executable = 4; + required uint64 rent_epoch = 5; + required bytes owner = 6 + [(nanopb).max_size = 32, (nanopb).fixed_length = true]; +} + +message InstrAcct { + // Selects an account in an external list + required uint32 index = 1; + required bool is_writable = 2; + required bool is_signer = 3; +} + +// The execution context of an instruction. Contains all required +// information to independently replay an instruction. +message InstrContext { + // The program invoked. + required bytes program_id = 1 + [(nanopb).max_size = 32, (nanopb).fixed_length = true]; + + // The BPF loader ID if the program_id is a user deployed program. + optional bytes loader_id = 2 + [(nanopb).max_size = 32, (nanopb).fixed_length = true]; + + required FeatureSet feature_set = 3; + + // Account state accessed by the instruction. + repeated AcctState accounts = 4 + [(nanopb).type = FT_POINTER]; + + // Account access list for this instruction (refers to above accounts list) + repeated InstrAcct instr_accounts = 5 + [(nanopb).type = FT_POINTER]; + + // The input data passed to program execution. + required bytes data = 6 + [(nanopb).type = FT_POINTER]; +} + +// The results of executing an InstrContext. +message InstrEffects { + // result is zero if the instruction executed succesfully. + // Otherwise, a non-zero error code. Error codes are implementation + // defined. + required int32 result = 1; + + // Some error cases additionally have a custom error code. Unlike + // the expected_result, this is stable across clients. + optional uint32 custom_err = 2; + + // Copies of accounts that were changed. May be in an arbitrary + // order. The pubkey of each account is unique in this list. Each + // account address modified here must also be in the + // InstrContext. + repeated AcctState modified_accounts = 3 + [(nanopb).type = FT_POINTER]; +} + +// An instruction processing test fixture. +message InstrFixture { + required InstrContext input = 1; + required InstrEffects output = 2; +} diff --git a/src/flamenco/runtime/tests/test_exec_instr.c b/src/flamenco/runtime/tests/test_exec_instr.c new file mode 100644 index 0000000000..c55fb62969 --- /dev/null +++ b/src/flamenco/runtime/tests/test_exec_instr.c @@ -0,0 +1,78 @@ +#define FD_SCRATCH_USE_HANDHOLDING 1 +#include "../../fd_flamenco_base.h" +#include "fd_exec_instr_test.h" +#include +#include +#include +#include +#include "../../nanopb/pb_firedancer.h" +#include "../../nanopb/pb_decode.h" +#include "fd_exec_test.pb.h" + +static int +run_test( fd_exec_instr_test_runner_t * runner, + char const * path ) { + + /* Read file content to memory */ + + int file = open( path, O_RDONLY ); + struct stat st; + FD_TEST( 0==fstat( file, &st ) ); + ulong file_sz = (ulong)st.st_size; + uchar * buf = fd_scratch_alloc( 1, file_sz ); + FD_TEST( 0==fd_io_read( file, buf, file_sz, file_sz, &file_sz ) ); + FD_TEST( 0==close( file ) ); + + /* Deserialize (unfortunately uses libc malloc) */ + + pb_istream_t istream = pb_istream_from_buffer( buf, file_sz ); + fd_exec_test_instr_fixture_t fixture[1] = {0}; + int decode_ok = pb_decode_ex( &istream, &fd_exec_test_instr_fixture_t_msg, fixture, PB_DECODE_NOINIT ); + if( FD_UNLIKELY( !decode_ok ) ) { + FD_LOG_WARNING(( "%s: failed to decode (%s)", path, PB_GET_ERROR(&istream) )); + pb_release( &fd_exec_test_instr_fixture_t_msg, fixture ); + return 0; + } + + /* Run test */ + + FD_LOG_NOTICE(( "Running test %s", path )); + int ok = fd_exec_instr_fixture_run( runner, fixture ); + pb_release( &fd_exec_test_instr_fixture_t_msg, fixture ); + return ok; +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + + /* TODO switch to leap API and set up a thread pool once available */ + ulong cpu_idx = fd_tile_cpu_id( fd_tile_idx() ); + if( cpu_idx>=fd_shmem_cpu_cnt() ) cpu_idx = 0UL; + fd_wksp_t * wksp = fd_wksp_new_anonymous( FD_SHMEM_GIGANTIC_PAGE_SZ, 4UL, fd_shmem_cpu_idx( fd_shmem_numa_idx( cpu_idx ) ), "wksp", 0UL ); + + ulong scratch_fmem[ 64UL ] __attribute((aligned(FD_SCRATCH_FMEM_ALIGN))); + uchar * scratch_smem = fd_wksp_alloc_laddr( wksp, FD_SCRATCH_SMEM_ALIGN, 1UL<<31, 1 ); + fd_scratch_attach( scratch_smem, scratch_fmem, 1UL<<31, 64UL ); + + void * runner_mem = fd_wksp_alloc_laddr( wksp, fd_exec_instr_test_runner_align(), fd_exec_instr_test_runner_footprint(), 2 ); + fd_exec_instr_test_runner_t * runner = fd_exec_instr_test_runner_new( runner_mem, 3 ); + + ulong fail_cnt = 0UL; + for( int j=1; j0UL; +}