LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_txn_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 348 0.0 %
Date: 2026-01-14 05:08:54 Functions: 0 6 0.0 %

          Line data    Source code
       1             : #include "fd_solfuzz.h"
       2             : #include "fd_solfuzz_private.h"
       3             : #include "fd_txn_harness.h"
       4             : #include "../fd_runtime.h"
       5             : #include "../fd_executor.h"
       6             : #include "../program/fd_builtin_programs.h"
       7             : #include "../sysvar/fd_sysvar_clock.h"
       8             : #include "../sysvar/fd_sysvar_epoch_schedule.h"
       9             : #include "../sysvar/fd_sysvar_recent_hashes.h"
      10             : #include "../sysvar/fd_sysvar_rent.h"
      11             : #include "../sysvar/fd_sysvar_slot_hashes.h"
      12             : #include "../sysvar/fd_sysvar_stake_history.h"
      13             : #include "../../accdb/fd_accdb_impl_v1.h"
      14             : #include "../../log_collector/fd_log_collector.h"
      15             : #include <assert.h>
      16             : 
      17             : /* Macros to append data to construct a serialized transaction
      18             :    without exceeding bounds */
      19           0 : #define FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _to_add, _sz ) __extension__({ \
      20           0 :    if( FD_UNLIKELY( (*_cur_data)+_sz>_begin+FD_TXN_MTU ) ) return ULONG_MAX;          \
      21           0 :    fd_memcpy( *_cur_data, _to_add, _sz );                                             \
      22           0 :    *_cur_data += _sz;                                                                 \
      23           0 : })
      24             : 
      25           0 : #define FD_CHECKED_ADD_CU16_TO_TXN_DATA( _begin, _cur_data, _to_add ) __extension__({ \
      26           0 :    do {                                                                               \
      27           0 :       uchar _buf[3];                                                                  \
      28           0 :       fd_bincode_encode_ctx_t _encode_ctx = { .data = _buf, .dataend = _buf+3 };      \
      29           0 :       fd_bincode_compact_u16_encode( &_to_add, &_encode_ctx );                        \
      30           0 :       ulong _sz = (ulong) ((uchar *)_encode_ctx.data - _buf );                        \
      31           0 :       FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _buf, _sz );                     \
      32           0 :    } while(0);                                                                        \
      33           0 : })
      34             : 
      35             : /* Writes a transaction account to the output Protobuf message's
      36             :    resulting_state field. out_account_data should be a pointer to
      37             :    a contiguous region of memory that can hold the account data +
      38             :    data length. */
      39             : static void
      40             : fd_solfuzz_pb_txn_ctx_write_account( fd_pubkey_t const *              pubkey,
      41             :                                      fd_account_meta_t const *        meta,
      42             :                                      fd_exec_test_resulting_state_t * resulting_state,
      43           0 :                                      pb_bytes_array_t *               out_account_data ) {
      44           0 :   fd_exec_test_acct_state_t * out_acct = &resulting_state->acct_states[ resulting_state->acct_states_count++ ];
      45           0 :   memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
      46             : 
      47             :   /* Copy over account content */
      48           0 :   memcpy( out_acct->address, pubkey, sizeof(fd_pubkey_t) );
      49             : 
      50           0 :   out_acct->lamports = meta->lamports;
      51             : 
      52           0 :   if( meta->dlen>0UL ) {
      53           0 :     out_acct->data       = out_account_data;
      54           0 :     out_acct->data->size = (pb_size_t)meta->dlen;
      55           0 :     fd_memcpy( out_acct->data->bytes, fd_account_data( meta ), meta->dlen );
      56           0 :   }
      57             : 
      58           0 :   out_acct->executable = meta->executable;
      59           0 :   memcpy( out_acct->owner, meta->owner, sizeof(fd_pubkey_t) );
      60           0 : }
      61             : 
      62             : static void
      63           0 : fd_solfuzz_txn_ctx_destroy( fd_solfuzz_runner_t * runner ) {
      64           0 :   fd_accdb_clear( runner->accdb_admin );
      65           0 :   fd_progcache_clear( runner->progcache_admin );
      66             : 
      67             :   /* In order to check for leaks in the workspace, we need to compact the
      68             :      allocators. Without doing this, empty superblocks may be retained
      69             :      by the fd_alloc instance, which mean we cannot check for leaks. */
      70           0 :   fd_alloc_compact( runner->accdb_admin->funk->alloc );
      71           0 :   fd_alloc_compact( runner->progcache_admin->funk->alloc );
      72           0 : }
      73             : 
      74             : /* Creates transaction execution context for a single test case.
      75             :    Returns a parsed txn descriptor on success and NULL on failure. */
      76             : static fd_txn_p_t *
      77             : fd_solfuzz_pb_txn_ctx_create( fd_solfuzz_runner_t *              runner,
      78           0 :                               fd_exec_test_txn_context_t const * test_ctx ) {
      79           0 :   fd_accdb_user_t * accdb = runner->accdb;
      80             : 
      81             :   /* Default slot */
      82           0 :   ulong slot = test_ctx->slot_ctx.slot ? test_ctx->slot_ctx.slot : 10; // Arbitrary default > 0
      83             : 
      84             :   /* Set up the funk transaction */
      85           0 :   fd_funk_txn_xid_t xid = { .ul = { slot, runner->bank->data->idx } };
      86           0 :   fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
      87           0 :   fd_accdb_attach_child        ( runner->accdb_admin,     &parent_xid, &xid );
      88           0 :   fd_progcache_txn_attach_child( runner->progcache_admin, &parent_xid, &xid );
      89             : 
      90             :   /* Set up slot context */
      91           0 :   fd_banks_clear_bank( runner->banks, runner->bank, 64UL );
      92             : 
      93             :   /* Restore feature flags */
      94           0 :   fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_ctx.features;
      95           0 :   fd_features_t * features_bm = fd_bank_features_modify( runner->bank );
      96           0 :   if( !fd_solfuzz_pb_restore_features( features_bm, feature_set ) ) {
      97           0 :     return NULL;
      98           0 :   }
      99             : 
     100             :   /* Set bank variables (defaults obtained from GenesisConfig::default
     101             :      in Agave) */
     102             : 
     103           0 :   fd_bank_slot_set( runner->bank, slot );
     104           0 :   fd_bank_parent_slot_set( runner->bank, fd_bank_slot_get( runner->bank ) - 1UL );
     105             : 
     106             :   /* Initialize builtin accounts */
     107           0 :   fd_builtin_programs_init( runner->bank, accdb, &xid, NULL );
     108             : 
     109             :   /* Load account states into funk (note this is different from the account keys):
     110             :     Account state = accounts to populate Funk
     111             :     Account keys = account keys that the transaction needs */
     112           0 :   for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
     113             :     /* Load the accounts into the account manager
     114             :        Borrowed accounts get reset anyways - we just need to load the account somewhere */
     115           0 :     fd_solfuzz_pb_load_account( runner->runtime, accdb, &xid, &test_ctx->account_shared_data[i], i );
     116           0 :   }
     117             : 
     118             :   /* Setup Bank manager */
     119           0 :   fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( runner->bank );
     120           0 :   fee_rate_governor->burn_percent                  = 50;
     121           0 :   fee_rate_governor->min_lamports_per_signature    = 0;
     122           0 :   fee_rate_governor->max_lamports_per_signature    = 0;
     123           0 :   fee_rate_governor->target_lamports_per_signature = 10000;
     124           0 :   fee_rate_governor->target_signatures_per_slot    = 20000;
     125             : 
     126             :   /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/bank.rs#L1249-L1251 */
     127           0 :   fd_runtime_new_fee_rate_governor_derived( runner->bank, fd_bank_parent_signature_cnt_get( runner->bank ) );
     128             : 
     129           0 :   fd_bank_ticks_per_slot_set( runner->bank, 64 );
     130             : 
     131           0 :   fd_bank_slots_per_year_set( runner->bank, SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(fd_bank_ticks_per_slot_get( runner->bank )) );
     132             : 
     133             :   /* Ensure the presence of */
     134           0 :   fd_epoch_schedule_t epoch_schedule_[1];
     135           0 :   fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( accdb, &xid, epoch_schedule_ );
     136           0 :   FD_TEST( epoch_schedule );
     137           0 :   fd_bank_epoch_schedule_set( runner->bank, *epoch_schedule );
     138             : 
     139           0 :   fd_rent_t rent[1];
     140           0 :   FD_TEST( fd_sysvar_rent_read( accdb, &xid, rent ) );
     141           0 :   fd_bank_rent_set( runner->bank, *rent );
     142             : 
     143           0 :   uchar __attribute__((aligned(FD_SLOT_HASHES_GLOBAL_ALIGN))) slot_hashes_mem[ FD_SYSVAR_SLOT_HASHES_FOOTPRINT ];
     144           0 :   fd_slot_hashes_global_t * slot_hashes = fd_sysvar_slot_hashes_read( accdb, &xid, slot_hashes_mem );
     145           0 :   FD_TEST( slot_hashes );
     146             : 
     147           0 :   fd_stake_history_t stake_history_[1];
     148           0 :   fd_stake_history_t * stake_history = fd_sysvar_stake_history_read( accdb, &xid, stake_history_ );
     149           0 :   FD_TEST( stake_history );
     150             : 
     151           0 :   fd_sol_sysvar_clock_t clock_[1];
     152           0 :   fd_sol_sysvar_clock_t const * clock = fd_sysvar_clock_read( accdb, &xid, clock_ );
     153           0 :   FD_TEST( clock );
     154             : 
     155             :   /* Epoch schedule and rent get set from the epoch bank */
     156           0 :   fd_sysvar_epoch_schedule_init( runner->bank, accdb, &xid, NULL );
     157           0 :   fd_sysvar_rent_init( runner->bank, accdb, &xid, NULL );
     158             : 
     159             :   /* Blockhash queue is given in txn message. We need to populate the following two fields:
     160             :      - block_hash_queue
     161             :      - recent_block_hashes */
     162           0 :   ulong num_blockhashes = test_ctx->blockhash_queue_count;
     163             : 
     164             :   /* Blockhash queue init */
     165           0 :   ulong blockhash_seed; FD_TEST( fd_rng_secure( &blockhash_seed, sizeof(ulong) ) );
     166           0 :   fd_blockhashes_t * blockhashes = fd_blockhashes_init( fd_bank_block_hash_queue_modify( runner->bank ), blockhash_seed );
     167             : 
     168             :   // Save lamports per signature for most recent blockhash, if sysvar cache contains recent block hashes
     169           0 :   uchar __attribute__((aligned(FD_SYSVAR_RECENT_HASHES_ALIGN))) rbh_mem[FD_SYSVAR_RECENT_HASHES_FOOTPRINT];
     170           0 :   fd_recent_block_hashes_t const * rbh_sysvar = fd_sysvar_recent_hashes_read( accdb, &xid, rbh_mem );
     171           0 :   fd_recent_block_hashes_t rbh[1];
     172           0 :   if( rbh_sysvar ) {
     173           0 :     rbh->hashes = rbh_sysvar->hashes;
     174           0 :   }
     175             : 
     176           0 :   if( rbh_sysvar && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
     177           0 :     fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
     178           0 :     if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
     179           0 :       fd_bank_rbh_lamports_per_sig_set( runner->bank, last->fee_calculator.lamports_per_signature );
     180           0 :     }
     181           0 :   }
     182             : 
     183             :   // Blockhash_queue[end] = last (latest) hash
     184             :   // Blockhash_queue[0] = genesis hash
     185           0 :   if( num_blockhashes > 0 ) {
     186           0 :     fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( runner->bank );
     187           0 :     memcpy( genesis_hash->hash, test_ctx->blockhash_queue[0]->bytes, sizeof(fd_hash_t) );
     188             : 
     189           0 :     for( ulong i = 0; i < num_blockhashes; ++i ) {
     190           0 :       fd_hash_t blockhash = FD_LOAD( fd_hash_t, test_ctx->blockhash_queue[i]->bytes );
     191             :       /* Drop duplicate blockhashes */
     192           0 :       if( FD_UNLIKELY( fd_blockhash_map_idx_remove( blockhashes->map, &blockhash, ULONG_MAX, blockhashes->d.deque )!=ULONG_MAX ) ) {
     193           0 :         FD_BASE58_ENCODE_32_BYTES( blockhash.hash, blockhash_b58 );
     194           0 :         FD_LOG_WARNING(( "Fuzz input has a duplicate blockhash %s at index %lu", blockhash_b58, i ));
     195           0 :       }
     196             :       // Recent block hashes cap is 150 (actually 151), while blockhash queue capacity is 300 (actually 301)
     197           0 :       fd_bank_poh_set( runner->bank, blockhash );
     198           0 :       fd_sysvar_recent_hashes_update( runner->bank, accdb, &xid, NULL );
     199           0 :     }
     200           0 :   } else {
     201             :     // Add a default empty blockhash and use it as genesis
     202           0 :     num_blockhashes = 1;
     203           0 :     *fd_bank_genesis_hash_modify( runner->bank ) = (fd_hash_t){0};
     204           0 :     fd_bank_poh_set( runner->bank, (fd_hash_t){0} );
     205           0 :     fd_sysvar_recent_hashes_update( runner->bank, accdb, &xid, NULL );
     206           0 :   }
     207             : 
     208             :   /* Restore sysvars from account context */
     209           0 :   fd_sysvar_cache_restore_fuzz( runner->bank, runner->accdb, &xid );
     210             : 
     211             :   /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
     212           0 :   fd_txn_p_t * txn    = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
     213           0 :   ulong        msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->tx );
     214           0 :   if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
     215           0 :     return NULL;
     216           0 :   }
     217             : 
     218             :   /* Set up txn descriptor from raw data */
     219           0 :   if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
     220           0 :     return NULL;
     221           0 :   }
     222             : 
     223           0 :   txn->payload_sz = msg_sz;
     224             : 
     225           0 :   return txn;
     226           0 : }
     227             : 
     228             : ulong
     229             : fd_solfuzz_pb_txn_serialize( uchar *                                      txn_raw_begin,
     230           0 :                              fd_exec_test_sanitized_transaction_t const * tx ) {
     231           0 :   uchar * txn_raw_cur_ptr = txn_raw_begin;
     232             : 
     233             :   /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
     234             :      Note that although documentation interchangably refers to the signature cnt as a compact-u16
     235             :      and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
     236             :      is represented the same way anyways and can be parsed identically. */
     237             :   // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
     238           0 :   uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
     239           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
     240           0 :   for( uchar i = 0; i < signature_cnt; ++i ) {
     241           0 :     fd_signature_t sig = {0};
     242           0 :     if( tx->signatures && tx->signatures[i] ) sig = FD_LOAD( fd_signature_t, tx->signatures[i]->bytes );
     243           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &sig, FD_TXN_SIGNATURE_SZ );
     244           0 :   }
     245             : 
     246             :   /* Message */
     247             :   /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
     248             :      https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
     249             : 
     250             :      We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
     251             :      collisions with the header_b0 tag. */
     252           0 :   uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
     253           0 :   if( !tx->message.is_legacy ) {
     254           0 :     uchar header_b0 = (uchar) 0x80UL;
     255           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
     256           0 :   }
     257             : 
     258             :   /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
     259           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
     260           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
     261           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
     262             : 
     263             :   /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
     264             :   // Array length is a compact u16
     265           0 :   ushort num_acct_keys = (ushort) tx->message.account_keys_count;
     266           0 :   FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
     267           0 :   for( ushort i = 0; i < num_acct_keys; ++i ) {
     268           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
     269           0 :   }
     270             : 
     271             :   /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
     272             :   // Note: add an empty blockhash if none is provided
     273           0 :   fd_hash_t msg_rbh = {0};
     274           0 :   if( tx->message.recent_blockhash ) msg_rbh = FD_LOAD( fd_hash_t, tx->message.recent_blockhash->bytes );
     275           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &msg_rbh, sizeof(fd_hash_t) );
     276             : 
     277             :   /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
     278             :   // Instruction count is a compact u16
     279           0 :   ushort instr_count = (ushort) tx->message.instructions_count;
     280           0 :   FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
     281           0 :   for( ushort i = 0; i < instr_count; ++i ) {
     282             :     // Program ID index
     283           0 :     uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
     284           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
     285             : 
     286             :     // Compact array of account addresses
     287           0 :     ushort acct_count = (ushort) tx->message.instructions[i].accounts_count;
     288           0 :     FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
     289           0 :     for( ushort j = 0; j < acct_count; ++j ) {
     290           0 :       uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
     291           0 :       FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
     292           0 :     }
     293             : 
     294             :     // Compact array of 8-bit data
     295           0 :     pb_bytes_array_t * data = tx->message.instructions[i].data;
     296           0 :     ushort data_len;
     297           0 :     if( data ) {
     298           0 :       data_len = (ushort) data->size;
     299           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
     300           0 :       FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
     301           0 :     } else {
     302           0 :       data_len = 0;
     303           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
     304           0 :     }
     305           0 :   }
     306             : 
     307             :   /* Address table lookups (N/A for legacy transactions) */
     308           0 :   ushort addr_table_cnt = 0;
     309           0 :   if( !tx->message.is_legacy ) {
     310             :     /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
     311             :     // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
     312           0 :     addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
     313           0 :     FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
     314           0 :     for( ushort i = 0; i < addr_table_cnt; ++i ) {
     315             :       // Account key
     316           0 :       FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.address_table_lookups[i].account_key, sizeof(fd_pubkey_t) );
     317             : 
     318             :       // Compact array of writable indexes
     319           0 :       ushort writable_count = (ushort) tx->message.address_table_lookups[i].writable_indexes_count;
     320           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
     321           0 :       for( ushort j = 0; j < writable_count; ++j ) {
     322           0 :         uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
     323           0 :         FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
     324           0 :       }
     325             : 
     326             :       // Compact array of readonly indexes
     327           0 :       ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
     328           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
     329           0 :       for( ushort j = 0; j < readonly_count; ++j ) {
     330           0 :         uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
     331           0 :         FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
     332           0 :       }
     333           0 :     }
     334           0 :   }
     335             : 
     336           0 :   return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
     337           0 : }
     338             : 
     339             : void
     340             : fd_solfuzz_txn_ctx_exec( fd_solfuzz_runner_t * runner,
     341             :                          fd_runtime_t *        runtime,
     342             :                          fd_txn_in_t const *   txn_in,
     343             :                          int *                 exec_res,
     344           0 :                          fd_txn_out_t *        txn_out ) {
     345             : 
     346           0 :   txn_out->err.is_committable = 1;
     347             : 
     348           0 :   runtime->log.enable_vm_tracing = runner->enable_vm_tracing;
     349           0 :   uchar * tracing_mem = NULL;
     350           0 :   if( runner->enable_vm_tracing ) {
     351           0 :     tracing_mem = fd_spad_alloc_check( runner->spad, FD_RUNTIME_VM_TRACE_STATIC_ALIGN, FD_RUNTIME_VM_TRACE_STATIC_FOOTPRINT * FD_MAX_INSTRUCTION_STACK_DEPTH );
     352           0 :   }
     353             : 
     354           0 :   runtime->accdb           = runner->accdb;
     355           0 :   runtime->progcache       = runner->progcache;
     356           0 :   runtime->status_cache    = NULL;
     357           0 :   runtime->log.tracing_mem = tracing_mem;
     358           0 :   runtime->log.dumping_mem = NULL;
     359           0 :   runtime->log.capture_ctx = NULL;
     360             : 
     361           0 :   fd_runtime_prepare_and_execute_txn( runtime, runner->bank, txn_in, txn_out );
     362           0 :   *exec_res = txn_out->err.txn_err;
     363           0 : }
     364             : 
     365             : ulong
     366             : fd_solfuzz_pb_txn_run( fd_solfuzz_runner_t * runner,
     367             :                        void const *          input_,
     368             :                        void **               output_,
     369             :                        void *                output_buf,
     370           0 :                        ulong                 output_bufsz ) {
     371           0 :   fd_exec_test_txn_context_t const * input  = fd_type_pun_const( input_ );
     372           0 :   fd_exec_test_txn_result_t **       output = fd_type_pun( output_ );
     373             : 
     374           0 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     375             : 
     376             :     /* Setup the transaction context */
     377           0 :     fd_txn_p_t * txn = fd_solfuzz_pb_txn_ctx_create( runner, input );
     378           0 :     if( FD_UNLIKELY( txn==NULL ) ) {
     379           0 :       fd_solfuzz_txn_ctx_destroy( runner );
     380           0 :       return 0UL;
     381           0 :     }
     382             : 
     383             :     /* Execute the transaction against the runtime */
     384           0 :     int exec_res = 0;
     385           0 :     fd_runtime_t *       runtime = runner->runtime;
     386           0 :     fd_txn_in_t *        txn_in  = fd_spad_alloc( runner->spad, alignof(fd_txn_in_t), sizeof(fd_txn_in_t) );
     387           0 :     fd_txn_out_t *       txn_out = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), sizeof(fd_txn_out_t) );
     388           0 :     fd_log_collector_t * log     = fd_spad_alloc( runner->spad, alignof(fd_log_collector_t), sizeof(fd_log_collector_t) );
     389           0 :     runtime->log.log_collector = log;
     390           0 :     runtime->acc_pool = runner->acc_pool;
     391           0 :     txn_in->txn = txn;
     392           0 :     txn_in->bundle.is_bundle = 0;
     393           0 :     fd_solfuzz_txn_ctx_exec( runner, runtime, txn_in, &exec_res, txn_out );
     394             : 
     395             :     /* Start saving txn exec results */
     396           0 :     FD_SCRATCH_ALLOC_INIT( l, output_buf );
     397           0 :     ulong output_end = (ulong)output_buf + output_bufsz;
     398             : 
     399           0 :     fd_exec_test_txn_result_t * txn_result =
     400           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
     401           0 :                                   sizeof (fd_exec_test_txn_result_t) );
     402           0 :     if( FD_UNLIKELY( _l > output_end ) ) {
     403           0 :       abort();
     404           0 :     }
     405           0 :     fd_memset( txn_result, 0, sizeof(fd_exec_test_txn_result_t) );
     406             : 
     407             :     /* Map the nonce errors into the agave expected ones. */
     408           0 :     if( FD_UNLIKELY( exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_NONCE_ALREADY_ADVANCED ||
     409           0 :                      exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR ||
     410           0 :                      exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_WRONG_NONCE )) {
     411           0 :       exec_res = FD_RUNTIME_TXN_ERR_BLOCKHASH_NOT_FOUND;
     412           0 :     }
     413             : 
     414             :     /* Capture basic results fields */
     415           0 :     txn_result->executed                          = txn_out->err.is_committable;
     416           0 :     txn_result->sanitization_error                = !txn_out->err.is_committable;
     417           0 :     txn_result->has_resulting_state               = false;
     418           0 :     txn_result->resulting_state.acct_states_count = 0;
     419           0 :     txn_result->is_ok                             = !exec_res;
     420           0 :     txn_result->status                            = (uint32_t) -exec_res;
     421           0 :     txn_result->instruction_error                 = 0;
     422           0 :     txn_result->instruction_error_index           = 0;
     423           0 :     txn_result->custom_error                      = 0;
     424           0 :     txn_result->has_fee_details                   = false;
     425           0 :     txn_result->loaded_accounts_data_size         = txn_out->details.loaded_accounts_data_size;
     426             : 
     427           0 :     if( txn_result->sanitization_error ) {
     428             :       /* Collect fees for transactions that failed to load */
     429           0 :       if( txn_out->err.is_fees_only ) {
     430           0 :         txn_result->has_fee_details                = true;
     431           0 :         txn_result->fee_details.prioritization_fee = txn_out->details.priority_fee;
     432           0 :         txn_result->fee_details.transaction_fee    = txn_out->details.execution_fee;
     433           0 :       }
     434             : 
     435           0 :       if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
     436           0 :         txn_result->instruction_error       = (uint32_t) -txn_out->err.exec_err;
     437           0 :         txn_result->instruction_error_index = (uint32_t) txn_out->err.exec_err_idx;
     438           0 :         if( txn_out->err.exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
     439           0 :           txn_result->custom_error = txn_out->err.custom_err;
     440           0 :         }
     441           0 :       }
     442             : 
     443           0 :       ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     444             : 
     445           0 :       txn_out->err.is_committable = 0;
     446           0 :       fd_runtime_cancel_txn( runner->runtime, txn_out );
     447           0 :       fd_solfuzz_txn_ctx_destroy( runner );
     448             : 
     449           0 :       *output = txn_result;
     450           0 :       return actual_end - (ulong)output_buf;
     451             : 
     452           0 :     } else {
     453             :       /* Capture the instruction error code */
     454           0 :       if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
     455           0 :         fd_txn_t const * txn            = TXN( txn_in->txn );
     456           0 :         int              instr_err_idx  = txn_out->err.exec_err_idx;
     457           0 :         int              program_id_idx = txn->instr[instr_err_idx].program_id;
     458             : 
     459           0 :         txn_result->instruction_error       = (uint32_t) -txn_out->err.exec_err;
     460           0 :         txn_result->instruction_error_index = (uint32_t) instr_err_idx;
     461             : 
     462             :         /* If the exec err was a custom instr error and came from a precompile instruction, don't capture the custom error code. */
     463           0 :         if( txn_out->err.exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR &&
     464           0 :             fd_executor_lookup_native_precompile_program( &txn_out->accounts.keys[ program_id_idx ] )==NULL ) {
     465           0 :           txn_result->custom_error = txn_out->err.custom_err;
     466           0 :         }
     467           0 :       }
     468           0 :     }
     469             : 
     470           0 :     txn_result->has_fee_details                = true;
     471           0 :     txn_result->fee_details.transaction_fee    = txn_out->details.execution_fee;
     472           0 :     txn_result->fee_details.prioritization_fee = txn_out->details.priority_fee;
     473           0 :     txn_result->executed_units                 = txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter;
     474             : 
     475             : 
     476             :     /* Rent is no longer collected */
     477           0 :     txn_result->rent                           = 0UL;
     478             : 
     479           0 :     if( txn_out->details.return_data.len > 0 ) {
     480           0 :       txn_result->return_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
     481           0 :                                       PB_BYTES_ARRAY_T_ALLOCSIZE( txn_out->details.return_data.len ) );
     482           0 :       if( FD_UNLIKELY( _l > output_end ) ) {
     483           0 :         abort();
     484           0 :       }
     485             : 
     486           0 :       txn_result->return_data->size = (pb_size_t)txn_out->details.return_data.len;
     487           0 :       fd_memcpy( txn_result->return_data->bytes, txn_out->details.return_data.data, txn_out->details.return_data.len );
     488           0 :     }
     489             : 
     490             :     /* Allocate space for captured accounts */
     491           0 :     txn_result->has_resulting_state         = true;
     492           0 :     txn_result->resulting_state.acct_states =
     493           0 :       FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
     494           0 :                                   sizeof (fd_exec_test_acct_state_t) * txn_out->accounts.cnt );
     495           0 :     if( FD_UNLIKELY( _l > output_end ) ) {
     496           0 :       abort();
     497           0 :     }
     498             : 
     499             :     /* There are two possible outcomes when a transaction passes
     500             :        sanitization checks in the runtime:
     501             :        1. The transaction fails to load and is not executed (i.e.
     502             :           load_transaction_accounts fails). We only capture the fee
     503             :           payer and nonce accounts.
     504             :        2. The transaction successfully loads, and is executed. This case
     505             :           includes both transactions that succeed and transactions that
     506             :           fail with an instruction error. If the transaction succeeds,
     507             :           the resulting account states for writable accounts are all
     508             :           captured. If the transaction failed with an instruction error,
     509             :           all changes made to the writable accounts up until the
     510             :           instruction error are captured.
     511             :        */
     512           0 :     if( txn_out->err.is_fees_only ) {
     513             :       /* If the transaction is a fees-only transaction, only capture the
     514             :          rollback accounts. Note that there is a weird allowed edge
     515             :          case where the nonce account can be the fee payer, so we add
     516             :          special casing to make sure we don't capture the fee payer
     517             :          twice. */
     518           0 :       if( FD_LIKELY( txn_out->accounts.nonce_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
     519           0 :         fd_pubkey_t *       fee_payer_key    = &txn_out->accounts.keys[FD_FEE_PAYER_TXN_IDX];
     520           0 :         fd_account_meta_t * fee_payer_meta   = txn_out->accounts.rollback_fee_payer;
     521           0 :         pb_bytes_array_t *  out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( fee_payer_meta->dlen ) );
     522           0 :         if( FD_UNLIKELY( _l>output_end ) ) {
     523           0 :           abort();
     524           0 :         }
     525             : 
     526           0 :         fd_solfuzz_pb_txn_ctx_write_account( fee_payer_key, fee_payer_meta, &txn_result->resulting_state, out_account_data );
     527           0 :       }
     528             : 
     529           0 :       if( txn_out->accounts.nonce_idx_in_txn!=ULONG_MAX ) {
     530           0 :         fd_pubkey_t *       nonce_key        = &txn_out->accounts.keys[txn_out->accounts.nonce_idx_in_txn];
     531           0 :         fd_account_meta_t * nonce_meta       = txn_out->accounts.rollback_nonce;
     532           0 :         pb_bytes_array_t *  out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( nonce_meta->dlen ) );
     533           0 :         if( FD_UNLIKELY( _l>output_end ) ) {
     534           0 :           abort();
     535           0 :         }
     536             : 
     537           0 :         fd_solfuzz_pb_txn_ctx_write_account( nonce_key, nonce_meta, &txn_result->resulting_state, out_account_data );
     538           0 :       }
     539           0 :     } else {
     540             :       /* Transaction executed, capture fee payer and other writable
     541             :          accounts */
     542           0 :       for( ulong j=0UL; j<txn_out->accounts.cnt; j++ ) {
     543           0 :         fd_pubkey_t *       pubkey   = &txn_out->accounts.keys[j];
     544           0 :         fd_account_meta_t * meta     = txn_out->accounts.account[j].meta;
     545             : 
     546           0 :         if( !( fd_runtime_account_is_writable_idx( txn_in, txn_out, runner->bank, (ushort)j ) || /* Capture writable accounts */
     547           0 :                j==FD_FEE_PAYER_TXN_IDX ) ) {                                                     /* Capture the fee payer account */
     548           0 :           continue;
     549           0 :         }
     550             : 
     551           0 :         pb_bytes_array_t * out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( meta->dlen ) );
     552           0 :         if( FD_UNLIKELY( _l>output_end ) ) {
     553           0 :           abort();
     554           0 :         }
     555             : 
     556           0 :         fd_solfuzz_pb_txn_ctx_write_account( pubkey, meta, &txn_result->resulting_state, out_account_data );
     557           0 :       }
     558           0 :     }
     559             : 
     560           0 :     ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     561           0 :     txn_out->err.is_committable = 0;
     562           0 :     fd_runtime_cancel_txn( runner->runtime, txn_out );
     563           0 :     fd_solfuzz_txn_ctx_destroy( runner );
     564             : 
     565           0 :     *output = txn_result;
     566           0 :     return actual_end - (ulong)output_buf;
     567           0 :   } FD_SPAD_FRAME_END;
     568           0 : }

Generated by: LCOV version 1.14