LCOV - code coverage report
Current view: top level - flamenco/runtime/tests/harness - fd_txn_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 380 0.0 %
Date: 2025-07-01 05:00:49 Functions: 0 5 0.0 %

          Line data    Source code
       1             : #include "fd_txn_harness.h"
       2             : #include "fd_harness_common.h"
       3             : 
       4             : static void
       5             : fd_runtime_fuzz_txn_ctx_destroy( fd_runtime_fuzz_runner_t * runner,
       6           0 :                                  fd_exec_slot_ctx_t *       slot_ctx ) {
       7           0 :   if( !slot_ctx ) return; // This shouldn't be false either
       8           0 :   fd_funk_txn_t *       funk_txn  = slot_ctx->funk_txn;
       9             : 
      10           0 :   fd_funk_txn_cancel( runner->funk, funk_txn, 1 );
      11           0 : }
      12             : 
      13             : /* Creates transaction execution context for a single test case. Returns a
      14             :    a parsed txn descriptor on success and NULL on failure. */
      15             : static fd_txn_p_t *
      16             : fd_runtime_fuzz_txn_ctx_create( fd_runtime_fuzz_runner_t *         runner,
      17             :                                 fd_exec_slot_ctx_t *               slot_ctx,
      18           0 :                                 fd_exec_test_txn_context_t const * test_ctx ) {
      19           0 :   const uchar empty_bytes[64] = { 0 };
      20           0 :   fd_funk_t * funk = runner->funk;
      21             : 
      22             :   /* Generate unique ID for funk txn */
      23             : 
      24           0 :   fd_funk_txn_xid_t xid[1] = {0};
      25           0 :   xid[0] = fd_funk_generate_xid();
      26             : 
      27             :   /* Create temporary funk transaction and spad contexts */
      28             : 
      29           0 :   fd_funk_txn_start_write( funk );
      30           0 :   fd_funk_txn_t * funk_txn = fd_funk_txn_prepare( funk, NULL, xid, 1 );
      31           0 :   fd_funk_txn_end_write( funk );
      32             : 
      33             :   /* Allocate contexts */
      34           0 :   assert( slot_ctx  );
      35             : 
      36             :   /* Set up slot context */
      37             : 
      38           0 :   slot_ctx->funk_txn     = funk_txn;
      39           0 :   slot_ctx->funk         = funk;
      40             : 
      41           0 :   slot_ctx->bank = runner->bank;
      42           0 :   fd_bank_clear_bank( slot_ctx->bank );
      43             : 
      44             :   /* Restore feature flags */
      45             : 
      46           0 :   fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_ctx.features;
      47           0 :   fd_features_t * features_bm = fd_bank_features_modify( slot_ctx->bank );
      48           0 :   if( !fd_runtime_fuzz_restore_features( features_bm, feature_set ) ) {
      49           0 :     return NULL;
      50           0 :   }
      51             : 
      52             :   /* Default slot */
      53           0 :   ulong slot = test_ctx->slot_ctx.slot ? test_ctx->slot_ctx.slot : 10; // Arbitrary default > 0
      54             : 
      55             :   /* Set slot bank variables (defaults obtained from GenesisConfig::default() in Agave) */
      56           0 :   slot_ctx->slot = slot;
      57           0 :   slot_ctx->bank->slot = slot;
      58             : 
      59             :   /* Initialize builtin accounts */
      60           0 :   fd_builtin_programs_init( slot_ctx );
      61             : 
      62             :   /* Load account states into funk (note this is different from the account keys):
      63             :     Account state = accounts to populate Funk
      64             :     Account keys = account keys that the transaction needs */
      65           0 :   for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
      66             :     /* Load the accounts into the account manager
      67             :        Borrowed accounts get reset anyways - we just need to load the account somewhere */
      68           0 :     FD_TXN_ACCOUNT_DECL( acc );
      69           0 :     fd_runtime_fuzz_load_account( acc, funk, funk_txn, &test_ctx->account_shared_data[i], 1 );
      70           0 :   }
      71             : 
      72             :   /* Add accounts to bpf program cache */
      73           0 :   fd_bpf_scan_and_create_bpf_program_cache_entry( slot_ctx, runner->spad );
      74             : 
      75             :   /* Setup Bank manager */
      76             : 
      77           0 :   fd_bank_prev_slot_set( slot_ctx->bank, slot_ctx->slot - 1UL );
      78             : 
      79           0 :   fd_bank_lamports_per_signature_set( slot_ctx->bank, 5000UL );
      80             : 
      81           0 :   fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, 5000UL );
      82             : 
      83           0 :   fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( slot_ctx->bank );
      84           0 :   fee_rate_governor->burn_percent                  = 50;
      85           0 :   fee_rate_governor->min_lamports_per_signature    = 0;
      86           0 :   fee_rate_governor->max_lamports_per_signature    = 0;
      87           0 :   fee_rate_governor->target_lamports_per_signature = 10000;
      88           0 :   fee_rate_governor->target_signatures_per_slot    = 20000;
      89             : 
      90           0 :   fd_bank_ticks_per_slot_set( slot_ctx->bank, 64 );
      91             : 
      92             :   /* Set epoch bank variables if not present (defaults obtained from GenesisConfig::default() in Agave) */
      93           0 :   fd_epoch_schedule_t default_epoch_schedule = {
      94           0 :                                                 .slots_per_epoch             = 432000,
      95           0 :                                                 .leader_schedule_slot_offset = 432000,
      96           0 :                                                 .warmup                      = 1,
      97           0 :                                                 .first_normal_epoch          = 14,
      98           0 :                                                 .first_normal_slot           = 524256
      99           0 :                                                };
     100           0 :   fd_rent_t           default_rent           = {
     101           0 :                                                 .lamports_per_uint8_year     = 3480,
     102           0 :                                                 .exemption_threshold         = 2.0,
     103           0 :                                                 .burn_percent                = 50
     104           0 :                                                };
     105           0 :   fd_bank_epoch_schedule_set( slot_ctx->bank, default_epoch_schedule );
     106             : 
     107           0 :   fd_bank_rent_set( slot_ctx->bank, default_rent );
     108             : 
     109           0 :   fd_bank_slots_per_year_set( slot_ctx->bank, SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(fd_bank_ticks_per_slot_get( slot_ctx->bank )) );
     110             : 
     111             :   // Override default values if provided
     112           0 :   fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( funk, funk_txn, runner->spad );
     113           0 :   if( epoch_schedule ) {
     114           0 :     fd_bank_epoch_schedule_set( slot_ctx->bank, *epoch_schedule );
     115           0 :   }
     116             : 
     117           0 :   fd_rent_t const * rent = fd_sysvar_rent_read( funk, funk_txn, runner->spad );
     118           0 :   if( rent ) {
     119           0 :     fd_bank_rent_set( slot_ctx->bank, *rent );
     120           0 :   }
     121             : 
     122             :   /* Provide default slot hashes of size 1 if not provided */
     123           0 :   fd_slot_hashes_global_t * slot_hashes = fd_sysvar_slot_hashes_read( funk, funk_txn, runner->spad );
     124           0 :   if( !slot_hashes ) {
     125           0 :     FD_SPAD_FRAME_BEGIN( runner->spad ) {
     126             :       /* The offseted gaddr aware types need the memory for the entire
     127             :          struct to be allocated out of a contiguous memory region. */
     128           0 :       fd_slot_hash_t * slot_hashes                          = NULL;
     129           0 :       void * mem                                            = fd_spad_alloc( runner->spad, FD_SYSVAR_SLOT_HASHES_ALIGN, fd_sysvar_slot_hashes_footprint( 1UL ) );
     130           0 :       fd_slot_hashes_global_t * default_slot_hashes_global  = fd_sysvar_slot_hashes_join( fd_sysvar_slot_hashes_new( mem, 1UL ), &slot_hashes );
     131             : 
     132           0 :       fd_slot_hash_t * dummy_elem = deq_fd_slot_hash_t_push_tail_nocopy( slot_hashes );
     133           0 :       memset( dummy_elem, 0, sizeof(fd_slot_hash_t) );
     134             : 
     135           0 :       fd_sysvar_slot_hashes_write( slot_ctx, default_slot_hashes_global );
     136             : 
     137           0 :       fd_sysvar_slot_hashes_delete( fd_sysvar_slot_hashes_leave( default_slot_hashes_global, slot_hashes ) );
     138           0 :     } FD_SPAD_FRAME_END;
     139           0 :   }
     140             : 
     141             :   /* Provide default stake history if not provided */
     142           0 :   fd_stake_history_t * stake_history = fd_sysvar_stake_history_read( funk, funk_txn, runner->spad );
     143           0 :   if( !stake_history ) {
     144             :     // Provide a 0-set default entry
     145           0 :     fd_epoch_stake_history_entry_pair_t entry = {0};
     146           0 :     fd_sysvar_stake_history_init( slot_ctx );
     147           0 :     fd_sysvar_stake_history_update( slot_ctx, &entry, runner->spad );
     148           0 :   }
     149             : 
     150             :   /* Provide default last restart slot sysvar if not provided */
     151           0 :   FD_TXN_ACCOUNT_DECL( acc );
     152           0 :   int err = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_last_restart_slot_id, funk, funk_txn );
     153           0 :   if( err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) {
     154           0 :     fd_sysvar_last_restart_slot_init( slot_ctx );
     155           0 :   }
     156             : 
     157             :   /* Provide a default clock if not present */
     158           0 :   fd_sol_sysvar_clock_t const * clock = fd_sysvar_clock_read( funk, funk_txn, runner->spad );
     159           0 :   if( !clock ) {
     160           0 :     fd_sysvar_clock_init( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn );
     161           0 :     fd_sysvar_clock_update( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn, runner->spad );
     162           0 :   }
     163             : 
     164             :   /* Epoch schedule and rent get set from the epoch bank */
     165           0 :   fd_sysvar_epoch_schedule_init( slot_ctx );
     166           0 :   fd_sysvar_rent_init( slot_ctx );
     167             : 
     168             :   /* Set the epoch rewards sysvar if partition epoch rewards feature is enabled
     169             : 
     170             :      TODO: The init parameters are not exactly conformant with Agave's epoch rewards sysvar. We should
     171             :      be calling `fd_begin_partitioned_rewards` with the same parameters as Agave. However,
     172             :      we just need the `active` field to be conformant due to a single Stake program check.
     173             :      THIS MAY CHANGE IN THE FUTURE. If there are other parts of transaction execution that use
     174             :      the epoch rewards sysvar, we may need to update this.
     175             :   */
     176           0 :   fd_sysvar_epoch_rewards_t * epoch_rewards = fd_sysvar_epoch_rewards_read( funk, funk_txn, runner->spad );
     177           0 :   if( !epoch_rewards ) {
     178           0 :     fd_point_value_t point_value = {0};
     179           0 :     fd_hash_t const * last_hash = test_ctx->blockhash_queue_count > 0 ? (fd_hash_t const *)test_ctx->blockhash_queue[0]->bytes : (fd_hash_t const *)empty_bytes;
     180           0 :     fd_sysvar_epoch_rewards_init( slot_ctx, 0UL, 2UL, 1UL, point_value, last_hash);
     181           0 :   }
     182             : 
     183             :   /* A NaN rent exemption threshold is U.B. in Solana Labs */
     184           0 :   rent = fd_sysvar_rent_read( funk, funk_txn, runner->spad );
     185           0 :   if( ( rent->exemption_threshold != 0.0 &&
     186           0 :         !fd_dblbits_is_normal( fd_dblbits( rent->exemption_threshold ) ) ) |
     187           0 :       ( rent->exemption_threshold     <      0.0 ) |
     188           0 :       ( rent->exemption_threshold     >    999.0 ) |
     189           0 :       ( rent->lamports_per_uint8_year > UINT_MAX ) |
     190           0 :       ( rent->burn_percent            >      100 ) ) {
     191           0 :     return NULL;
     192           0 :   }
     193             : 
     194             :   /* Blockhash queue is given in txn message. We need to populate the following two fields:
     195             :      - slot_ctx->slot_bank.block_hash_queue
     196             :      - slot_ctx->slot_bank.recent_block_hashes */
     197           0 :   ulong num_blockhashes = test_ctx->blockhash_queue_count;
     198             : 
     199             :   /* Blockhash queue init */
     200           0 :   fd_block_hash_queue_global_t * block_hash_queue = fd_bank_block_hash_queue_modify( slot_ctx->bank );
     201           0 :   uchar * last_hash_mem = (uchar *)fd_ulong_align_up( (ulong)block_hash_queue + sizeof(fd_block_hash_queue_global_t), alignof(fd_hash_t) );
     202           0 :   uchar * ages_pool_mem = (uchar *)fd_ulong_align_up( (ulong)last_hash_mem + sizeof(fd_hash_t), fd_hash_hash_age_pair_t_map_align() );
     203           0 :   fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_hash_hash_age_pair_t_map_join( fd_hash_hash_age_pair_t_map_new( ages_pool_mem, 400 ) );
     204             : 
     205           0 :   block_hash_queue->max_age          = FD_BLOCKHASH_QUEUE_MAX_ENTRIES;
     206           0 :   block_hash_queue->ages_root_offset = 0UL;
     207           0 :   fd_block_hash_queue_ages_pool_update( block_hash_queue, ages_pool );
     208           0 :   block_hash_queue->last_hash_index  = 0UL;
     209           0 :   block_hash_queue->last_hash_offset = (ulong)last_hash_mem - (ulong)block_hash_queue;
     210             : 
     211             :   // Save lamports per signature for most recent blockhash, if sysvar cache contains recent block hashes
     212           0 :   fd_recent_block_hashes_global_t const * rbh_global = fd_sysvar_recent_hashes_read( funk, funk_txn, runner->spad );
     213           0 :   fd_recent_block_hashes_t rbh[1];
     214           0 :   if( rbh_global ) {
     215           0 :     rbh->hashes = deq_fd_block_block_hash_entry_t_join( (uchar*)rbh_global + rbh_global->hashes_offset );
     216           0 :   }
     217             : 
     218           0 :   if( rbh_global && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
     219           0 :     fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
     220           0 :     if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
     221           0 :       fd_bank_lamports_per_signature_set( slot_ctx->bank, last->fee_calculator.lamports_per_signature );
     222           0 :       fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, last->fee_calculator.lamports_per_signature );
     223           0 :     }
     224           0 :   }
     225             : 
     226             :   // Blockhash_queue[end] = last (latest) hash
     227             :   // Blockhash_queue[0] = genesis hash
     228           0 :   if( num_blockhashes > 0 ) {
     229           0 :     fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( slot_ctx->bank );
     230           0 :     memcpy( genesis_hash->hash, test_ctx->blockhash_queue[0]->bytes, sizeof(fd_hash_t) );
     231             : 
     232           0 :     for( ulong i = 0; i < num_blockhashes; ++i ) {
     233             :       // Recent block hashes cap is 150 (actually 151), while blockhash queue capacity is 300 (actually 301)
     234           0 :       fd_block_block_hash_entry_t blockhash_entry;
     235           0 :       memcpy( &blockhash_entry.blockhash, test_ctx->blockhash_queue[i]->bytes, sizeof(fd_hash_t) );
     236           0 :       fd_bank_poh_set( slot_ctx->bank, blockhash_entry.blockhash );
     237           0 :       fd_sysvar_recent_hashes_update( slot_ctx, runner->spad );
     238           0 :     }
     239           0 :   } else {
     240             :     // Add a default empty blockhash and use it as genesis
     241           0 :     num_blockhashes = 1;
     242           0 :     fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( slot_ctx->bank );
     243           0 :     memcpy( genesis_hash->hash, empty_bytes, sizeof(fd_hash_t) );
     244           0 :     fd_block_block_hash_entry_t blockhash_entry;
     245           0 :     memcpy( &blockhash_entry.blockhash, empty_bytes, sizeof(fd_hash_t) );
     246           0 :     fd_bank_poh_set( slot_ctx->bank, blockhash_entry.blockhash );
     247           0 :     fd_sysvar_recent_hashes_update( slot_ctx, runner->spad );
     248           0 :   }
     249             : 
     250             :   /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
     251           0 :   uchar * txn_raw_begin = fd_spad_alloc( runner->spad, alignof(uchar), 1232 );
     252           0 :   ushort instr_count, addr_table_cnt;
     253           0 :   ulong msg_sz = fd_runtime_fuzz_serialize_txn( txn_raw_begin, &test_ctx->tx, &instr_count, &addr_table_cnt );
     254           0 :   if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
     255           0 :     return NULL;
     256           0 :   }
     257             : 
     258             :   /* Set up txn descriptor from raw data */
     259           0 :   fd_txn_t * txn_descriptor = (fd_txn_t *) fd_spad_alloc( runner->spad, fd_txn_align(), fd_txn_footprint( instr_count, addr_table_cnt ) );
     260           0 :   if( FD_UNLIKELY( !fd_txn_parse( txn_raw_begin, msg_sz, txn_descriptor, NULL ) ) ) {
     261           0 :     return NULL;
     262           0 :   }
     263             : 
     264             :   /* Run txn preparation phases and execution
     265             :      NOTE: This should be modified accordingly if transaction setup logic changes */
     266           0 :   fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
     267           0 :   memcpy( txn->payload, txn_raw_begin, msg_sz );
     268           0 :   txn->payload_sz = msg_sz;
     269           0 :   txn->flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
     270           0 :   memcpy( txn->_, txn_descriptor, fd_txn_footprint( instr_count, addr_table_cnt ) );
     271             : 
     272           0 :   return txn;
     273           0 : }
     274             : 
     275             : /* Takes in a parsed txn descriptor to be executed against the runtime.
     276             :    Returns the task info. */
     277             : static fd_execute_txn_task_info_t *
     278             : fd_runtime_fuzz_txn_ctx_exec( fd_runtime_fuzz_runner_t * runner,
     279             :                               fd_exec_slot_ctx_t *       slot_ctx,
     280           0 :                               fd_txn_p_t *               txn ) {
     281           0 :   fd_execute_txn_task_info_t * task_info = fd_spad_alloc( runner->spad, alignof(fd_execute_txn_task_info_t), sizeof(fd_execute_txn_task_info_t) );
     282           0 :   memset( task_info, 0, sizeof(fd_execute_txn_task_info_t) );
     283           0 :   task_info->txn     = txn;
     284           0 :   task_info->txn_ctx = fd_spad_alloc( runner->spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
     285             : 
     286           0 :   fd_tpool_t tpool[1];
     287           0 :   tpool->worker_cnt = 1;
     288           0 :   tpool->worker_max = 1;
     289             : 
     290           0 :   fd_runtime_prepare_txns_start( slot_ctx, task_info, txn, 1UL, runner->spad );
     291             : 
     292             :   /* Setup the spad for account allocation */
     293           0 :   task_info->txn_ctx->spad      = runner->spad;
     294           0 :   task_info->txn_ctx->spad_wksp = fd_wksp_containing( runner->spad );
     295             : 
     296           0 :   fd_runtime_pre_execute_check( task_info, 0 );
     297             : 
     298           0 :   if( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) {
     299           0 :       task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
     300           0 :       task_info->exec_res    = fd_execute_txn( task_info );
     301           0 :   }
     302             : 
     303           0 :   fd_bank_execution_fees_set( slot_ctx->bank, fd_bank_execution_fees_get( slot_ctx->bank ) + task_info->txn_ctx->execution_fee );
     304             : 
     305           0 :   fd_bank_priority_fees_set( slot_ctx->bank, fd_bank_priority_fees_get( slot_ctx->bank ) + task_info->txn_ctx->priority_fee );
     306             : 
     307           0 :   return task_info;
     308           0 : }
     309             : 
     310             : ulong
     311             : fd_runtime_fuzz_serialize_txn( uchar *                                      txn_raw_begin,
     312             :                                fd_exec_test_sanitized_transaction_t const * tx,
     313             :                                ushort *                                     out_instr_cnt,
     314           0 :                                ushort *                                     out_addr_table_cnt ) {
     315           0 :   const uchar empty_bytes[64] = { 0 };
     316           0 :   uchar * txn_raw_cur_ptr = txn_raw_begin;
     317             : 
     318             :   /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
     319             :      Note that although documentation interchangably refers to the signature cnt as a compact-u16
     320             :      and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
     321             :      is represented the same way anyways and can be parsed identically. */
     322             :   // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
     323           0 :   uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
     324           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
     325           0 :   for( uchar i = 0; i < signature_cnt; ++i ) {
     326           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->signatures && tx->signatures[i] ? tx->signatures[i]->bytes : empty_bytes, FD_TXN_SIGNATURE_SZ );
     327           0 :   }
     328             : 
     329             :   /* Message */
     330             :   /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
     331             :      https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
     332             : 
     333             :      We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
     334             :      collisions with the header_b0 tag. */
     335           0 :   uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
     336           0 :   if( !tx->message.is_legacy ) {
     337           0 :     uchar header_b0 = (uchar) 0x80UL;
     338           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
     339           0 :   }
     340             : 
     341             :   /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
     342           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
     343           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
     344           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
     345             : 
     346             :   /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
     347             :   // Array length is a compact u16
     348           0 :   ushort num_acct_keys = (ushort) tx->message.account_keys_count;
     349           0 :   FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
     350           0 :   for( ushort i = 0; i < num_acct_keys; ++i ) {
     351           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
     352           0 :   }
     353             : 
     354             :   /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
     355             :   // Note: add an empty blockhash if none is provided
     356           0 :   FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.recent_blockhash ? tx->message.recent_blockhash->bytes : empty_bytes, sizeof(fd_hash_t) );
     357             : 
     358             :   /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
     359             :   // Instruction count is a compact u16
     360           0 :   ushort instr_count = (ushort) tx->message.instructions_count;
     361           0 :   FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
     362           0 :   for( ushort i = 0; i < instr_count; ++i ) {
     363             :     // Program ID index
     364           0 :     uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
     365           0 :     FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
     366             : 
     367             :     // Compact array of account addresses
     368           0 :     ushort acct_count = (ushort) tx->message.instructions[i].accounts_count;
     369           0 :     FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
     370           0 :     for( ushort j = 0; j < acct_count; ++j ) {
     371           0 :       uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
     372           0 :       FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
     373           0 :     }
     374             : 
     375             :     // Compact array of 8-bit data
     376           0 :     pb_bytes_array_t * data = tx->message.instructions[i].data;
     377           0 :     ushort data_len;
     378           0 :     if( data ) {
     379           0 :       data_len = (ushort) data->size;
     380           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
     381           0 :       FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
     382           0 :     } else {
     383           0 :       data_len = 0;
     384           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
     385           0 :     }
     386           0 :   }
     387             : 
     388             :   /* Address table lookups (N/A for legacy transactions) */
     389           0 :   ushort addr_table_cnt = 0;
     390           0 :   if( !tx->message.is_legacy ) {
     391             :     /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
     392             :     // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
     393           0 :     addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
     394           0 :     FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
     395           0 :     for( ushort i = 0; i < addr_table_cnt; ++i ) {
     396             :       // Account key
     397           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) );
     398             : 
     399             :       // Compact array of writable indexes
     400           0 :       ushort writable_count = (ushort) tx->message.address_table_lookups[i].writable_indexes_count;
     401           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
     402           0 :       for( ushort j = 0; j < writable_count; ++j ) {
     403           0 :         uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
     404           0 :         FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
     405           0 :       }
     406             : 
     407             :       // Compact array of readonly indexes
     408           0 :       ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
     409           0 :       FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
     410           0 :       for( ushort j = 0; j < readonly_count; ++j ) {
     411           0 :         uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
     412           0 :         FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
     413           0 :       }
     414           0 :     }
     415           0 :   }
     416             : 
     417           0 :   *out_instr_cnt = instr_count;
     418           0 :   *out_addr_table_cnt = addr_table_cnt;
     419           0 :   return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
     420           0 : }
     421             : 
     422             : ulong
     423             : fd_runtime_fuzz_txn_run( fd_runtime_fuzz_runner_t * runner,
     424             :                          void const *               input_,
     425             :                          void **                    output_,
     426             :                          void *                     output_buf,
     427           0 :                          ulong                      output_bufsz ) {
     428           0 :   fd_exec_test_txn_context_t const * input  = fd_type_pun_const( input_ );
     429           0 :   fd_exec_test_txn_result_t **       output = fd_type_pun( output_ );
     430             : 
     431           0 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     432             : 
     433             :     /* Initialize memory */
     434           0 :     uchar *               slot_ctx_mem = fd_spad_alloc( runner->spad, FD_EXEC_SLOT_CTX_ALIGN,  FD_EXEC_SLOT_CTX_FOOTPRINT  );
     435           0 :     fd_exec_slot_ctx_t *  slot_ctx     = fd_exec_slot_ctx_join( fd_exec_slot_ctx_new( slot_ctx_mem ) );
     436             : 
     437             :     /* Setup the transaction context */
     438           0 :     fd_txn_p_t * txn = fd_runtime_fuzz_txn_ctx_create( runner, slot_ctx, input );
     439           0 :     if( txn==NULL ) {
     440           0 :       fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
     441           0 :       return 0;
     442           0 :     }
     443             : 
     444             :     /* Execute the transaction against the runtime */
     445           0 :     fd_execute_txn_task_info_t * task_info = fd_runtime_fuzz_txn_ctx_exec( runner, slot_ctx, txn );
     446           0 :     fd_exec_txn_ctx_t *          txn_ctx   = task_info->txn_ctx;
     447             : 
     448           0 :     int exec_res = task_info->exec_res;
     449             : 
     450             :     /* Start saving txn exec results */
     451           0 :     FD_SCRATCH_ALLOC_INIT( l, output_buf );
     452           0 :     ulong output_end = (ulong)output_buf + output_bufsz;
     453             : 
     454           0 :     fd_exec_test_txn_result_t * txn_result =
     455           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
     456           0 :                                   sizeof (fd_exec_test_txn_result_t) );
     457           0 :     if( FD_UNLIKELY( _l > output_end ) ) {
     458           0 :       abort();
     459           0 :     }
     460           0 :     fd_memset( txn_result, 0, sizeof(fd_exec_test_txn_result_t) );
     461             : 
     462             :     /* Capture basic results fields */
     463           0 :     txn_result->executed                          = task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
     464           0 :     txn_result->sanitization_error                = !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS );
     465           0 :     txn_result->has_resulting_state               = false;
     466           0 :     txn_result->resulting_state.acct_states_count = 0;
     467           0 :     txn_result->is_ok                             = !exec_res;
     468           0 :     txn_result->status                            = (uint32_t) -exec_res;
     469           0 :     txn_result->instruction_error                 = 0;
     470           0 :     txn_result->instruction_error_index           = 0;
     471           0 :     txn_result->custom_error                      = 0;
     472           0 :     txn_result->executed_units                    = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
     473           0 :     txn_result->has_fee_details                   = false;
     474           0 :     txn_result->loaded_accounts_data_size         = task_info->txn_ctx->loaded_accounts_data_size;
     475             : 
     476           0 :     if( txn_result->sanitization_error ) {
     477             :       /* Collect fees for transactions that failed to load */
     478           0 :       if( task_info->txn->flags & FD_TXN_P_FLAGS_FEES_ONLY ) {
     479           0 :         txn_result->has_fee_details                = true;
     480           0 :         txn_result->fee_details.prioritization_fee = task_info->txn_ctx->priority_fee;
     481           0 :         txn_result->fee_details.transaction_fee    = task_info->txn_ctx->execution_fee;
     482           0 :       }
     483             : 
     484           0 :       if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
     485             :       /* If exec_res was an instruction error and we have a sanitization error, it was a precompile error */
     486           0 :         txn_result->instruction_error       = (uint32_t) -txn_ctx->exec_err;
     487           0 :         txn_result->instruction_error_index = (uint32_t) txn_ctx->instr_err_idx;
     488             : 
     489             :         /*
     490             :         TODO: precompile error codes are not conformant, so we're ignoring custom error codes for them for now. This should be revisited in the future.
     491             :         For now, only precompiles throw custom error codes, so we can ignore all custom error codes thrown in the sanitization phase. If this changes,
     492             :         this logic will have to be revisited.
     493             : 
     494             :         if( task_info->txn_ctx->exec_err == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
     495             :           txn_result->custom_error = txn_ctx->custom_err;
     496             :         }
     497             :         */
     498           0 :       }
     499             : 
     500           0 :       ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     501           0 :       fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
     502             : 
     503           0 :       *output = txn_result;
     504           0 :       return actual_end - (ulong)output_buf;
     505             : 
     506           0 :     } else {
     507             :       /* Capture the instruction error code */
     508           0 :       if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
     509           0 :         int instr_err_idx                   = txn_ctx->instr_err_idx;
     510           0 :         int program_id_idx                  = txn_ctx->instr_infos[instr_err_idx].program_id;
     511             : 
     512           0 :         txn_result->instruction_error       = (uint32_t) -txn_ctx->exec_err;
     513           0 :         txn_result->instruction_error_index = (uint32_t) instr_err_idx;
     514             : 
     515             :         /* If the exec err was a custom instr error and came from a precompile instruction, don't capture the custom error code. */
     516           0 :         if( txn_ctx->exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR &&
     517           0 :             fd_executor_lookup_native_precompile_program( &txn_ctx->accounts[ program_id_idx ] )==NULL ) {
     518           0 :           txn_result->custom_error = txn_ctx->custom_err;
     519           0 :         }
     520           0 :       }
     521           0 :     }
     522             : 
     523           0 :     txn_result->has_fee_details                = true;
     524           0 :     txn_result->fee_details.transaction_fee    = task_info->txn_ctx->execution_fee;
     525           0 :     txn_result->fee_details.prioritization_fee = task_info->txn_ctx->priority_fee;
     526             : 
     527             :     /* Rent is only collected on successfully loaded transactions */
     528           0 :     txn_result->rent                           = txn_ctx->collected_rent;
     529             : 
     530           0 :     if( txn_ctx->return_data.len > 0 ) {
     531           0 :       txn_result->return_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
     532           0 :                                       PB_BYTES_ARRAY_T_ALLOCSIZE( txn_ctx->return_data.len ) );
     533           0 :       if( FD_UNLIKELY( _l > output_end ) ) {
     534           0 :         abort();
     535           0 :       }
     536             : 
     537           0 :       txn_result->return_data->size = (pb_size_t)txn_ctx->return_data.len;
     538           0 :       fd_memcpy( txn_result->return_data->bytes, txn_ctx->return_data.data, txn_ctx->return_data.len );
     539           0 :     }
     540             : 
     541             :     /* Allocate space for captured accounts */
     542           0 :     ulong modified_acct_cnt = txn_ctx->accounts_cnt;
     543             : 
     544           0 :     txn_result->has_resulting_state         = true;
     545           0 :     txn_result->resulting_state.acct_states =
     546           0 :       FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
     547           0 :                                   sizeof (fd_exec_test_acct_state_t) * modified_acct_cnt );
     548           0 :     if( FD_UNLIKELY( _l > output_end ) ) {
     549           0 :       abort();
     550           0 :     }
     551             : 
     552             :     /* If the transaction is a fees-only transaction, we have to create rollback accounts to iterate over and save. */
     553           0 :     fd_txn_account_t * accounts_to_save = txn_ctx->accounts;
     554           0 :     ulong              accounts_cnt     = txn_ctx->accounts_cnt;
     555           0 :     if( task_info->txn->flags & FD_TXN_P_FLAGS_FEES_ONLY ) {
     556           0 :       accounts_to_save = fd_spad_alloc( runner->spad, alignof(fd_txn_account_t), sizeof(fd_txn_account_t) * 2 );
     557           0 :       accounts_cnt     = 0UL;
     558             : 
     559           0 :       if( FD_LIKELY( txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
     560           0 :         accounts_to_save[accounts_cnt++] = *txn_ctx->rollback_fee_payer_account;
     561           0 :       }
     562             : 
     563           0 :       if( txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
     564           0 :         accounts_to_save[accounts_cnt++] = *txn_ctx->rollback_nonce_account;
     565           0 :       }
     566           0 :     }
     567             : 
     568             :     /* Capture borrowed accounts */
     569           0 :     for( ulong j=0UL; j<accounts_cnt; j++ ) {
     570           0 :       fd_txn_account_t * acc = &accounts_to_save[j];
     571             : 
     572           0 :       if( !( fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, (ushort)j ) || j==FD_FEE_PAYER_TXN_IDX ) ) continue;
     573           0 :       assert( acc->vt->is_mutable( acc ) );
     574             : 
     575           0 :       ulong modified_idx = txn_result->resulting_state.acct_states_count;
     576           0 :       assert( modified_idx < modified_acct_cnt );
     577             : 
     578           0 :       fd_exec_test_acct_state_t * out_acct = &txn_result->resulting_state.acct_states[ modified_idx ];
     579           0 :       memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
     580             :       /* Copy over account content */
     581             : 
     582           0 :       memcpy( out_acct->address, acc->pubkey, sizeof(fd_pubkey_t) );
     583             : 
     584           0 :       out_acct->lamports = acc->vt->get_lamports( acc );
     585             : 
     586           0 :       if( acc->vt->get_data_len( acc ) > 0 ) {
     587           0 :         out_acct->data =
     588           0 :           FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
     589           0 :                                       PB_BYTES_ARRAY_T_ALLOCSIZE( acc->vt->get_data_len( acc ) ) );
     590           0 :         if( FD_UNLIKELY( _l > output_end ) ) {
     591           0 :           abort();
     592           0 :         }
     593           0 :         out_acct->data->size = (pb_size_t)acc->vt->get_data_len( acc );
     594           0 :         fd_memcpy( out_acct->data->bytes, acc->vt->get_data( acc ), acc->vt->get_data_len( acc ) );
     595           0 :       }
     596             : 
     597           0 :       out_acct->executable = acc->vt->is_executable( acc );
     598           0 :       out_acct->rent_epoch = acc->vt->get_rent_epoch( acc );
     599           0 :       memcpy( out_acct->owner, acc->vt->get_owner( acc ), sizeof(fd_pubkey_t) );
     600             : 
     601           0 :       txn_result->resulting_state.acct_states_count++;
     602           0 :     }
     603             : 
     604           0 :     ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     605           0 :     fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
     606             : 
     607           0 :     *output = txn_result;
     608           0 :     return actual_end - (ulong)output_buf;
     609           0 :   } FD_SPAD_FRAME_END;
     610           0 : }

Generated by: LCOV version 1.14