LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_executor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 750 1288 58.2 %
Date: 2025-01-08 12:08:44 Functions: 30 40 75.0 %

          Line data    Source code
       1             : #include "fd_executor.h"
       2             : #include "fd_acc_mgr.h"
       3             : #include "fd_hashes.h"
       4             : #include "fd_runtime_err.h"
       5             : #include "context/fd_exec_slot_ctx.h"
       6             : #include "context/fd_exec_txn_ctx.h"
       7             : #include "context/fd_exec_instr_ctx.h"
       8             : 
       9             : #include "../nanopb/pb_encode.h"
      10             : #include "../../util/rng/fd_rng.h"
      11             : #include "fd_system_ids.h"
      12             : #include "fd_account.h"
      13             : #include "program/fd_address_lookup_table_program.h"
      14             : #include "program/fd_bpf_loader_program.h"
      15             : #include "program/fd_loader_v4_program.h"
      16             : #include "program/fd_compute_budget_program.h"
      17             : #include "program/fd_config_program.h"
      18             : #include "program/fd_precompiles.h"
      19             : #include "program/fd_stake_program.h"
      20             : #include "program/fd_system_program.h"
      21             : #include "program/fd_vote_program.h"
      22             : #include "program/fd_zk_elgamal_proof_program.h"
      23             : #include "program/fd_bpf_program_util.h"
      24             : #include "sysvar/fd_sysvar_cache.h"
      25             : #include "sysvar/fd_sysvar_slot_history.h"
      26             : #include "sysvar/fd_sysvar_epoch_schedule.h"
      27             : #include "sysvar/fd_sysvar_instructions.h"
      28             : 
      29             : #include "../../ballet/base58/fd_base58.h"
      30             : #include "../../ballet/pack/fd_pack.h"
      31             : #include "../../ballet/pack/fd_pack_cost.h"
      32             : #include "../../ballet/sbpf/fd_sbpf_loader.h"
      33             : #include "../../ballet/pack/fd_pack.h"
      34             : 
      35             : #include "../../util/bits/fd_uwide.h"
      36             : 
      37             : #define SORT_NAME        sort_uint64_t
      38           0 : #define SORT_KEY_T       uint64_t
      39           0 : #define SORT_BEFORE(a,b) (a)<(b)
      40             : #include "../../util/tmpl/fd_sort.c"
      41             : 
      42             : #include <assert.h>
      43             : #include <errno.h>
      44             : #include <stdio.h>   /* snprintf(3) */
      45             : #include <fcntl.h>   /* openat(2) */
      46             : #include <unistd.h>  /* write(3) */
      47             : #include <time.h>
      48             : 
      49             : #define MAX_COMPUTE_UNITS_PER_BLOCK                (48000000UL)
      50             : #define MAX_COMPUTE_UNITS_PER_WRITE_LOCKED_ACCOUNT (12000000UL)
      51             : /* We should strive for these max limits matching between pack and the
      52             :    runtime. If there's a reason for these limits to mismatch, and there
      53             :    could be, then someone should explicitly document that when relaxing
      54             :    these assertions. */
      55             : FD_STATIC_ASSERT( FD_PACK_MAX_COST_PER_BLOCK==MAX_COMPUTE_UNITS_PER_BLOCK,                     executor_pack_max_mismatch );
      56             : FD_STATIC_ASSERT( FD_PACK_MAX_WRITE_COST_PER_ACCT==MAX_COMPUTE_UNITS_PER_WRITE_LOCKED_ACCOUNT, executor_pack_max_mismatch );
      57             : 
      58             : /* TODO: precompiles currently enter this noop function. Once the move_precompile_verification_to_svm
      59             :    feature gets activated, this will need to be replaced with precompile verification functions. */
      60             : static int
      61         915 : fd_noop_instr_execute( fd_exec_instr_ctx_t * ctx FD_PARAM_UNUSED ) {
      62         915 :   return FD_EXECUTOR_INSTR_SUCCESS;
      63         915 : }
      64             : 
      65             : struct fd_native_prog_info {
      66             :   fd_pubkey_t key;
      67             :   fd_exec_instr_fn_t fn;
      68             : };
      69             : typedef struct fd_native_prog_info fd_native_prog_info_t;
      70             : 
      71             : #define MAP_PERFECT_NAME fd_native_program_fn_lookup_tbl
      72             : #define MAP_PERFECT_LG_TBL_SZ 4
      73             : #define MAP_PERFECT_T fd_native_prog_info_t
      74       68793 : #define MAP_PERFECT_HASH_C 478U
      75             : #define MAP_PERFECT_KEY key.uc
      76             : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
      77             : #define MAP_PERFECT_ZERO_KEY  (0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
      78             : #define MAP_PERFECT_COMPLEX_KEY 1
      79       68793 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
      80             : 
      81       68793 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>28)&0xFU)
      82             : 
      83             : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
      84             :                              a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
      85             :                                           PERFECT_HASH( (a08 | (a09<<8) | (a10<<16) | (a11<<24)) )
      86       68793 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr + 8UL ) )
      87             : 
      88             : #define MAP_PERFECT_0       ( VOTE_PROG_ID            ), .fn = fd_vote_program_execute
      89             : #define MAP_PERFECT_1       ( SYS_PROG_ID             ), .fn = fd_system_program_execute
      90             : #define MAP_PERFECT_2       ( CONFIG_PROG_ID          ), .fn = fd_config_program_execute
      91             : #define MAP_PERFECT_3       ( STAKE_PROG_ID           ), .fn = fd_stake_program_execute
      92             : #define MAP_PERFECT_4       ( COMPUTE_BUDGET_PROG_ID  ), .fn = fd_compute_budget_program_execute
      93             : #define MAP_PERFECT_5       ( ADDR_LUT_PROG_ID        ), .fn = fd_address_lookup_table_program_execute
      94             : #define MAP_PERFECT_6       ( ZK_EL_GAMAL_PROG_ID     ), .fn = fd_executor_zk_elgamal_proof_program_execute
      95             : #define MAP_PERFECT_7       ( BPF_LOADER_1_PROG_ID    ), .fn = fd_bpf_loader_program_execute
      96             : #define MAP_PERFECT_8       ( BPF_LOADER_2_PROG_ID    ), .fn = fd_bpf_loader_program_execute
      97             : #define MAP_PERFECT_9       ( BPF_UPGRADEABLE_PROG_ID ), .fn = fd_bpf_loader_program_execute
      98             : #define MAP_PERFECT_10      ( LOADER_V4_PROG_ID       ), .fn = fd_loader_v4_program_execute
      99             : #define MAP_PERFECT_11      ( ED25519_SV_PROG_ID      ), .fn = fd_noop_instr_execute
     100             : #define MAP_PERFECT_12      ( KECCAK_SECP_PROG_ID     ), .fn = fd_noop_instr_execute
     101             : #define MAP_PERFECT_13      ( SECP256R1_PROG_ID       ), .fn = fd_noop_instr_execute
     102             : 
     103             : #include "../../util/tmpl/fd_map_perfect.c"
     104             : #undef PERFECT_HASH
     105             : 
     106             : /* https://github.com/anza-xyz/agave/blob/9efdd74b1b65ecfd85b0db8ad341c6bd4faddfef/program-runtime/src/invoke_context.rs#L461-L488 */
     107             : fd_exec_instr_fn_t
     108       68793 : fd_executor_lookup_native_program( fd_borrowed_account_t const * prog_acc ) {
     109       68793 :   fd_pubkey_t const * pubkey        = prog_acc->pubkey;
     110       68793 :   fd_pubkey_t const * owner         = (fd_pubkey_t const *)prog_acc->const_meta->info.owner;
     111             : 
     112             :   /* Native programs should be owned by the native loader...
     113             :      This will not be the case though once core programs are migrated to BPF. */
     114       68793 :   int is_native_program = !memcmp( owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) );
     115       68793 :   if( FD_UNLIKELY( !memcmp( pubkey, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t) ) &&
     116       68793 :                    !memcmp( owner,  fd_solana_system_program_id.key,             sizeof(fd_pubkey_t) ) ) ) {
     117             :     /* ... except for the special case for testnet ed25519, which is
     118             :        bizarrely owned by the system program. */
     119           0 :     is_native_program = 1;
     120           0 :   }
     121       68793 :   fd_pubkey_t const * lookup_pubkey = is_native_program ? pubkey : owner;
     122       68793 :   const fd_native_prog_info_t null_function = (const fd_native_prog_info_t) {0};
     123       68793 :   return fd_native_program_fn_lookup_tbl_query( lookup_pubkey, &null_function )->fn;
     124       68793 : }
     125             : 
     126             : /* Returns 1 if the sysvar instruction is used, 0 otherwise */
     127             : uint
     128        5190 : fd_executor_txn_uses_sysvar_instructions( fd_exec_txn_ctx_t const * txn_ctx ) {
     129       34032 :   for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
     130       28845 :     if( FD_UNLIKELY( memcmp( txn_ctx->accounts[i].key, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
     131           3 :       return 1;
     132           3 :     }
     133       28845 :   }
     134             : 
     135        5187 :   return 0;
     136        5190 : }
     137             : 
     138             : int
     139        6792 : fd_executor_is_system_nonce_account( fd_borrowed_account_t * account ) {
     140        6792 : FD_SCRATCH_SCOPE_BEGIN {
     141        6792 :   if ( memcmp( account->const_meta->info.owner, fd_solana_system_program_id.uc, sizeof(fd_pubkey_t) ) == 0 ) {
     142        6786 :     if ( account->const_meta->dlen == 0 ) {
     143        6711 :       return 0;
     144        6711 :     } else if ( account->const_meta->dlen == 80 ) { // TODO: nonce size macro
     145          72 :       fd_bincode_decode_ctx_t decode = { .data = account->const_data,
     146          72 :                                          .dataend = account->const_data + account->const_meta->dlen,
     147          72 :                                          .valloc = fd_scratch_virtual() };
     148          72 :       fd_nonce_state_versions_t nonce_versions;
     149          72 :       if (fd_nonce_state_versions_decode( &nonce_versions, &decode ) != 0 ) {
     150           6 :         return -1;
     151           6 :       }
     152          66 :       fd_nonce_state_t * state;;
     153          66 :       if ( fd_nonce_state_versions_is_current( &nonce_versions ) ) {
     154          60 :         state = &nonce_versions.inner.current;
     155          60 :       } else {
     156           6 :         state = &nonce_versions.inner.legacy;
     157           6 :       }
     158             : 
     159          66 :       if ( fd_nonce_state_is_initialized( state ) ) {
     160          60 :         return 1;
     161          60 :       }
     162          66 :     }
     163        6786 :   }
     164             : 
     165          15 :   return -1;
     166        6792 : } FD_SCRATCH_SCOPE_END;
     167        6792 : }
     168             : 
     169             : static int
     170        6726 : check_rent_transition( fd_borrowed_account_t * account, fd_rent_t const * rent, ulong fee ) {
     171        6726 :   ulong min_balance   = fd_rent_exempt_minimum_balance( rent, account->const_meta->dlen );
     172        6726 :   ulong pre_lamports  = account->const_meta->info.lamports;
     173        6726 :   uchar pre_is_exempt = pre_lamports >= min_balance;
     174             : 
     175        6726 :   ulong post_lamports  = pre_lamports - fee;
     176        6726 :   uchar post_is_exempt = post_lamports >= min_balance;
     177             : 
     178        6726 :   if ( post_lamports == 0 || post_is_exempt ) {
     179        6648 :     return 1;
     180        6648 :   }
     181             : 
     182          78 :   if ( pre_lamports == 0 || pre_is_exempt ) {
     183           0 :     return 0;
     184           0 :   }
     185             : 
     186          78 :   return post_lamports <= pre_lamports;
     187          78 : }
     188             : 
     189             : /* https://github.com/anza-xyz/agave/blob/v2.0.2/svm/src/account_loader.rs#L103 */
     190             : int
     191        6831 : fd_validate_fee_payer( fd_borrowed_account_t * account, fd_rent_t const * rent, ulong fee ) {
     192        6831 :   if( FD_UNLIKELY( account->const_meta->info.lamports==0UL ) ) {
     193          39 :     return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     194          39 :   }
     195             : 
     196        6792 :   ulong min_balance = 0UL;
     197             : 
     198        6792 :   int is_nonce = fd_executor_is_system_nonce_account( account );
     199        6792 :   if ( FD_UNLIKELY( is_nonce<0 ) ) {
     200          21 :     return FD_RUNTIME_TXN_ERR_INVALID_ACCOUNT_FOR_FEE;
     201          21 :   }
     202             : 
     203        6771 :   if( is_nonce ) {
     204          60 :     min_balance = fd_rent_exempt_minimum_balance( rent, 80 );
     205          60 :   }
     206             : 
     207        6771 :   ulong out = ULONG_MAX;
     208        6771 :   int cf = fd_ulong_checked_sub( account->const_meta->info.lamports, min_balance, &out);
     209        6771 :   if( FD_UNLIKELY( cf!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
     210           6 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
     211           6 :   }
     212             : 
     213        6765 :   cf = fd_ulong_checked_sub( out, fee, &out );
     214        6765 :   if( FD_UNLIKELY( cf!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
     215          39 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
     216          39 :   }
     217             : 
     218        6726 :   if( FD_UNLIKELY( account->const_meta->info.lamports<fee ) ) {
     219           0 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
     220        6726 :   } else if( FD_UNLIKELY( memcmp( account->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) != 0 &&
     221        6726 :                           !check_rent_transition( account, rent, fee ) ) ) {
     222           0 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
     223           0 :   }
     224             : 
     225        6726 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     226        6726 : }
     227             : 
     228             : static int
     229           0 : status_check_tower( ulong slot, void * _ctx ) {
     230           0 :   fd_exec_slot_ctx_t * ctx = (fd_exec_slot_ctx_t *)_ctx;
     231           0 :   if( slot==ctx->slot_bank.slot ) {
     232           0 :     return 1;
     233           0 :   }
     234             : 
     235           0 :   if( fd_txncache_is_rooted_slot( ctx->status_cache, slot ) ) {
     236           0 :     return 1;
     237           0 :   }
     238             : 
     239           0 :   if( fd_sysvar_slot_history_find_slot( ctx->slot_history, slot ) == FD_SLOT_HISTORY_SLOT_FOUND ) {
     240           0 :     return 1;
     241           0 :   }
     242             : 
     243           0 :   return 0;
     244           0 : }
     245             : 
     246             : static int
     247        6930 : fd_executor_check_status_cache( fd_exec_txn_ctx_t * txn_ctx ) {
     248             : 
     249        6930 :   if( FD_UNLIKELY( !txn_ctx->slot_ctx->status_cache ) ) {
     250        6930 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     251        6930 :   }
     252             : 
     253           0 :   fd_hash_t * blockhash = (fd_hash_t *)((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->recent_blockhash_off);
     254             : 
     255           0 :   fd_txncache_query_t curr_query;
     256           0 :   curr_query.blockhash = blockhash->uc;
     257           0 :   fd_blake3_t b3[1];
     258             : 
     259             :   /* Compute the blake3 hash of the transaction message
     260             :      https://github.com/anza-xyz/agave/blob/v2.1.7/sdk/program/src/message/versions/mod.rs#L159-L167 */
     261           0 :   fd_blake3_init( b3 );
     262           0 :   fd_blake3_append( b3, "solana-tx-message-v1", 20UL );
     263           0 :   fd_blake3_append( b3, ((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->message_off),(ulong)( txn_ctx->_txn_raw->txn_sz - txn_ctx->txn_descriptor->message_off ) );
     264           0 :   fd_blake3_fini( b3, &txn_ctx->blake_txn_msg_hash );
     265           0 :   curr_query.txnhash = txn_ctx->blake_txn_msg_hash.uc;
     266             : 
     267             :   // TODO: figure out if it is faster to batch query properly and loop all txns again
     268           0 :   int err;
     269           0 :   fd_txncache_query_batch( txn_ctx->slot_ctx->status_cache, &curr_query, 1UL, (void *)txn_ctx->slot_ctx, status_check_tower, &err );
     270           0 :   return err;
     271        6930 : }
     272             : 
     273             : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3596-L3605 */
     274             : int
     275        7971 : fd_executor_check_transactions( fd_exec_txn_ctx_t * txn_ctx ) {
     276             :   /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3603 */
     277        7971 :   int err = fd_check_transaction_age( txn_ctx );
     278        7971 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     279        1041 :     return err;
     280        1041 :   }
     281             : 
     282             :   /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3604 */
     283        6930 :   err = fd_executor_check_status_cache( txn_ctx );
     284        6930 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     285           0 :     return err;
     286           0 :   }
     287             : 
     288        6930 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     289        6930 : }
     290             : 
     291             : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/verify_precompiles.rs#L11-L34 */
     292             : int
     293       12477 : fd_executor_verify_precompiles( fd_exec_txn_ctx_t * txn_ctx ) {
     294       12477 :   ushort                 instr_cnt = txn_ctx->txn_descriptor->instr_cnt;
     295       12477 :   fd_acct_addr_t const * tx_accs   = fd_txn_get_acct_addrs( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
     296       12477 :   int                    err       = 0;
     297       26049 :   for( ushort i=0; i<instr_cnt; i++ ) {
     298       17685 :     fd_txn_instr_t  const * instr      = &txn_ctx->txn_descriptor->instr[i];
     299       17685 :     fd_acct_addr_t  const * program_id = tx_accs + instr->program_id;
     300       17685 :     if( !memcmp( program_id, &fd_solana_ed25519_sig_verify_program_id, sizeof(fd_pubkey_t) ) ) {
     301        3687 :       err = fd_precompile_ed25519_verify( txn_ctx, instr );
     302        3687 :       if( FD_UNLIKELY( err ) ) {
     303        3144 :         FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
     304        3144 :         return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
     305        3144 :       }
     306       13998 :     } else if( !memcmp( program_id, &fd_solana_keccak_secp_256k_program_id, sizeof(fd_pubkey_t) )) {
     307         393 :       err = fd_precompile_secp256k1_verify( txn_ctx, instr );
     308         393 :       if( FD_UNLIKELY( err ) ) {
     309         315 :         FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
     310         315 :         return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
     311         315 :       }
     312       13605 :     } else if( !memcmp( program_id, &fd_solana_secp256r1_program_id, sizeof(fd_pubkey_t)) && FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, enable_secp256r1_precompile ) ) {
     313        1071 :       err = fd_precompile_secp256r1_verify( txn_ctx, instr );
     314        1071 :       if( FD_UNLIKELY( err ) ) {
     315         654 :         FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
     316         654 :         return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
     317         654 :       }
     318        1071 :     }
     319       17685 :   }
     320        8364 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     321       12477 : }
     322             : 
     323             : 
     324             : /* Only accounts in the transaction account keys that are owned by one of the four 
     325             :    loaders (bpf v1, v2, v3, v4) are iterated over in Agave's replenish_program_cache() 
     326             :    function to be loaded into the program cache. An account may be in the program cache 
     327             :    iff the owners match one of the four loaders since `filter_executable_program_accounts()` 
     328             :    filters out all other accounts here:
     329             :    https://github.com/anza-xyz/agave/blob/v2.1/svm/src/transaction_processor.rs#L530-L560
     330             :  
     331             :    If this check holds true, the account is promoted to an executable account within 
     332             :    `fd_execute_load_transaction_accounts()`, which sadly involves modifying its read-only metadata
     333             :    to set the `executable` flag to true.
     334             :  
     335             :    Note that although the v4 loader is not yet activated, Agave still checks that the
     336             :    owner matches one of the four bpf loaders provided in the hyperlink below
     337             :    within `filter_executable_program_accounts()`:
     338             :    https://github.com/anza-xyz/agave/blob/v2.1/sdk/account/src/lib.rs#L800-L806 */
     339             : FD_FN_PURE static inline int
     340         450 : is_maybe_in_loaded_program_cache( fd_borrowed_account_t const * acct ) {
     341         450 :   return !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_deprecated_program_id.key,  sizeof(fd_pubkey_t) ) ||
     342         450 :          !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_program_id.key,             sizeof(fd_pubkey_t) ) ||
     343         450 :          !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ||
     344         450 :          !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_v4_program_id.key,          sizeof(fd_pubkey_t) );
     345         450 : }
     346             : 
     347             : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L410-427 */
     348             : static int
     349             : accumulate_and_check_loaded_account_data_size( ulong   acc_size,
     350             :                                                ulong   requested_loaded_accounts_data_size,
     351       35682 :                                                ulong * accumulated_account_size ) {
     352       35682 :   *accumulated_account_size = fd_ulong_sat_add( *accumulated_account_size, acc_size );
     353       35682 :   if( FD_UNLIKELY( *accumulated_account_size>requested_loaded_accounts_data_size ) ) {
     354           0 :     return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
     355           0 :   }
     356       35682 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     357       35682 : }
     358             : 
     359             : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L191-L372 */
     360             : int
     361        6726 : fd_executor_load_transaction_accounts( fd_exec_txn_ctx_t * txn_ctx ) {
     362             : 
     363        6726 :   ulong                 requested_loaded_accounts_data_size = txn_ctx->loaded_accounts_data_size_limit;
     364        6726 :   ulong                 accumulated_account_size            = 0UL;
     365        6726 :   fd_rawtxn_b_t const * txn_raw                             = txn_ctx->_txn_raw;
     366        6726 :   ushort                instr_cnt                           = txn_ctx->txn_descriptor->instr_cnt;
     367             : 
     368             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L323-L337 
     369             :      
     370             :      In the agave client, this big chunk of code is responsible for loading in all of the
     371             :      accounts in the transaction, mimicking each call to `load_transaction_account()`
     372             :      (https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L406-L497)
     373             :      
     374             :      This contains a LOT of special casing as their accounts database and program cache
     375             :      is handled very differently than the FD client.
     376             : 
     377             :      The logic is as follows:
     378             :      1. If the account is the instructions sysvar, then load in the compiled
     379             :         instructions from the transactions into the sysvar's data.
     380             :      2. If the account is a fee payer, then it is already loaded.
     381             :      3. If the account is an account override, then handle seperately. Account
     382             :         overrides are used for simulating transactions. 
     383             :         - This is only used for testing.
     384             :      4. If the account is not writable and not an instruction account and it is
     385             :         in the loaded program cache, then load in a dummy account with the
     386             :         correct owner and the executable flag set to true.
     387             :         - See comments below
     388             :      5. Otherwise load in the account from the accounts DB. If the account is
     389             :         writable try to collect rent from the account.
     390             : 
     391             :      After the account is loaded, accumulate the data size to make sure the
     392             :      transaction doesn't violate the transaction loading limit.
     393             : 
     394             :      In the firedancer client only some of these steps are necessary because
     395             :      all of the accounts are loaded in from the accounts db into borrowed
     396             :      accounts already.
     397             :      1. If the account is writable, try to collect fees on the account. Unlike
     398             :         the agave client, this is also done on the fee payer account. The agave
     399             :         client tries to collect rent on the fee payer while the fee is being
     400             :         collected in validate_fees().
     401             :      2. If the account is not writable and it is not an instruction account
     402             :         and would be in the loaded program cache, then it should be replaced
     403             :         with a dummy value.
     404             :      */
     405             : 
     406        6726 :   fd_epoch_schedule_t const * schedule = fd_sysvar_cache_epoch_schedule( txn_ctx->slot_ctx->sysvar_cache );
     407        6726 :   ulong                       epoch    = fd_slot_to_epoch( schedule, txn_ctx->slot_ctx->slot_bank.slot, NULL );
     408             : 
     409             :   /* In `load_transaction_account()`, there are special checks based on the priviledges of each account key.
     410             :      We precompute the results here before proceeding with the special casing. Note the start range of 1 since
     411             :      `load_transaction_account()` doesn't handle the fee payer. */
     412        6726 :   uchar txn_account_is_instruction_account[MAX_TX_ACCOUNT_LOCKS] = {0};
     413       18066 :   for( ushort i=0UL; i<instr_cnt; i++ ) {
     414             :     /* Set up the instr infos for the transaction */
     415       11340 :     fd_txn_instr_t const * instr = &txn_ctx->txn_descriptor->instr[i];
     416       11340 :     fd_convert_txn_instr_to_instr( txn_ctx, instr, txn_ctx->borrowed_accounts, &txn_ctx->instr_infos[i] );
     417             : 
     418       11340 :     uchar const * instr_acc_idxs = fd_txn_get_instr_accts( instr, txn_raw->raw );
     419       49719 :     for( ushort j=0; j<instr->acct_cnt; j++ ) {
     420       38379 :       uchar txn_acc_idx                               = instr_acc_idxs[j];
     421       38379 :       txn_account_is_instruction_account[txn_acc_idx] = 1;
     422       38379 :     }
     423       11340 :   }
     424        6726 :   txn_ctx->instr_info_cnt = txn_ctx->txn_descriptor->instr_cnt;
     425             : 
     426       40116 :   for( ulong i=1UL; i<txn_ctx->accounts_cnt; i++ ) {
     427       33390 :     fd_borrowed_account_t * acct = &txn_ctx->borrowed_accounts[i];
     428             : 
     429             :     /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L418-L421 */
     430       33390 :     uchar is_instruction_account = txn_account_is_instruction_account[i];
     431       33390 :     uchar is_writable            = !!( fd_txn_account_is_writable_idx( txn_ctx, (int)i ) );
     432             : 
     433             :     /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L417 */
     434             : 
     435       33390 :     int   err      = fd_txn_borrowed_account_view_idx( txn_ctx, (uchar)i, &acct );
     436       33390 :     ulong acc_size = err==FD_ACC_MGR_SUCCESS ? acct->const_meta->dlen : 0UL;
     437             : 
     438             :     /* `load_transaction_account()`
     439             :         https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L406-L497 */
     440             : 
     441             :     /* First case: checking if the account is the instructions sysvar
     442             :        https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L422-L429 */
     443       33390 :     if( FD_UNLIKELY( !memcmp( acct->pubkey->key, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) ) ) {
     444          93 :       acct->account_found = 1;
     445          93 :       fd_sysvar_instructions_serialize_account( txn_ctx, (fd_instr_info_t const *)txn_ctx->instr_infos, txn_ctx->txn_descriptor->instr_cnt );
     446             :       /* Continue because this should not be counted towards the total loaded account size.
     447             :          https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L426 */
     448          93 :       continue;
     449          93 :     } 
     450             : 
     451             :     /* Second case: loading a program account that is not writable, not an instruction account,
     452             :        not already executable, and may be in the loaded program cache. We bypass this special casing
     453             :        if `disable_account_loader_special_case` is active.
     454             :        https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L438-L451 */
     455       33297 :     if( FD_UNLIKELY( !fd_account_is_executable( acct->const_meta ) && 
     456       33297 :                      !is_instruction_account && !is_writable && 
     457       33297 :                      !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, disable_account_loader_special_case ) && 
     458       33297 :                      is_maybe_in_loaded_program_cache( acct ) ) ) {
     459             :       /* In the corresponding branch in the agave client, a dummy account is loaded in that has the
     460             :          executable flag set to true. This is a hack to mirror those semantics.
     461             :          https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L499-L507 */
     462         135 :       void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
     463         135 :       fd_borrowed_account_make_readonly_copy( acct, borrowed_account_data );
     464         135 :       fd_account_meta_t * meta = (fd_account_meta_t *)acct->const_meta;
     465         135 :       meta->info.executable = 1;
     466         135 :       acct->account_found = 1;
     467         135 :     }
     468             :     /* Third case: Default case 
     469             :        https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L452-L494 */
     470       33162 :     else {
     471             :       /* If the account exists and is writable, collect rent from it. */
     472       33162 :       if( FD_LIKELY( acct->account_found ) ) {
     473       30984 :         if( is_writable ) {
     474       11280 :           txn_ctx->collected_rent += fd_runtime_collect_rent_from_account( txn_ctx->slot_ctx, acct->meta, acct->pubkey, epoch );
     475       11280 :           acct->starting_lamports = acct->meta->info.lamports;
     476       11280 :         }
     477       30984 :       }
     478       33162 :     }
     479             : 
     480       33297 :     err = accumulate_and_check_loaded_account_data_size( acc_size,
     481       33297 :                                                          requested_loaded_accounts_data_size,
     482       33297 :                                                          &accumulated_account_size );
     483             : 
     484       33297 :     if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     485           0 :       return err;
     486           0 :     }
     487       33297 :   }
     488             : 
     489        6726 :   fd_pubkey_t program_owners[instr_cnt];
     490        6726 :   ushort      program_owners_cnt = 0;
     491             : 
     492             :   /* The logic below handles special casing with loading instruction accounts.
     493             :      https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L297-L358 */
     494             :   /* At this point, we can set has_program_id to be 0 as the program_indices vector is of length 0 */
     495        6726 :   txn_ctx->has_program_id = 0;
     496       15621 :   for( ushort i=0; i<instr_cnt; i++ ) {
     497       10431 :     fd_txn_instr_t const * instr = &txn_ctx->txn_descriptor->instr[i];
     498             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L304-306 */
     499       10431 :     fd_borrowed_account_t * program_account = NULL;
     500       10431 :     int err = fd_txn_borrowed_account_view_idx_allow_dead( txn_ctx, instr->program_id, &program_account );
     501       10431 :     if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     502           0 :       return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
     503           0 :     }
     504             : 
     505             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L307-309 */
     506       10431 :     if( FD_UNLIKELY( !memcmp( program_account->pubkey->key, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) ) {
     507          60 :       continue;
     508          60 :     }
     509             : 
     510             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L311-L314 */
     511       10371 :     if( FD_UNLIKELY( !program_account->account_found ) ) {
     512         963 :       return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
     513         963 :     }
     514             : 
     515             :     /* The above checks from the mirrored `load_transaction_account()` function would promote
     516             :        this account to executable if necessary, so this check is sufficient.
     517             :        https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/svm/src/account_loader.rs#L493-L500 */
     518        9408 :     if( FD_UNLIKELY( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_accounts_executable_flag_checks ) && 
     519        9408 :                      !fd_account_is_executable( program_account->const_meta ) ) ) {
     520         120 :       return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
     521         120 :     }
     522             : 
     523             :     /* At this point, program_indices will no longer have 0 length, so we are set this flag to 1 */
     524             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L321 */
     525        9288 :     txn_ctx->has_program_id = 1;
     526             : 
     527             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L322-325 */
     528        9288 :     if( !memcmp( program_account->const_meta->info.owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) {
     529        6450 :       continue;
     530        6450 :     }
     531             : 
     532             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L326-330
     533             :        The agave client does checks on the program account's owners as well.
     534             :        However, it is important to not do these checks multiple times as the
     535             :        total size of accounts and their owners are accumulated: duplicate owners
     536             :        should be avoided. */
     537             : 
     538             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L334-353 */
     539        2838 :     FD_BORROWED_ACCOUNT_DECL( owner_account );
     540        2838 :     err = fd_acc_mgr_view( txn_ctx->slot_ctx->acc_mgr, txn_ctx->slot_ctx->funk_txn, (fd_pubkey_t *) program_account->const_meta->info.owner, owner_account );
     541        2838 :     if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     542         450 :       return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
     543         450 :     }
     544             : 
     545        2646 :     for( ushort i=0; i<program_owners_cnt; i++ ) {
     546         258 :       if( !memcmp( program_owners[i].key, owner_account->pubkey, sizeof(fd_pubkey_t) ) ) {
     547             :         /* If the owner account has already been seen, skip the owner checks
     548             :            and do not acccumulate the account size. */
     549         255 :         continue;
     550         255 :       }
     551         258 :     }
     552             : 
     553             :     /* https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/svm/src/account_loader.rs#L537-L545 */
     554        2388 :     if( FD_UNLIKELY( memcmp( owner_account->const_meta->info.owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ||
     555        2388 :                      ( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_accounts_executable_flag_checks ) &&
     556        2388 :                        !fd_account_is_executable( owner_account->const_meta ) ) ) ) {
     557           3 :       return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
     558           3 :     }
     559             : 
     560             :     /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L342-347 */
     561             :     /* Count the owner's data in the loaded account size for program accounts.
     562             :        However, it is important to not double count repeated owners. */
     563        2385 :     err = accumulate_and_check_loaded_account_data_size( owner_account->const_meta->dlen,
     564        2385 :                                                          requested_loaded_accounts_data_size,
     565        2385 :                                                          &accumulated_account_size );
     566        2385 :     if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     567           0 :       return err;
     568           0 :     }
     569             : 
     570        2385 :     fd_memcpy( &program_owners[ program_owners_cnt++ ], owner_account->pubkey, sizeof(fd_pubkey_t) );
     571        2385 :   }
     572             : 
     573        5190 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     574        6726 : }
     575             : 
     576             : /* https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
     577             : int
     578        8364 : fd_executor_validate_account_locks( fd_exec_txn_ctx_t const * txn_ctx ) {
     579             :   /* Ensure the number of account keys does not exceed the transaction lock limit
     580             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L123 */
     581        8364 :   ulong tx_account_lock_limit = get_transaction_account_lock_limit( txn_ctx->slot_ctx );
     582        8364 :   if( FD_UNLIKELY( txn_ctx->accounts_cnt>tx_account_lock_limit ) ) {
     583          45 :     return FD_RUNTIME_TXN_ERR_TOO_MANY_ACCOUNT_LOCKS;
     584          45 :   }
     585             : 
     586             :   /* Duplicate account check
     587             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L125 */
     588       54504 :   for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
     589      218430 :     for( ushort j=(ushort)(i+1U); j<txn_ctx->accounts_cnt; j++ ) {
     590      172245 :       if( FD_UNLIKELY( !memcmp( &txn_ctx->accounts[i], &txn_ctx->accounts[j], sizeof(fd_pubkey_t) ) ) ) {
     591         348 :         return FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE;
     592         348 :       }
     593      172245 :     }
     594       46533 :   }
     595             : 
     596             :   /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/sdk/src/transaction/sanitized.rs#L286-L288 */
     597        7971 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     598        8319 : }
     599             : 
     600             : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L101-L126 */
     601             : static int
     602             : fd_should_set_exempt_rent_epoch_max( fd_exec_slot_ctx_t const * slot_ctx,
     603       22212 :                                      fd_borrowed_account_t *    rec ) {
     604             :   /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L109-L125 */
     605       22212 :   if( FD_FEATURE_ACTIVE( slot_ctx, disable_rent_fees_collection ) ) {
     606        4596 :     if( FD_LIKELY( rec->const_meta->info.rent_epoch!=ULONG_MAX
     607        4596 :                 && rec->const_meta->info.lamports>=fd_rent_exempt_minimum_balance( &slot_ctx->epoch_ctx->epoch_bank.rent, rec->const_meta->dlen ) ) ) {
     608        3093 :       return 1;
     609        3093 :     }
     610        1503 :     return 0;
     611        4596 :   }
     612             : 
     613       17616 :   ulong epoch = fd_slot_to_epoch( &slot_ctx->epoch_ctx->epoch_bank.epoch_schedule, slot_ctx->slot_bank.slot, NULL );
     614             : 
     615             :   /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L158-L162 */
     616       17616 :   if( rec->const_meta->info.rent_epoch==ULONG_MAX || rec->const_meta->info.rent_epoch>epoch ) {
     617       17175 :     return 0;
     618       17175 :   }
     619             : 
     620             :   /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L163-L166 */
     621         441 :   if( rec->const_meta->info.executable || !memcmp( rec->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) ) {
     622         156 :     return 1;
     623         156 :   }
     624             : 
     625             :   /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L167-L183 */
     626         285 :   if( rec->const_meta->info.lamports && rec->const_meta->info.lamports<fd_rent_exempt_minimum_balance( &slot_ctx->epoch_ctx->epoch_bank.rent, rec->const_meta->dlen ) ) {
     627         132 :     return 0;
     628         132 :   }
     629             : 
     630         153 :   return 1;
     631         285 : }
     632             : 
     633             : static void
     634             : compute_priority_fee( fd_exec_txn_ctx_t const * txn_ctx,
     635             :                       ulong *                   fee,
     636        6831 :                       ulong *                   priority ) {
     637        6831 :   switch( txn_ctx->prioritization_fee_type ) {
     638        6771 :   case FD_COMPUTE_BUDGET_PRIORITIZATION_FEE_TYPE_DEPRECATED: {
     639        6771 :     if( !txn_ctx->compute_unit_limit ) {
     640          27 :       *priority = 0UL;
     641          27 :     }
     642        6744 :     else {
     643        6744 :       uint128 micro_lamport_fee = (uint128)txn_ctx->compute_unit_price * (uint128)MICRO_LAMPORTS_PER_LAMPORT;
     644        6744 :       uint128 _priority         = micro_lamport_fee / (uint128)txn_ctx->compute_unit_limit;
     645        6744 :       *priority                 = _priority > (uint128)ULONG_MAX ? ULONG_MAX : (ulong)_priority;
     646        6744 :     }
     647             : 
     648        6771 :     *fee = txn_ctx->compute_unit_price;
     649        6771 :     return;
     650             : 
     651          60 :   } case FD_COMPUTE_BUDGET_PRIORITIZATION_FEE_TYPE_COMPUTE_UNIT_PRICE: {
     652          60 :     uint128 micro_lamport_fee = (uint128)txn_ctx->compute_unit_price * (uint128)txn_ctx->compute_unit_limit;
     653          60 :     *priority                 = txn_ctx->compute_unit_price;
     654          60 :     uint128 _fee              = (micro_lamport_fee + (uint128)(MICRO_LAMPORTS_PER_LAMPORT - 1)) / (uint128)(MICRO_LAMPORTS_PER_LAMPORT);
     655          60 :     *fee                      = _fee > (uint128)ULONG_MAX ? ULONG_MAX : (ulong)_fee;
     656          60 :     return;
     657             : 
     658           0 :   }
     659           0 :   default:
     660           0 :     __builtin_unreachable();
     661        6831 :   }
     662        6831 : }
     663             : 
     664             : static ulong
     665        6831 : fd_executor_lamports_per_signature( fd_slot_bank_t const *slot_bank ) {
     666             :   // https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/fee_calculator.rs#L110
     667        6831 :   return slot_bank->fee_rate_governor.target_lamports_per_signature / 2;
     668        6831 : }
     669             : 
     670             : static void
     671             : fd_executor_calculate_fee( fd_exec_txn_ctx_t *txn_ctx,
     672             :                           fd_txn_t const *txn_descriptor,
     673             :                           fd_rawtxn_b_t const *txn_raw,
     674             :                           ulong *ret_execution_fee,
     675        6831 :                           ulong *ret_priority_fee) {
     676             : 
     677             :   // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L82
     678        6831 :   #define ACCOUNT_DATA_COST_PAGE_SIZE fd_ulong_sat_mul(32, 1024)
     679             : 
     680             :   // https://github.com/firedancer-io/solana/blob/08a1ef5d785fe58af442b791df6c4e83fe2e7c74/runtime/src/bank.rs#L4443
     681        6831 :   ulong priority     = 0UL;
     682        6831 :   ulong priority_fee = 0UL;
     683        6831 :   compute_priority_fee( txn_ctx, &priority_fee, &priority );
     684             : 
     685             :   // let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature);
     686        6831 :   ulong num_signatures = txn_descriptor->signature_cnt;
     687       18375 :   for (ushort i=0; i<txn_descriptor->instr_cnt; ++i ) {
     688       11544 :     fd_txn_instr_t const * txn_instr  = &txn_descriptor->instr[i];
     689       11544 :     fd_pubkey_t *          program_id = &txn_ctx->accounts[txn_instr->program_id];
     690       11544 :     if( !memcmp(program_id->uc, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t)) ||
     691       11544 :         !memcmp(program_id->uc, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t)) ||
     692       11544 :         (!memcmp(program_id->uc, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t)) && FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, enable_secp256r1_precompile )) ) {
     693         927 :       if( !txn_instr->data_sz ) {
     694           0 :         continue;
     695           0 :       }
     696         927 :       uchar * data   = (uchar *)txn_raw->raw + txn_instr->data_off;
     697         927 :       num_signatures = fd_ulong_sat_add(num_signatures, (ulong)(data[0]));
     698         927 :     }
     699       11544 :   }
     700             : 
     701        6831 :   ulong signature_fee = fd_executor_lamports_per_signature(&txn_ctx->slot_ctx->slot_bank) * num_signatures;
     702             : 
     703             :   // TODO: as far as I can tell, this is always 0
     704             :   //
     705             :   //            let write_lock_fee = Self::get_num_write_locks_in_message(message)
     706             :   //                .saturating_mul(fee_structure.lamports_per_write_lock);
     707        6831 :   ulong lamports_per_write_lock = 0UL;
     708        6831 :   ulong write_lock_fee          = fd_ulong_sat_mul(fd_txn_account_cnt(txn_descriptor, FD_TXN_ACCT_CAT_WRITABLE), lamports_per_write_lock);
     709             : 
     710             :   // TODO: the fee_structure bin is static and default..
     711             :   //        let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
     712             :   //            FeeStructure::calculate_memory_usage_cost(
     713             :   //                budget_limits.loaded_accounts_data_size_limit,
     714             :   //                budget_limits.heap_cost,
     715             :   //            )
     716             :   //        } else {
     717             :   //            0_u64
     718             :   //        };
     719             :   //        let total_compute_units =
     720             :   //            loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
     721             :   //        let compute_fee = self
     722             :   //            .compute_fee_bins
     723             :   //            .iter()
     724             :   //            .find(|bin| total_compute_units <= bin.limit)
     725             :   //            .map(|bin| bin.fee)
     726             :   //            .unwrap_or_else(|| {
     727             :   //                self.compute_fee_bins
     728             :   //                    .last()
     729             :   //                    .map(|bin| bin.fee)
     730             :   //                    .unwrap_or_default()
     731             :   //            });
     732             : 
     733             :   // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L203-L206
     734        6831 :   ulong execution_fee = fd_ulong_sat_add( signature_fee, write_lock_fee );
     735             : 
     736        6831 :   if( execution_fee >= ULONG_MAX ) {
     737           0 :     *ret_execution_fee = ULONG_MAX;
     738        6831 :   } else {
     739        6831 :     *ret_execution_fee = execution_fee;
     740        6831 :   }
     741             : 
     742        6831 :   if( priority_fee >= ULONG_MAX ) {
     743           3 :     *ret_priority_fee = ULONG_MAX;
     744        6828 :   } else {
     745        6828 :     *ret_priority_fee = priority_fee;
     746        6828 :   }
     747             : 
     748        6831 :   #undef ACCOUNT_DATA_COST_PAGE_SIZE
     749        6831 : }
     750             : 
     751             : static int
     752        6831 : fd_executor_collect_fees( fd_exec_txn_ctx_t * txn_ctx, fd_borrowed_account_t * fee_payer_rec ) {
     753             : 
     754        6831 :   ulong execution_fee = 0UL;
     755        6831 :   ulong priority_fee  = 0UL;
     756             : 
     757        6831 :   fd_executor_calculate_fee( txn_ctx, txn_ctx->txn_descriptor, txn_ctx->_txn_raw, &execution_fee, &priority_fee );
     758             : 
     759        6831 :   fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( txn_ctx->slot_ctx->epoch_ctx );
     760        6831 :   ulong             total_fee  = fd_ulong_sat_add( execution_fee, priority_fee );
     761             : 
     762             :   // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L54
     763        6831 :   if ( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_rounding_in_fee_calculation ) ) {
     764        5505 :     total_fee = fd_rust_cast_double_to_ulong( round( (double)total_fee ) );
     765        5505 :   }
     766             : 
     767        6831 :   int err = fd_validate_fee_payer( fee_payer_rec, &epoch_bank->rent, total_fee );
     768        6831 :   if( FD_UNLIKELY( err ) ) {
     769         105 :     return err;
     770         105 :   }
     771             : 
     772             :   /* At this point, the fee payer has been validated and the fee has been
     773             :      calculated. This means that the fee can be safely subtracted from the
     774             :      fee payer's borrowed account. However, the starting lamports of the
     775             :      account must be updated as well. Each instruction must have the net
     776             :      same (balanced) amount of lamports. This is done by comparing the
     777             :      borrowed accounts starting lamports and comparing it to the sum of
     778             :      the ending lamports. Therefore, we need to update the starting lamports
     779             :      specifically for the fee payer.
     780             : 
     781             :      This is especially important in the case where the transaction fails. This
     782             :      is because we need to roll back the account to the balance AFTER the fee
     783             :      is paid. It is also possible for the accounts data and owner to change.
     784             :      This means that the entire state of the borrowed account must be rolled
     785             :      back to this point. */
     786             : 
     787        6726 :   fee_payer_rec->meta->info.lamports -= total_fee;
     788        6726 :   fee_payer_rec->starting_lamports    = fee_payer_rec->meta->info.lamports;
     789             : 
     790             :   /* Update the fee payer's rent epoch to ULONG_MAX if it is rent exempt. */
     791        6726 :   if( fd_should_set_exempt_rent_epoch_max( txn_ctx->slot_ctx, fee_payer_rec ) ) {
     792           0 :     fee_payer_rec->meta->info.rent_epoch = ULONG_MAX;
     793           0 :   }
     794             : 
     795        6726 :   txn_ctx->execution_fee = execution_fee;
     796        6726 :   txn_ctx->priority_fee  = priority_fee;
     797             : 
     798        6726 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     799        6831 : }
     800             : 
     801             : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L413-L497 */
     802             : int
     803        6930 : fd_executor_validate_transaction_fee_payer( fd_exec_txn_ctx_t * txn_ctx ) {
     804             :   /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L423-L430 */
     805        6930 :   int err = fd_executor_compute_budget_program_execute_instructions( txn_ctx, txn_ctx->_txn_raw );
     806        6930 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     807          99 :     return err;
     808          99 :   }
     809             : 
     810             :   /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L431-L436 */
     811        6831 :   fd_borrowed_account_t * fee_payer_rec = NULL;
     812        6831 :   err = fd_txn_borrowed_account_modify_fee_payer( txn_ctx, &fee_payer_rec );
     813        6831 :   if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     814           0 :     return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     815           0 :   }
     816             : 
     817             :   /* Collect rent from the fee payer and set the starting lamports (to avoid unbalanced lamports issues in instruction execution)
     818             :      https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L438-L445 */
     819        6831 :   fd_epoch_schedule_t const * schedule = fd_sysvar_cache_epoch_schedule( txn_ctx->slot_ctx->sysvar_cache );
     820        6831 :   ulong                       epoch    = fd_slot_to_epoch( schedule, txn_ctx->slot_ctx->slot_bank.slot, NULL );
     821        6831 :   txn_ctx->collected_rent += fd_runtime_collect_rent_from_account( txn_ctx->slot_ctx, fee_payer_rec->meta, fee_payer_rec->pubkey, epoch );
     822        6831 :   fee_payer_rec->starting_lamports = fee_payer_rec->meta->info.lamports;
     823             : 
     824             :   /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L431-L488 */
     825        6831 :   err = fd_executor_collect_fees( txn_ctx, fee_payer_rec );
     826        6831 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     827         105 :     return err;
     828         105 :   }
     829             : 
     830        6726 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     831        6831 : }
     832             : 
     833             : int
     834       12741 : fd_executor_setup_accessed_accounts_for_txn( fd_exec_txn_ctx_t * txn_ctx ) {
     835             : 
     836       12741 :   fd_pubkey_t * tx_accs   = (fd_pubkey_t *)((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->acct_addr_off);
     837             : 
     838             :   // Set up accounts in the transaction body and perform checks
     839       59679 :   for( ulong i = 0; i < txn_ctx->txn_descriptor->acct_addr_cnt; i++ ) {
     840       46938 :     txn_ctx->accounts[i] = tx_accs[i];
     841       46938 :   }
     842             : 
     843       12741 :   txn_ctx->accounts_cnt += (uchar) txn_ctx->txn_descriptor->acct_addr_cnt;
     844             : 
     845       12741 :   if( txn_ctx->txn_descriptor->transaction_version == FD_TXN_V0 ) {
     846             :     /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/runtime/src/bank/address_lookup_table.rs#L44-L48 */
     847       12228 :     fd_slot_hashes_t const * slot_hashes = fd_sysvar_cache_slot_hashes( txn_ctx->slot_ctx->sysvar_cache );
     848       12228 :     if( FD_UNLIKELY( !slot_hashes ) ) {
     849           0 :       return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     850           0 :     }
     851             : 
     852       12228 :     fd_pubkey_t readonly_lut_accs[128];
     853       12228 :     ulong readonly_lut_accs_cnt = 0;
     854       12228 :     FD_SCRATCH_SCOPE_BEGIN {
     855             :       // Set up accounts in the account look up tables.
     856       12228 :       fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn_ctx->txn_descriptor );
     857       19749 :       for( ulong i = 0; i < txn_ctx->txn_descriptor->addr_table_lookup_cnt; i++ ) {
     858        7785 :         fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
     859        7785 :         fd_pubkey_t const * addr_lut_acc = (fd_pubkey_t *)((uchar *)txn_ctx->_txn_raw->raw + addr_lut->addr_off);
     860             : 
     861             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L90-L94 */
     862        7785 :         FD_BORROWED_ACCOUNT_DECL(addr_lut_rec);
     863        7785 :         int err = fd_acc_mgr_view(txn_ctx->slot_ctx->acc_mgr, txn_ctx->slot_ctx->funk_txn, (fd_pubkey_t *) addr_lut_acc, addr_lut_rec);
     864        7785 :         if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
     865          57 :           return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
     866          57 :         }
     867             : 
     868             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L96-L114 */
     869        7728 :         if( FD_UNLIKELY( memcmp( addr_lut_rec->const_meta->info.owner, fd_solana_address_lookup_table_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
     870          36 :           return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER;
     871          36 :         }
     872             : 
     873             :         /* Realistically impossible case, but need to make sure we don't cause an OOB data access
     874             :            https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L205-L209 */
     875        7692 :         if( FD_UNLIKELY( addr_lut_rec->const_meta->dlen < FD_LOOKUP_TABLE_META_SIZE ) ) {
     876         117 :           return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     877         117 :         }
     878             : 
     879             :         /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/accounts-db/src/accounts.rs#L141-L142 */
     880        7575 :         fd_address_lookup_table_state_t addr_lookup_table_state;
     881        7575 :         fd_bincode_decode_ctx_t decode_ctx = {
     882        7575 :           .data = addr_lut_rec->const_data,
     883        7575 :           .dataend = &addr_lut_rec->const_data[FD_LOOKUP_TABLE_META_SIZE],
     884        7575 :           .valloc  = fd_scratch_virtual(),
     885        7575 :         };
     886             : 
     887             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L197-L214 */
     888        7575 :         if( FD_UNLIKELY( fd_address_lookup_table_state_decode( &addr_lookup_table_state, &decode_ctx ) ) ) {
     889           9 :           return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     890           9 :         }
     891             : 
     892             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L200-L203 */
     893        7566 :         if( FD_UNLIKELY( addr_lookup_table_state.discriminant != fd_address_lookup_table_state_enum_lookup_table ) ) {
     894           3 :           return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     895           3 :         }
     896             : 
     897             :         /* Again probably an impossible case, but the ALUT data needs to be 32-byte aligned
     898             :            https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L210-L214 */
     899        7563 :         if( FD_UNLIKELY( ( addr_lut_rec->const_meta->dlen - FD_LOOKUP_TABLE_META_SIZE ) & 0x1fUL ) ) {
     900           3 :           return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     901           3 :         }
     902             : 
     903             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L101-L112 */
     904        7560 :         fd_pubkey_t * lookup_addrs = (fd_pubkey_t *)&addr_lut_rec->const_data[FD_LOOKUP_TABLE_META_SIZE];
     905        7560 :         ulong lookup_addrs_cnt = ( addr_lut_rec->const_meta->dlen - FD_LOOKUP_TABLE_META_SIZE ) >> 5UL; // = (dlen - 56) / 32
     906             : 
     907             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L175-L176 */
     908        7560 :         ulong active_addresses_len;
     909        7560 :         err = fd_get_active_addresses_len( &addr_lookup_table_state.inner.lookup_table,
     910        7560 :                                            txn_ctx->slot_ctx->slot_bank.slot,
     911        7560 :                                            slot_hashes->hashes,
     912        7560 :                                            lookup_addrs_cnt,
     913        7560 :                                            &active_addresses_len );
     914        7560 :         if( FD_UNLIKELY( err ) ) {
     915           9 :           return err;
     916           9 :         }
     917             : 
     918             :         /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L169-L182 */
     919        7551 :         uchar * writable_lut_idxs = (uchar *)txn_ctx->_txn_raw->raw + addr_lut->writable_off;
     920       19014 :         for( ulong j = 0; j < addr_lut->writable_cnt; j++ ) {
     921             :           /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
     922       11475 :           if( writable_lut_idxs[j] >= active_addresses_len ) {
     923          12 :             return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
     924          12 :           }
     925       11463 :           txn_ctx->accounts[txn_ctx->accounts_cnt++] = lookup_addrs[writable_lut_idxs[j]];
     926       11463 :         }
     927             : 
     928        7539 :         uchar * readonly_lut_idxs = (uchar *)txn_ctx->_txn_raw->raw + addr_lut->readonly_off;
     929       15291 :         for( ulong j = 0; j < addr_lut->readonly_cnt; j++ ) {
     930             :           /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
     931        7770 :           if( readonly_lut_idxs[j] >= active_addresses_len ) {
     932          18 :             return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
     933          18 :           }
     934        7752 :           readonly_lut_accs[readonly_lut_accs_cnt++] = lookup_addrs[readonly_lut_idxs[j]];
     935        7752 :         }
     936        7539 :       }
     937       12228 :     } FD_SCRATCH_SCOPE_END;
     938             : 
     939       11964 :     fd_memcpy( &txn_ctx->accounts[txn_ctx->accounts_cnt], readonly_lut_accs, readonly_lut_accs_cnt * sizeof(fd_pubkey_t) );
     940       11964 :     txn_ctx->accounts_cnt += readonly_lut_accs_cnt;
     941       11964 :   }
     942       12477 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     943       12741 : }
     944             : 
     945             : static void
     946           0 : dump_sorted_features( const fd_features_t * features, fd_exec_test_feature_set_t * output_feature_set ) {
     947             :   /* NOTE: Caller must have a scratch frame prepared */
     948           0 :   uint64_t * unsorted_features = fd_scratch_alloc( alignof(uint64_t), FD_FEATURE_ID_CNT * sizeof(uint64_t) );
     949           0 :   ulong num_features = 0;
     950           0 :   for( const fd_feature_id_t * current_feature = fd_feature_iter_init(); !fd_feature_iter_done( current_feature ); current_feature = fd_feature_iter_next( current_feature ) ) {
     951           0 :     if (features->f[current_feature->index] != FD_FEATURE_DISABLED) {
     952           0 :       unsorted_features[num_features++] = (uint64_t) current_feature->id.ul[0];
     953           0 :     }
     954           0 :   }
     955             :   // Sort the features
     956           0 :   void * scratch = fd_scratch_alloc( sort_uint64_t_stable_scratch_align(), sort_uint64_t_stable_scratch_footprint(num_features) );
     957           0 :   uint64_t * sorted_features = sort_uint64_t_stable_fast( unsorted_features, num_features, scratch );
     958             : 
     959             :   // Set feature set in message
     960           0 :   output_feature_set->features_count = (pb_size_t) num_features;
     961           0 :   output_feature_set->features       = sorted_features;
     962           0 : }
     963             : 
     964             : static void
     965             : dump_account_state( fd_borrowed_account_t const * borrowed_account,
     966           0 :                       fd_exec_test_acct_state_t * output_account ) {
     967             :     // Address
     968           0 :     fd_memcpy(output_account->address, borrowed_account->pubkey, sizeof(fd_pubkey_t));
     969             : 
     970             :     // Lamports
     971           0 :     output_account->lamports = (uint64_t) borrowed_account->const_meta->info.lamports;
     972             : 
     973             :     // Data
     974           0 :     output_account->data = fd_scratch_alloc(alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(borrowed_account->const_meta->dlen));
     975           0 :     output_account->data->size = (pb_size_t) borrowed_account->const_meta->dlen;
     976           0 :     fd_memcpy(output_account->data->bytes, borrowed_account->const_data, borrowed_account->const_meta->dlen);
     977             : 
     978             :     // Executable
     979           0 :     output_account->executable = (bool) borrowed_account->const_meta->info.executable;
     980             : 
     981             :     // Rent epoch
     982           0 :     output_account->rent_epoch = (uint64_t) borrowed_account->const_meta->info.rent_epoch;
     983             : 
     984             :     // Owner
     985           0 :     fd_memcpy(output_account->owner, borrowed_account->const_meta->info.owner, sizeof(fd_pubkey_t));
     986             : 
     987             :     // Seed address (not present)
     988           0 :     output_account->has_seed_addr = false;
     989           0 : }
     990             : 
     991             : void
     992             : fd_create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t * instr_context,
     993             :                                                  fd_exec_txn_ctx_t const *txn_ctx,
     994           0 :                                                  fd_instr_info_t const *instr ) {
     995             :   /*
     996             :   NOTE: Calling this function requires the caller to have a scratch frame ready (see dump_instr_to_protobuf)
     997             :   */
     998             : 
     999             :   /* Prepare sysvar cache accounts */
    1000           0 :   fd_pubkey_t const fd_relevant_sysvar_ids[] = {
    1001           0 :     fd_sysvar_clock_id,
    1002           0 :     fd_sysvar_epoch_schedule_id,
    1003           0 :     fd_sysvar_epoch_rewards_id,
    1004           0 :     fd_sysvar_fees_id,
    1005           0 :     fd_sysvar_rent_id,
    1006           0 :     fd_sysvar_slot_hashes_id,
    1007           0 :     fd_sysvar_recent_block_hashes_id,
    1008           0 :     fd_sysvar_stake_history_id,
    1009           0 :     fd_sysvar_last_restart_slot_id,
    1010           0 :     fd_sysvar_instructions_id,
    1011           0 :   };
    1012           0 :   const ulong num_sysvar_entries = (sizeof(fd_relevant_sysvar_ids) / sizeof(fd_pubkey_t));
    1013             : 
    1014             :   /* Program ID */
    1015           0 :   fd_memcpy( instr_context->program_id, instr->program_id_pubkey.uc, sizeof(fd_pubkey_t) );
    1016             : 
    1017             :   /* Accounts */
    1018           0 :   instr_context->accounts_count = (pb_size_t) txn_ctx->accounts_cnt;
    1019           0 :   instr_context->accounts = fd_scratch_alloc(alignof(fd_exec_test_acct_state_t), (instr_context->accounts_count + num_sysvar_entries + txn_ctx->executable_cnt) * sizeof(fd_exec_test_acct_state_t));
    1020           0 :   for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
    1021             :     // Copy account information over
    1022           0 :     fd_borrowed_account_t const * borrowed_account = &txn_ctx->borrowed_accounts[i];
    1023           0 :     fd_exec_test_acct_state_t * output_account = &instr_context->accounts[i];
    1024           0 :     dump_account_state( borrowed_account, output_account );
    1025           0 :   }
    1026             : 
    1027             :   /* Add sysvar cache variables */
    1028           0 :   for( ulong i = 0; i < num_sysvar_entries; i++ ) {
    1029           0 :     FD_BORROWED_ACCOUNT_DECL(borrowed_account);
    1030           0 :     int ret = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, &fd_relevant_sysvar_ids[i], borrowed_account );
    1031           0 :     if( ret != FD_ACC_MGR_SUCCESS ) {
    1032           0 :       continue;
    1033           0 :     }
    1034             :     // Make sure the account doesn't exist in the output accounts yet
    1035           0 :     int account_exists = 0;
    1036           0 :     for( ulong j = 0; j < txn_ctx->accounts_cnt; j++ ) {
    1037           0 :       if ( 0 == memcmp( txn_ctx->accounts[j].key, fd_relevant_sysvar_ids[i].uc, sizeof(fd_pubkey_t) ) ) {
    1038           0 :         account_exists = true;
    1039           0 :         break;
    1040           0 :       }
    1041           0 :     }
    1042             : 
    1043             :     // Copy it into output
    1044           0 :     if (!account_exists) {
    1045           0 :       fd_exec_test_acct_state_t * output_account = &instr_context->accounts[instr_context->accounts_count++];
    1046           0 :       dump_account_state( borrowed_account, output_account );
    1047           0 :     }
    1048           0 :   }
    1049             : 
    1050             :   /* Add executable accounts */
    1051           0 :   for( ulong i = 0; i < txn_ctx->executable_cnt; i++ ) {
    1052           0 :     FD_BORROWED_ACCOUNT_DECL(borrowed_account);
    1053           0 :     int ret = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, txn_ctx->executable_accounts[i].pubkey, borrowed_account );
    1054           0 :     if( ret != FD_ACC_MGR_SUCCESS ) {
    1055           0 :       continue;
    1056           0 :     }
    1057             :     // Make sure the account doesn't exist in the output accounts yet
    1058           0 :     bool account_exists = false;
    1059           0 :     for( ulong j = 0; j < instr_context->accounts_count; j++ ) {
    1060           0 :       if( 0 == memcmp( instr_context->accounts[j].address, txn_ctx->executable_accounts[i].pubkey->uc, sizeof(fd_pubkey_t) ) ) {
    1061           0 :         account_exists = true;
    1062           0 :         break;
    1063           0 :       }
    1064           0 :     }
    1065             :     // Copy it into output
    1066           0 :     if( !account_exists ) {
    1067           0 :       fd_exec_test_acct_state_t * output_account = &instr_context->accounts[instr_context->accounts_count++];
    1068           0 :       dump_account_state( borrowed_account, output_account );
    1069           0 :     }
    1070           0 :   }
    1071             : 
    1072             :   /* Instruction Accounts */
    1073           0 :   instr_context->instr_accounts_count = (pb_size_t) instr->acct_cnt;
    1074           0 :   instr_context->instr_accounts = fd_scratch_alloc( alignof(fd_exec_test_instr_acct_t), instr_context->instr_accounts_count * sizeof(fd_exec_test_instr_acct_t) );
    1075           0 :   for( ushort i = 0; i < instr->acct_cnt; i++ ) {
    1076           0 :     fd_exec_test_instr_acct_t * output_instr_account = &instr_context->instr_accounts[i];
    1077             : 
    1078           0 :     uchar account_flag = instr->acct_flags[i];
    1079           0 :     bool is_writable = account_flag & FD_INSTR_ACCT_FLAGS_IS_WRITABLE;
    1080           0 :     bool is_signer = account_flag & FD_INSTR_ACCT_FLAGS_IS_SIGNER;
    1081             : 
    1082           0 :     output_instr_account->index = instr->acct_txn_idxs[i];
    1083           0 :     output_instr_account->is_writable = is_writable;
    1084           0 :     output_instr_account->is_signer = is_signer;
    1085           0 :   }
    1086             : 
    1087             :   /* Data */
    1088           0 :   instr_context->data = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(instr->data_sz) );
    1089           0 :   instr_context->data->size = (pb_size_t) instr->data_sz;
    1090           0 :   fd_memcpy( instr_context->data->bytes, instr->data, instr->data_sz );
    1091             : 
    1092             :   /* Compute Units */
    1093           0 :   instr_context->cu_avail = txn_ctx->compute_meter;
    1094             : 
    1095             :   /* Slot Context */
    1096           0 :   instr_context->has_slot_context = true;
    1097             : 
    1098             :   /* Epoch Context */
    1099           0 :   instr_context->has_epoch_context = true;
    1100           0 :   instr_context->epoch_context.has_features = true;
    1101           0 :   dump_sorted_features( &txn_ctx->epoch_ctx->features, &instr_context->epoch_context.features );
    1102           0 : }
    1103             : 
    1104             : /*  This function dumps individual instructions from a ledger replay.
    1105             : 
    1106             :     The following arguments can be added when replaying ledger transactions:
    1107             :       --dump-insn-to-pb <0/1>
    1108             :         * If enabled, instructions will be dumped to the specified output directory
    1109             :       --dump-proto-sig-filter <base_58_enc_sig>
    1110             :         * If enabled, only instructions with the specified transaction signature will be dumped
    1111             :         * Provided signature must be base58-encoded
    1112             :         * Default behavior if signature filter is not provided is to dump EVERY instruction
    1113             :       --dump-proto-output-dir <output_dir>
    1114             :         * Each file represents a single instruction as a serialized InstrContext Protobuf message
    1115             :         * File name format is "instr-<base58_enc_sig>-<instruction_idx>.bin", where instruction_idx is 1-indexed
    1116             : 
    1117             :     solana-conformance (https://github.com/firedancer-io/solana-conformance)
    1118             :       * Allows decoding / debugging of instructions in an isolated environment
    1119             :       * Allows execution result(s) comparison with Solana / Agave
    1120             :       * See solana-conformance/README.md for functionality and use cases
    1121             : */
    1122             : static void
    1123             : dump_instr_to_protobuf( fd_exec_txn_ctx_t *txn_ctx,
    1124             :                         fd_instr_info_t *instr,
    1125           0 :                         ushort instruction_idx ) {
    1126             : 
    1127             : 
    1128           0 :   FD_SCRATCH_SCOPE_BEGIN {
    1129             :     // Get base58-encoded tx signature
    1130           0 :     const fd_ed25519_sig_t * signatures = fd_txn_get_signatures( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
    1131           0 :     fd_ed25519_sig_t signature; fd_memcpy( signature, signatures[0], sizeof(fd_ed25519_sig_t) );
    1132           0 :     char encoded_signature[FD_BASE58_ENCODED_64_SZ];
    1133           0 :     ulong out_size;
    1134           0 :     fd_base58_encode_64( signature, &out_size, encoded_signature );
    1135             : 
    1136           0 :     if (txn_ctx->capture_ctx->dump_proto_sig_filter) {
    1137           0 :       ulong filter_strlen = (ulong) strlen(txn_ctx->capture_ctx->dump_proto_sig_filter);
    1138             : 
    1139             :       // Terminate early if the signature does not match
    1140           0 :       if( memcmp( txn_ctx->capture_ctx->dump_proto_sig_filter, encoded_signature, filter_strlen < out_size ? filter_strlen : out_size ) ) {
    1141           0 :         return;
    1142           0 :       }
    1143           0 :     }
    1144             : 
    1145           0 :     fd_exec_test_instr_context_t instr_context = FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT;
    1146           0 :     fd_create_instr_context_protobuf_from_instructions( &instr_context, txn_ctx, instr );
    1147             : 
    1148             :     /* Output to file */
    1149           0 :     ulong out_buf_size = 100 * 1024 * 1024;
    1150           0 :     uint8_t * out = fd_scratch_alloc( alignof(uchar) , out_buf_size );
    1151           0 :     pb_ostream_t stream = pb_ostream_from_buffer( out, out_buf_size );
    1152           0 :     if (pb_encode(&stream, FD_EXEC_TEST_INSTR_CONTEXT_FIELDS, &instr_context)) {
    1153           0 :       char output_filepath[256]; fd_memset(output_filepath, 0, sizeof(output_filepath));
    1154           0 :       char * position = fd_cstr_init(output_filepath);
    1155           0 :       position = fd_cstr_append_cstr(position, txn_ctx->capture_ctx->dump_proto_output_dir);
    1156           0 :       position = fd_cstr_append_cstr(position, "/instr-");
    1157           0 :       position = fd_cstr_append_cstr(position, encoded_signature);
    1158           0 :       position = fd_cstr_append_cstr(position, "-");
    1159           0 :       position = fd_cstr_append_ushort_as_text(position, '0', 0, instruction_idx, 3); // Assume max 3 digits
    1160           0 :       position = fd_cstr_append_cstr(position, ".bin");
    1161           0 :       fd_cstr_fini(position);
    1162             : 
    1163           0 :       FILE * file = fopen(output_filepath, "wb");
    1164           0 :       if( file ) {
    1165           0 :         fwrite( out, 1, stream.bytes_written, file );
    1166           0 :         fclose( file );
    1167           0 :       }
    1168           0 :     }
    1169           0 :   } FD_SCRATCH_SCOPE_END;
    1170           0 : }
    1171             : 
    1172             : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L319-L357 */
    1173             : static inline int
    1174             : fd_txn_ctx_push( fd_exec_txn_ctx_t * txn_ctx,
    1175       81504 :                  fd_instr_info_t *   instr ) {
    1176             :   /* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
    1177             :      https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L327-L328 */
    1178       81504 :   ulong starting_lamports_h = 0UL;
    1179       81504 :   ulong starting_lamports_l = 0UL;
    1180       81504 :   int err = fd_instr_info_sum_account_lamports( instr, &starting_lamports_h, &starting_lamports_l );
    1181       81504 :   if( FD_UNLIKELY( err ) ) {
    1182           0 :     return err;
    1183           0 :   }
    1184       81504 :   instr->starting_lamports_h = starting_lamports_h;
    1185       81504 :   instr->starting_lamports_l = starting_lamports_l;
    1186             : 
    1187             :   /* Check that the caller's lamport sum has not changed.
    1188             :      https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L329-L340 */
    1189       81504 :   if( txn_ctx->instr_stack_sz>0 ) {
    1190             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L330 */
    1191         519 :     fd_exec_instr_ctx_t const * caller_instruction_context = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz-1 ];
    1192             : 
    1193             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L331-L332 */
    1194         519 :     ulong original_caller_lamport_sum_h = caller_instruction_context->instr->starting_lamports_h;
    1195         519 :     ulong original_caller_lamport_sum_l = caller_instruction_context->instr->starting_lamports_l;
    1196             : 
    1197             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L333-L334 */
    1198         519 :     ulong current_caller_lamport_sum_h = 0UL;
    1199         519 :     ulong current_caller_lamport_sum_l = 0UL;
    1200         519 :     int err = fd_instr_info_sum_account_lamports( caller_instruction_context->instr, &current_caller_lamport_sum_h, &current_caller_lamport_sum_l );
    1201         519 :     if( FD_UNLIKELY( err ) ) {
    1202           0 :       return err;
    1203           0 :     }
    1204             : 
    1205             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L335-L339 */
    1206         519 :     if( FD_UNLIKELY( current_caller_lamport_sum_h!=original_caller_lamport_sum_h || 
    1207         519 :                      current_caller_lamport_sum_l!=original_caller_lamport_sum_l ) ) {
    1208           0 :       return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    1209           0 :     }
    1210         519 :   }
    1211             : 
    1212             :   /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L347-L351 */
    1213       81504 :   if( FD_UNLIKELY( txn_ctx->instr_trace_length>=FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
    1214           0 :     return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
    1215           0 :   }
    1216       81504 :   txn_ctx->instr_trace_length++;
    1217             : 
    1218             :   /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L352-L356 */
    1219       81504 :   if( FD_UNLIKELY( txn_ctx->instr_stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
    1220           0 :     return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
    1221           0 :   }
    1222       81504 :   txn_ctx->instr_stack_sz++;
    1223             : 
    1224       81504 :   return FD_EXECUTOR_INSTR_SUCCESS;
    1225       81504 : }
    1226             : 
    1227             : /* Pushes a new instruction onto the instruction stack and trace. This check loops through all instructions in the current call stack
    1228             :    and checks for reentrancy violations. If successful, simply increments the instruction stack and trace size and returns. It is
    1229             :    the responsibility of the caller to populate the newly pushed instruction fields, which are undefined otherwise.
    1230             : 
    1231             :    https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L246-L290 */
    1232             : int
    1233             : fd_instr_stack_push( fd_exec_txn_ctx_t *     txn_ctx,
    1234       81564 :                      fd_instr_info_t *       instr ) {
    1235             :   /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L253-L255 */
    1236             :   /* Agave looks into the program_indices that was generated in load_transaction_accounts, if the vector has nothing, it throw a UnsupportedProgramId */
    1237       81564 :   if( FD_UNLIKELY( !txn_ctx->has_program_id ) ) {
    1238          57 :     return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
    1239          57 :   }
    1240             :   /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L256-L286 */
    1241       81507 :   if( txn_ctx->instr_stack_sz ) {
    1242             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L261-L285 */
    1243         522 :     uchar contains = 0;
    1244         522 :     uchar is_last  = 0;
    1245             : 
    1246             :     // Checks all previous instructions in the stack for reentrancy
    1247        1197 :     for( uchar level=0; level<txn_ctx->instr_stack_sz; level++ ) {
    1248         675 :       fd_exec_instr_ctx_t * instr_ctx = &txn_ctx->instr_stack[level];
    1249             :       // Optimization: compare program id index instead of pubkey since account keys are unique
    1250         675 :       if( instr->program_id == instr_ctx->instr->program_id ) {
    1251             :         // Reentrancy not allowed unless caller is calling itself
    1252          42 :         if( level == txn_ctx->instr_stack_sz-1 ) {
    1253          39 :           is_last = 1;
    1254          39 :         }
    1255          42 :         contains = 1;
    1256          42 :       }
    1257         675 :     }
    1258             :     /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L282-L285 */
    1259         522 :     if( FD_UNLIKELY( contains && !is_last ) ) {
    1260           3 :       return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
    1261           3 :     }
    1262         522 :   }
    1263             :   /* "Push" a new instruction onto the stack by simply incrementing the stack and trace size counters
    1264             :      https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L289 */
    1265       81504 :   return fd_txn_ctx_push( txn_ctx, instr );
    1266       81507 : }
    1267             : 
    1268             : /* Pops an instruction from the instruction stack. Agave's implementation performs instruction balancing checks every time pop is called,
    1269             :    but error codes returned from `pop` are only used if the program's execution was successful. Therefore, we can optimize our code by only
    1270             :    checking for unbalanced instructions if the program execution was successful within fd_execute_instr.
    1271             : 
    1272             :    https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L293-L298 */
    1273             : int
    1274             : fd_instr_stack_pop( fd_exec_txn_ctx_t *       txn_ctx,
    1275       81504 :                     fd_instr_info_t const *   instr ) {
    1276             :   /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L362-L364 */
    1277       81504 :   if( FD_UNLIKELY( txn_ctx->instr_stack_sz==0 ) ) {
    1278           0 :     return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
    1279           0 :   }
    1280       81504 :   txn_ctx->instr_stack_sz--;
    1281             : 
    1282             :   /* Verify all executable accounts have no outstanding refs
    1283             :      https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L369-L374 */
    1284      525726 :   for( ushort i=0; i<instr->acct_cnt; i++ ) {
    1285      444690 :     if( FD_UNLIKELY( instr->borrowed_accounts[i]->const_meta->info.executable &&
    1286      444690 :                       instr->borrowed_accounts[i]->refcnt_excl ) ) {
    1287         468 :       return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING;
    1288         468 :     }
    1289      444690 :   }
    1290             : 
    1291             :   /* Verify lamports are balanced before and after instruction
    1292             :      https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L366-L380 */
    1293       81036 :   ulong ending_lamports_h = 0UL;
    1294       81036 :   ulong ending_lamports_l = 0UL;
    1295       81036 :   int err = fd_instr_info_sum_account_lamports( instr, &ending_lamports_h, &ending_lamports_l );
    1296       81036 :   if( FD_UNLIKELY( err ) ) {
    1297           0 :     return err;
    1298           0 :   }
    1299       81036 :   if( FD_UNLIKELY( ending_lamports_l != instr->starting_lamports_l || ending_lamports_h != instr->starting_lamports_h ) ) {
    1300         186 :    return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    1301         186 :   }
    1302             : 
    1303       80850 :   return FD_EXECUTOR_INSTR_SUCCESS;;
    1304           0 : }
    1305             : 
    1306             : int
    1307             : fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
    1308       68853 :                   fd_instr_info_t *   instr ) {
    1309       68853 :   FD_RUNTIME_TXN_SPAD_FRAME_BEGIN( txn_ctx->spad, txn_ctx ) {
    1310       68853 :   FD_SCRATCH_SCOPE_BEGIN {
    1311       68853 :     fd_exec_instr_ctx_t * parent = NULL;
    1312       68853 :     if( txn_ctx->instr_stack_sz ) {
    1313         522 :       parent = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
    1314         522 :     }
    1315             : 
    1316       68853 :     int err = fd_instr_stack_push( txn_ctx, instr );
    1317       68853 :     if( FD_UNLIKELY( err ) ) {
    1318          60 :       FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, txn_ctx->instr_err_idx );
    1319          60 :       return err;
    1320          60 :     }
    1321             : 
    1322       68793 :     fd_exec_instr_ctx_t * ctx = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
    1323       68793 :     *ctx = (fd_exec_instr_ctx_t) {
    1324       68793 :       .instr     = instr,
    1325       68793 :       .txn_ctx   = txn_ctx,
    1326       68793 :       .epoch_ctx = txn_ctx->epoch_ctx,
    1327       68793 :       .slot_ctx  = txn_ctx->slot_ctx,
    1328       68793 :       .acc_mgr   = txn_ctx->acc_mgr,
    1329       68793 :       .funk_txn  = txn_ctx->funk_txn,
    1330       68793 :       .parent    = parent,
    1331       68793 :       .index     = parent ? (parent->child_cnt++) : 0,
    1332       68793 :       .depth     = parent ? (parent->depth+1    ) : 0,
    1333       68793 :       .child_cnt = 0U,
    1334       68793 :     };
    1335             : 
    1336       68793 :     txn_ctx->instr_trace[ txn_ctx->instr_trace_length - 1 ] = (fd_exec_instr_trace_entry_t) {
    1337       68793 :       .instr_info = instr,
    1338       68793 :       .stack_height = txn_ctx->instr_stack_sz,
    1339       68793 :     };
    1340             : 
    1341       68793 :     fd_exec_instr_fn_t native_prog_fn = fd_executor_lookup_native_program( &txn_ctx->borrowed_accounts[ instr->program_id ] );
    1342       68793 :     fd_exec_txn_ctx_reset_return_data( txn_ctx );
    1343       68793 :     int exec_result = FD_EXECUTOR_INSTR_SUCCESS;
    1344       68793 :     if( native_prog_fn != NULL ) {
    1345             :       /* Log program invokation (internally caches program_id base58) */
    1346       68712 :       fd_log_collector_program_invoke( ctx );
    1347       68712 :       exec_result = native_prog_fn( ctx );
    1348       68712 :     } else {
    1349          81 :       exec_result = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
    1350          81 :     }
    1351             : 
    1352       68793 :     int stack_pop_err = fd_instr_stack_pop( txn_ctx, instr );
    1353       68793 :     if( FD_LIKELY( exec_result == FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1354             :       /* Log success */
    1355       12969 :       fd_log_collector_program_success( ctx );
    1356             : 
    1357             :       /* Only report the stack pop error on success */
    1358       12969 :       if( FD_UNLIKELY( stack_pop_err ) ) {
    1359           0 :         FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, stack_pop_err, txn_ctx->instr_err_idx );
    1360           0 :         return stack_pop_err;
    1361           0 :       }
    1362       55824 :     } else {
    1363             :       /* if txn_ctx->exec_err is not set, it indicates an instruction error */
    1364       55824 :       if( !txn_ctx->exec_err ) {
    1365       55713 :         FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, exec_result, txn_ctx->instr_err_idx );
    1366       55713 :       }
    1367             : 
    1368       55824 :       if( !txn_ctx->failed_instr ) {
    1369       55815 :         txn_ctx->failed_instr = ctx;
    1370       55815 :         ctx->instr_err        = (uint)( -exec_result - 1 );
    1371       55815 :       }
    1372             : 
    1373             :       /* Log failure cases.
    1374             :          We assume that the correct type of error is stored in ctx.
    1375             :          Syscalls are expected to log when the error is generated, while
    1376             :          native programs will be logged here.
    1377             :          (This is because syscall errors often carry data with them.) */
    1378       55824 :       fd_log_collector_program_failure( ctx );
    1379       55824 :     }
    1380             : 
    1381             : #ifdef VLOG
    1382             :   if ( FD_UNLIKELY( exec_result != FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1383             :     FD_LOG_WARNING(( "instruction executed unsuccessfully: error code %d, custom err: %d, program id: %s", exec_result, txn_ctx->custom_err, FD_BASE58_ENC_32_ALLOCA( instr->program_id_pubkey.uc ));
    1384             :   } else {
    1385             :     FD_LOG_WARNING(( "instruction executed successfully: error code %d, custom err: %d, program id: %s", exec_result, txn_ctx->custom_err, FD_BASE58_ENC_32_ALLOCA( instr->program_id_pubkey.uc ));
    1386             :   }
    1387             : #endif
    1388             : 
    1389       68793 :     return exec_result;
    1390       68853 :   } FD_SCRATCH_SCOPE_END;
    1391       68853 :   } FD_RUNTIME_TXN_SPAD_FRAME_END;
    1392       68853 : }
    1393             : 
    1394             : void
    1395           0 : fd_txn_reclaim_accounts( fd_exec_txn_ctx_t * txn_ctx ) {
    1396           0 :   for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
    1397           0 :     fd_borrowed_account_t * acc_rec = &txn_ctx->borrowed_accounts[i];
    1398             : 
    1399             :     /* An account writable iff it is writable AND it is not being demoted.
    1400             :         If this criteria is not met, the account should not be marked as touched
    1401             :         via updating its most recent slot. */
    1402           0 :     if( !fd_txn_account_is_writable_idx( txn_ctx, (int)i ) ) {
    1403           0 :       continue;
    1404           0 :     }
    1405             : 
    1406           0 :     acc_rec->meta->slot = txn_ctx->slot_ctx->slot_bank.slot;
    1407             : 
    1408           0 :     if( acc_rec->meta->info.lamports == 0 ) {
    1409           0 :       acc_rec->meta->dlen = 0;
    1410           0 :       memset( acc_rec->meta->info.owner, 0, sizeof(fd_pubkey_t) );
    1411           0 :     }
    1412           0 :   }
    1413           0 : }
    1414             : 
    1415             : int
    1416             : fd_executor_is_blockhash_valid_for_age( fd_block_hash_queue_t const * block_hash_queue,
    1417             :                                         fd_hash_t const *             blockhash,
    1418        7971 :                                         ulong                         max_age ) {
    1419        7971 :   fd_hash_hash_age_pair_t_mapnode_t key;
    1420        7971 :   fd_memcpy( key.elem.key.uc, blockhash, sizeof(fd_hash_t) );
    1421             : 
    1422        7971 :   fd_hash_hash_age_pair_t_mapnode_t * hash_age = fd_hash_hash_age_pair_t_map_find( block_hash_queue->ages_pool, block_hash_queue->ages_root, &key );
    1423        7971 :   if( hash_age==NULL ) {
    1424             :     #ifdef VLOG
    1425             :     FD_LOG_WARNING(( "txn with missing recent blockhash - blockhash: %s", FD_BASE58_ENC_32_ALLOCA( blockhash->uc ) ));
    1426             :     #endif
    1427        2535 :     return 0;
    1428        2535 :   }
    1429        5436 :   ulong age = block_hash_queue->last_hash_index-hash_age->elem.val.hash_index;
    1430             : #ifdef VLOG
    1431             :   if( age>max_age ) {
    1432             :     FD_LOG_WARNING(( "txn with old blockhash - age: %lu, blockhash: %s", age, FD_BASE58_ENC_32_ALLOCA( hash_age->elem.key.uc ) ));
    1433             :   }
    1434             : #endif
    1435        5436 :   return ( age<=max_age );
    1436        7971 : }
    1437             : 
    1438             : void
    1439       12477 : fd_executor_setup_borrowed_accounts_for_txn( fd_exec_txn_ctx_t * txn_ctx ) {
    1440       12477 :   ulong j = 0;
    1441       77139 :   for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
    1442       64662 :     FD_SCRATCH_SCOPE_BEGIN {
    1443             : 
    1444       64662 :     fd_pubkey_t * acc = &txn_ctx->accounts[i];
    1445       64662 :     txn_ctx->nonce_accounts[i] = 0;
    1446             : 
    1447       64662 :     fd_borrowed_account_t * borrowed_account = fd_borrowed_account_init( &txn_ctx->borrowed_accounts[i] );
    1448       64662 :     int                     err              = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, acc, borrowed_account );
    1449       64662 :     if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
    1450           0 :       FD_LOG_ERR(( "fd_acc_mgr_view err=%d", err ));
    1451           0 :     }
    1452       64662 :     uchar is_unknown_account = err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT;
    1453       64662 :     memcpy( borrowed_account->pubkey->key, acc, sizeof(fd_pubkey_t) );
    1454       64662 :     if ( FD_UNLIKELY( is_unknown_account ) ) {
    1455       10239 :       borrowed_account->account_found = 0;
    1456       10239 :     }
    1457             : 
    1458             :     /* Create a borrowed account for all writable accounts and the fee payer
    1459             :        account which is almost always writable, but doesn't have to be.
    1460             : 
    1461             :        TODO: The borrowed account semantics should better match Agave's. */
    1462       64662 :     if( fd_txn_account_is_writable_idx( txn_ctx, (int)i ) || i==FD_FEE_PAYER_TXN_IDX ) {
    1463       33207 :       void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
    1464       33207 :       fd_borrowed_account_make_modifiable( borrowed_account, borrowed_account_data );
    1465             : 
    1466             :       /* All new accounts should have their rent epoch set to ULONG_MAX.
    1467             :          https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L485-L497 */
    1468       33207 :       if( is_unknown_account
    1469       33207 :           || ( i>0UL && fd_should_set_exempt_rent_epoch_max( txn_ctx->slot_ctx, borrowed_account ) ) ) {
    1470        9345 :         borrowed_account->meta->info.rent_epoch = ULONG_MAX;
    1471        9345 :       }
    1472       33207 :     }
    1473             : 
    1474       64662 :     fd_account_meta_t const * meta = borrowed_account->const_meta ? borrowed_account->const_meta : borrowed_account->meta;
    1475       64662 :     if( meta==NULL ) {
    1476        4296 :       static const fd_account_meta_t sentinel = { .magic = FD_ACCOUNT_META_MAGIC, .info = { .rent_epoch = ULONG_MAX } };
    1477        4296 :       borrowed_account->const_meta        = &sentinel;
    1478        4296 :       borrowed_account->starting_lamports = 0UL;
    1479        4296 :       borrowed_account->starting_dlen     = 0UL;
    1480        4296 :       continue;
    1481        4296 :     }
    1482             : 
    1483       60366 :     if( meta->info.executable ) {
    1484       26475 :       FD_BORROWED_ACCOUNT_DECL(owner_borrowed_account);
    1485       26475 :       int err = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, (fd_pubkey_t *)meta->info.owner, owner_borrowed_account );
    1486       26475 :       if( FD_UNLIKELY( err ) ) {
    1487       17583 :         borrowed_account->starting_owner_dlen = 0UL;
    1488       17583 :       } else {
    1489        8892 :         borrowed_account->starting_owner_dlen = owner_borrowed_account->const_meta->dlen;
    1490        8892 :       }
    1491       26475 :     }
    1492             : 
    1493       60366 :     if( FD_UNLIKELY( memcmp( meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
    1494        4641 :       fd_bpf_upgradeable_loader_state_t program_loader_state = {0};
    1495        4641 :       int err = 0;
    1496        4641 :       if( FD_UNLIKELY( !read_bpf_upgradeable_loader_state_for_program( txn_ctx, (uchar) i, &program_loader_state, &err ) ) ) {
    1497        1503 :         continue;
    1498        1503 :       }
    1499             : 
    1500        3138 :       if( !fd_bpf_upgradeable_loader_state_is_program( &program_loader_state ) ) {
    1501        1467 :         continue;
    1502        1467 :       }
    1503             : 
    1504        1671 :       fd_pubkey_t * programdata_acc = &program_loader_state.inner.program.programdata_address;
    1505        1671 :       fd_borrowed_account_t * executable_account = fd_borrowed_account_init( &txn_ctx->executable_accounts[j] );
    1506        1671 :       fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, programdata_acc, executable_account);
    1507        1671 :       j++;
    1508        1671 :     }
    1509             : 
    1510       64662 :     } FD_SCRATCH_SCOPE_END;
    1511       64662 :   }
    1512       12477 :   txn_ctx->executable_cnt = j;
    1513       12477 : }
    1514             : 
    1515             : /* Stuff to be done before multithreading can begin */
    1516             : int
    1517             : fd_execute_txn_prepare_start( fd_exec_slot_ctx_t *  slot_ctx,
    1518             :                               fd_exec_txn_ctx_t *   txn_ctx,
    1519             :                               fd_txn_t const *      txn_descriptor,
    1520       12741 :                               fd_rawtxn_b_t const * txn_raw ) {
    1521             :   /* Init txn ctx */
    1522       12741 :   fd_exec_txn_ctx_new( txn_ctx );
    1523       12741 :   fd_exec_txn_ctx_from_exec_slot_ctx( slot_ctx, txn_ctx );
    1524       12741 :   fd_exec_txn_ctx_setup( txn_ctx, txn_descriptor, txn_raw );
    1525             : 
    1526             :   /* Unroll accounts from aluts and place into correct spots */
    1527       12741 :   int res = fd_executor_setup_accessed_accounts_for_txn( txn_ctx );
    1528             : 
    1529       12741 :   return res;
    1530             :   /* TODO:FIXME: MOVE THIS PELASE */
    1531       12741 : }
    1532             : 
    1533             : int
    1534           0 : fd_executor_txn_verify( fd_exec_txn_ctx_t * txn_ctx ) {
    1535           0 :   FD_SCRATCH_SCOPE_BEGIN {
    1536           0 :     fd_sha512_t * shas[ FD_TXN_ACTUAL_SIG_MAX ];
    1537           0 :     for ( ulong i=0; i<FD_TXN_ACTUAL_SIG_MAX; i++ ) {
    1538           0 :       fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( fd_scratch_alloc( alignof( fd_sha512_t ), sizeof( fd_sha512_t ) ) ) );
    1539           0 :       if( FD_UNLIKELY( !sha ) ) FD_LOG_ERR(( "fd_sha512_join failed" ));
    1540           0 :       shas[i] = sha;
    1541           0 :     }
    1542             : 
    1543           0 :     uchar  signature_cnt = txn_ctx->txn_descriptor->signature_cnt;
    1544           0 :     ushort signature_off = txn_ctx->txn_descriptor->signature_off;
    1545           0 :     ushort acct_addr_off = txn_ctx->txn_descriptor->acct_addr_off;
    1546           0 :     ushort message_off   = txn_ctx->txn_descriptor->message_off;
    1547             : 
    1548           0 :     uchar const * signatures = (uchar *)txn_ctx->_txn_raw->raw + signature_off;
    1549           0 :     uchar const * pubkeys = (uchar *)txn_ctx->_txn_raw->raw + acct_addr_off;
    1550           0 :     uchar const * msg = (uchar *)txn_ctx->_txn_raw->raw + message_off;
    1551           0 :     ulong msg_sz = (ulong)txn_ctx->_txn_raw->txn_sz - message_off;
    1552             : 
    1553             :     /* Verify signatures */
    1554           0 :     int res = fd_ed25519_verify_batch_single_msg( msg, msg_sz, signatures, pubkeys, shas, signature_cnt );
    1555           0 :     if( FD_UNLIKELY( res != FD_ED25519_SUCCESS ) ) {
    1556           0 :       return -1;
    1557           0 :     }
    1558             : 
    1559           0 :     return 0;
    1560           0 :   } FD_SCRATCH_SCOPE_END;
    1561           0 : }
    1562             : 
    1563             : /* Creates a TxnContext Protobuf message from a provided txn_ctx.
    1564             :    - The transaction is assumed to have just finished phase 1 of preparation
    1565             :    - Caller of this function should have a scratch frame ready
    1566             : */
    1567             : static void
    1568             : create_txn_context_protobuf_from_txn( fd_exec_test_txn_context_t * txn_context_msg,
    1569             :                                       fd_exec_txn_ctx_t *          txn_ctx,
    1570           0 :                                       fd_spad_t *                  spad ) {
    1571           0 :   fd_txn_t const * txn_descriptor = txn_ctx->txn_descriptor;
    1572           0 :   uchar const * txn_payload = (uchar const *) txn_ctx->_txn_raw->raw;
    1573           0 :   fd_exec_slot_ctx_t const * slot_ctx = txn_ctx->slot_ctx;
    1574             : 
    1575             :   /* We don't want to store builtins in account shared data */
    1576           0 :   fd_pubkey_t const loaded_builtins[] = {
    1577           0 :     fd_solana_system_program_id,
    1578           0 :     fd_solana_vote_program_id,
    1579           0 :     fd_solana_stake_program_id,
    1580           0 :     fd_solana_config_program_id,
    1581             :     // fd_solana_zk_token_proof_program_id,
    1582           0 :     fd_solana_bpf_loader_v4_program_id,
    1583           0 :     fd_solana_address_lookup_table_program_id,
    1584           0 :     fd_solana_bpf_loader_deprecated_program_id,
    1585           0 :     fd_solana_bpf_loader_program_id,
    1586           0 :     fd_solana_bpf_loader_upgradeable_program_id,
    1587           0 :     fd_solana_compute_budget_program_id,
    1588           0 :     fd_solana_keccak_secp_256k_program_id,
    1589           0 :     fd_solana_secp256r1_program_id,
    1590           0 :     fd_solana_zk_elgamal_proof_program_id,
    1591           0 :     fd_solana_ed25519_sig_verify_program_id,
    1592           0 :     fd_solana_spl_native_mint_id,
    1593           0 :   };
    1594           0 :   const ulong num_loaded_builtins = (sizeof(loaded_builtins) / sizeof(fd_pubkey_t));
    1595             : 
    1596             :   /* Prepare sysvar cache accounts */
    1597           0 :   fd_pubkey_t const fd_relevant_sysvar_ids[] = {
    1598           0 :     fd_sysvar_clock_id,
    1599           0 :     fd_sysvar_epoch_schedule_id,
    1600           0 :     fd_sysvar_epoch_rewards_id,
    1601           0 :     fd_sysvar_fees_id,
    1602           0 :     fd_sysvar_rent_id,
    1603           0 :     fd_sysvar_slot_hashes_id,
    1604           0 :     fd_sysvar_recent_block_hashes_id,
    1605           0 :     fd_sysvar_stake_history_id,
    1606           0 :     fd_sysvar_last_restart_slot_id,
    1607           0 :     fd_sysvar_instructions_id,
    1608           0 :   };
    1609           0 :   const ulong num_sysvar_entries = (sizeof(fd_relevant_sysvar_ids) / sizeof(fd_pubkey_t));
    1610             : 
    1611             :   /* Transaction Context -> tx */
    1612           0 :   txn_context_msg->has_tx = true;
    1613           0 :   fd_exec_test_sanitized_transaction_t * sanitized_transaction = &txn_context_msg->tx;
    1614             : 
    1615             :   /* Transaction Context -> tx -> message */
    1616           0 :   sanitized_transaction->has_message = true;
    1617           0 :   fd_exec_test_transaction_message_t * message = &sanitized_transaction->message;
    1618             : 
    1619             :   /* Transaction Context -> tx -> message -> is_legacy */
    1620           0 :   message->is_legacy = txn_descriptor->transaction_version == FD_TXN_VLEGACY;
    1621             : 
    1622             :   /* Transaction Context -> tx -> message -> header */
    1623           0 :   message->has_header = true;
    1624           0 :   fd_exec_test_message_header_t * header = &message->header;
    1625             : 
    1626             :   /* Transaction Context -> tx -> message -> header -> num_required_signatures */
    1627           0 :   header->num_required_signatures = txn_descriptor->signature_cnt;
    1628             : 
    1629             :   /* Transaction Context -> tx -> message -> header -> num_readonly_signed_accounts */
    1630           0 :   header->num_readonly_signed_accounts = txn_descriptor->readonly_signed_cnt;
    1631             : 
    1632             :   /* Transaction Context -> tx -> message -> header -> num_readonly_unsigned_accounts */
    1633           0 :   header->num_readonly_unsigned_accounts = txn_descriptor->readonly_unsigned_cnt;
    1634             : 
    1635             :   /* Transaction Context -> tx -> message -> account_keys */
    1636           0 :   message->account_keys_count = txn_descriptor->acct_addr_cnt;
    1637           0 :   message->account_keys = fd_scratch_alloc( alignof(pb_bytes_array_t *), PB_BYTES_ARRAY_T_ALLOCSIZE(txn_descriptor->acct_addr_cnt * sizeof(pb_bytes_array_t *)) );
    1638           0 :   fd_acct_addr_t const * account_keys = fd_txn_get_acct_addrs( txn_descriptor, txn_payload );
    1639           0 :   for( ulong i = 0; i < txn_descriptor->acct_addr_cnt; i++ ) {
    1640           0 :     pb_bytes_array_t * account_key = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_pubkey_t)) );
    1641           0 :     account_key->size = sizeof(fd_pubkey_t);
    1642           0 :     memcpy( account_key->bytes, &account_keys[i], sizeof(fd_pubkey_t) );
    1643           0 :     message->account_keys[i] = account_key;
    1644           0 :   }
    1645             : 
    1646             :   /* Transaction Context -> tx -> message -> account_shared_data
    1647             :      Contains:
    1648             :       - Account data for regular accounts
    1649             :       - Account data for LUT accounts
    1650             :       - Account data for executable accounts
    1651             :       - Account data for (almost) all sysvars
    1652             :   */
    1653             :   // Dump regular accounts first
    1654           0 :   message->account_shared_data_count = 0;
    1655           0 :   message->account_shared_data = fd_scratch_alloc( alignof(fd_exec_test_acct_state_t),
    1656           0 :                                                    (txn_ctx->accounts_cnt * 2 + txn_descriptor->addr_table_lookup_cnt + num_sysvar_entries) * sizeof(fd_exec_test_acct_state_t) );
    1657           0 :   for( ulong i = 0; i < txn_ctx->accounts_cnt; ++i ) {
    1658           0 :     FD_BORROWED_ACCOUNT_DECL(borrowed_account);
    1659           0 :     int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->accounts[i], borrowed_account );
    1660           0 :     if( FD_UNLIKELY(ret != FD_ACC_MGR_SUCCESS) ) {
    1661           0 :       continue;
    1662           0 :     }
    1663             : 
    1664             :     // Make sure account is not a builtin
    1665           0 :     bool is_builtin = false;
    1666           0 :     for( ulong j = 0; j < num_loaded_builtins; ++j ) {
    1667           0 :       if( 0 == memcmp( &txn_ctx->accounts[i], &loaded_builtins[j], sizeof(fd_pubkey_t) ) ) {
    1668           0 :         is_builtin = true;
    1669           0 :         break;
    1670           0 :       }
    1671           0 :     }
    1672           0 :     if( !is_builtin ) {
    1673           0 :       dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
    1674           0 :     }
    1675           0 :   }
    1676             : 
    1677             :   // For executable accounts, we need to set up dummy borrowed accounts by cluttering txn ctx state and resetting it after
    1678             :   // TODO: Revisit this hacky approach
    1679           0 :   txn_ctx->spad = spad;
    1680           0 :   fd_spad_push( txn_ctx->spad );
    1681           0 :   txn_ctx->funk_txn = slot_ctx->funk_txn;
    1682           0 :   fd_executor_setup_borrowed_accounts_for_txn( txn_ctx );
    1683             : 
    1684             :   // Dump executable accounts
    1685           0 :   for( ulong i = 0; i < txn_ctx->executable_cnt; ++i ) {
    1686           0 :     if( !txn_ctx->executable_accounts[i].const_meta ) {
    1687           0 :       continue;
    1688           0 :     }
    1689           0 :     dump_account_state( &txn_ctx->executable_accounts[i], &message->account_shared_data[message->account_shared_data_count++] );
    1690           0 :   }
    1691             : 
    1692             :   // Reset state
    1693           0 :   txn_ctx->funk_txn = NULL;
    1694           0 :   txn_ctx->executable_cnt = 0;
    1695           0 :   fd_spad_pop( txn_ctx->spad );
    1696             : 
    1697             :   // Dump LUT accounts
    1698           0 :   fd_txn_acct_addr_lut_t const * address_lookup_tables = fd_txn_get_address_tables_const( txn_descriptor );
    1699           0 :   for( ulong i = 0; i < txn_descriptor->addr_table_lookup_cnt; ++i ) {
    1700           0 :     FD_BORROWED_ACCOUNT_DECL(borrowed_account);
    1701           0 :     fd_pubkey_t * alut_key = (fd_pubkey_t *) (txn_payload + address_lookup_tables[i].addr_off);
    1702           0 :     int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, alut_key, borrowed_account );
    1703           0 :     if( FD_UNLIKELY(ret != FD_ACC_MGR_SUCCESS) ) {
    1704           0 :       continue;
    1705           0 :     }
    1706           0 :     dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
    1707           0 :   }
    1708             : 
    1709             :   // Dump sysvars
    1710           0 :   for( ulong i = 0; i < num_sysvar_entries; i++ ) {
    1711           0 :     FD_BORROWED_ACCOUNT_DECL(borrowed_account);
    1712           0 :     int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &fd_relevant_sysvar_ids[i], borrowed_account );
    1713           0 :     if( ret != FD_ACC_MGR_SUCCESS ) {
    1714           0 :       continue;
    1715           0 :     }
    1716             : 
    1717             :     // Make sure the account doesn't exist in the output accounts yet
    1718           0 :     int account_exists = 0;
    1719           0 :     for( ulong j = 0; j < txn_ctx->accounts_cnt; j++ ) {
    1720           0 :       if ( 0 == memcmp( txn_ctx->accounts[j].key, fd_relevant_sysvar_ids[i].uc, sizeof(fd_pubkey_t) ) ) {
    1721           0 :         account_exists = true;
    1722           0 :         break;
    1723           0 :       }
    1724           0 :     }
    1725             :     // Copy it into output
    1726           0 :     if (!account_exists) {
    1727           0 :       dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
    1728           0 :     }
    1729           0 :   }
    1730             : 
    1731             :   /* Transaction Context -> tx -> message -> recent_blockhash */
    1732           0 :   uchar const * recent_blockhash = fd_txn_get_recent_blockhash( txn_descriptor, txn_payload );
    1733           0 :   message->recent_blockhash = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_hash_t)) );
    1734           0 :   message->recent_blockhash->size = sizeof(fd_hash_t);
    1735           0 :   memcpy( message->recent_blockhash->bytes, recent_blockhash, sizeof(fd_hash_t) );
    1736             : 
    1737             :   /* Transaction Context -> tx -> message -> instructions */
    1738           0 :   message->instructions_count = txn_descriptor->instr_cnt;
    1739           0 :   message->instructions = fd_scratch_alloc( alignof(fd_exec_test_compiled_instruction_t), txn_descriptor->instr_cnt * sizeof(fd_exec_test_compiled_instruction_t) );
    1740           0 :   for( ulong i = 0; i < txn_descriptor->instr_cnt; ++i ) {
    1741           0 :     fd_txn_instr_t instr = txn_descriptor->instr[i];
    1742           0 :     fd_exec_test_compiled_instruction_t * compiled_instruction = &message->instructions[i];
    1743             : 
    1744             :     // compiled instruction -> program_id_index
    1745           0 :     compiled_instruction->program_id_index = instr.program_id;
    1746             : 
    1747             :     // compiled instruction -> accounts
    1748           0 :     compiled_instruction->accounts_count = instr.acct_cnt;
    1749           0 :     compiled_instruction->accounts = fd_scratch_alloc( alignof(uint32_t), instr.acct_cnt * sizeof(uint32_t) );
    1750           0 :     uchar const * instr_accounts = fd_txn_get_instr_accts( &instr, txn_payload );
    1751           0 :     for( ulong j = 0; j < instr.acct_cnt; ++j ) {
    1752           0 :       uchar instr_acct_index = instr_accounts[j];
    1753           0 :       compiled_instruction->accounts[j] = instr_acct_index;
    1754           0 :     }
    1755             : 
    1756             :     // compiled instruction -> data
    1757           0 :     uchar const * instr_data = fd_txn_get_instr_data( &instr, txn_payload );
    1758           0 :     compiled_instruction->data = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(instr.data_sz) );
    1759           0 :     compiled_instruction->data->size = instr.data_sz;
    1760           0 :     memcpy( compiled_instruction->data->bytes, instr_data, instr.data_sz );
    1761           0 :   }
    1762             : 
    1763             :   /* ALUT stuff (non-legacy) */
    1764           0 :   message->address_table_lookups_count = 0;
    1765           0 :   if( !message->is_legacy ) {
    1766             :     /* Transaction Context -> tx -> message -> address_table_lookups */
    1767           0 :     message->address_table_lookups_count = txn_descriptor->addr_table_lookup_cnt;
    1768           0 :     message->address_table_lookups = fd_scratch_alloc( alignof(fd_exec_test_message_address_table_lookup_t),
    1769           0 :                                                        txn_descriptor->addr_table_lookup_cnt * sizeof(fd_exec_test_message_address_table_lookup_t) );
    1770           0 :     for( ulong i = 0; i < txn_descriptor->addr_table_lookup_cnt; ++i ) {
    1771             :       // alut -> account_key
    1772           0 :       fd_pubkey_t * alut_key = (fd_pubkey_t *) (txn_payload + address_lookup_tables[i].addr_off);
    1773           0 :       memcpy( message->address_table_lookups[i].account_key, alut_key, sizeof(fd_pubkey_t) );
    1774             : 
    1775             :       // Access ALUT account data to access its keys
    1776           0 :       FD_BORROWED_ACCOUNT_DECL(addr_lut_rec);
    1777           0 :       int err = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, alut_key, addr_lut_rec);
    1778           0 :       if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
    1779           0 :         FD_LOG_ERR(( "addr lut not found" ));
    1780           0 :       }
    1781             : 
    1782             :       // alut -> writable_indexes
    1783           0 :       message->address_table_lookups[i].writable_indexes_count = address_lookup_tables[i].writable_cnt;
    1784           0 :       message->address_table_lookups[i].writable_indexes = fd_scratch_alloc( alignof(uint32_t), address_lookup_tables[i].writable_cnt * sizeof(uint32_t) );
    1785           0 :       uchar * writable_indexes = (uchar *) (txn_payload + address_lookup_tables[i].writable_off);
    1786           0 :       for( ulong j = 0; j < address_lookup_tables[i].writable_cnt; ++j ) {
    1787           0 :         message->address_table_lookups[i].writable_indexes[j] = writable_indexes[j];
    1788           0 :       }
    1789             : 
    1790             :       // alut -> readonly_indexes
    1791           0 :       message->address_table_lookups[i].readonly_indexes_count = address_lookup_tables[i].readonly_cnt;
    1792           0 :       message->address_table_lookups[i].readonly_indexes = fd_scratch_alloc( alignof(uint32_t), address_lookup_tables[i].readonly_cnt * sizeof(uint32_t) );
    1793           0 :       uchar * readonly_indexes = (uchar *) (txn_payload + address_lookup_tables[i].readonly_off);
    1794           0 :       for( ulong j = 0; j < address_lookup_tables[i].readonly_cnt; ++j ) {
    1795           0 :         message->address_table_lookups[i].readonly_indexes[j] = readonly_indexes[j];
    1796           0 :       }
    1797           0 :     }
    1798           0 :   }
    1799             : 
    1800             :   /* Transaction Context -> tx -> message_hash */
    1801             :   // Skip because it does not matter what's in here
    1802             : 
    1803             :   /* Transaction Context -> tx -> is_simple_vote_tx */
    1804             :   // Doesn't matter for FD, but does for Agave
    1805             :   // https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/sdk/src/simple_vote_transaction_checker.rs#L5-L9
    1806           0 :   sanitized_transaction->is_simple_vote_tx = ( txn_descriptor->signature_cnt < 3 )
    1807           0 :                                           && ( message->is_legacy )
    1808           0 :                                           && ( txn_descriptor->instr_cnt == 1 )
    1809           0 :                                           && ( 0 == memcmp( &txn_ctx->accounts[txn_descriptor->instr[0].program_id], fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) );
    1810             : 
    1811             :   /* Transaction Context -> tx -> signatures */
    1812           0 :   sanitized_transaction->signatures_count = txn_descriptor->signature_cnt;
    1813           0 :   sanitized_transaction->signatures = fd_scratch_alloc( alignof(pb_bytes_array_t *), PB_BYTES_ARRAY_T_ALLOCSIZE(txn_descriptor->signature_cnt * sizeof(pb_bytes_array_t *)) );
    1814           0 :   fd_ed25519_sig_t const * signatures = fd_txn_get_signatures( txn_descriptor, txn_payload );
    1815           0 :   for( uchar i = 0; i < txn_descriptor->signature_cnt; ++i ) {
    1816           0 :     pb_bytes_array_t * signature = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_ed25519_sig_t)) );
    1817           0 :     signature->size = sizeof(fd_ed25519_sig_t);
    1818           0 :     memcpy( signature->bytes, &signatures[i], sizeof(fd_ed25519_sig_t) );
    1819           0 :     sanitized_transaction->signatures[i] = signature;
    1820           0 :   }
    1821             : 
    1822             :   /* Transaction Context -> blockhash_queue
    1823             :      NOTE: Agave's implementation of register_hash incorrectly allows the blockhash queue to hold max_age + 1 (max 301)
    1824             :      entries. We have this incorrect logic implemented in fd_sysvar_recent_hashes:register_blockhash and it's not a
    1825             :      huge issue, but something to keep in mind. */
    1826           0 :   pb_bytes_array_t ** output_blockhash_queue = fd_scratch_alloc(
    1827           0 :                                                       alignof(pb_bytes_array_t *),
    1828           0 :                                                       PB_BYTES_ARRAY_T_ALLOCSIZE((FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1) * sizeof(pb_bytes_array_t *)) );
    1829           0 :   txn_context_msg->blockhash_queue_count = 0;
    1830           0 :   txn_context_msg->blockhash_queue = output_blockhash_queue;
    1831             : 
    1832             :   // Iterate over all block hashes in the queue and save them
    1833           0 :   fd_block_hash_queue_t const * queue = &slot_ctx->slot_bank.block_hash_queue;
    1834           0 :   fd_hash_hash_age_pair_t_mapnode_t * nn;
    1835           0 :   for ( fd_hash_hash_age_pair_t_mapnode_t * n = fd_hash_hash_age_pair_t_map_minimum( queue->ages_pool, queue->ages_root ); n; n = nn ) {
    1836           0 :     nn = fd_hash_hash_age_pair_t_map_successor( queue->ages_pool, n );
    1837             : 
    1838             :     /* Get the index in the blockhash queue
    1839             :        - Lower index = newer
    1840             :        - 0 will be the most recent blockhash
    1841             :        - Index range is [0, max_age] (not a typo) */
    1842           0 :     ulong queue_index = queue->last_hash_index - n->elem.val.hash_index;
    1843           0 :     fd_hash_t blockhash = n->elem.key;
    1844             : 
    1845             :     // Write the blockhash to the correct index (note we write in reverse order since in the Protobuf message, the oldest blockhash goes first)
    1846           0 :     pb_bytes_array_t * output_blockhash = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_hash_t)) );
    1847           0 :     output_blockhash->size = sizeof(fd_hash_t);
    1848           0 :     memcpy( output_blockhash->bytes, &blockhash, sizeof(fd_hash_t) );
    1849           0 :     output_blockhash_queue[FD_BLOCKHASH_QUEUE_MAX_ENTRIES - queue_index] = output_blockhash;
    1850           0 :     txn_context_msg->blockhash_queue_count++;
    1851           0 :   }
    1852             : 
    1853             :   // Shift blockhash queue elements if num elements < 301
    1854           0 :   if( txn_context_msg->blockhash_queue_count < FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1 ) {
    1855           0 :     ulong index_offset = FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1 - txn_context_msg->blockhash_queue_count;
    1856           0 :     for( ulong i = 0; i < txn_context_msg->blockhash_queue_count; i++ ) {
    1857           0 :       output_blockhash_queue[i] = output_blockhash_queue[i + index_offset];
    1858           0 :     }
    1859           0 :   }
    1860             : 
    1861             :   /* Transaction Context -> epoch_ctx */
    1862           0 :   txn_context_msg->has_epoch_ctx = true;
    1863           0 :   txn_context_msg->epoch_ctx.has_features = true;
    1864           0 :   dump_sorted_features( &txn_ctx->epoch_ctx->features, &txn_context_msg->epoch_ctx.features );
    1865             : 
    1866             :   /* Transaction Context -> slot_ctx */
    1867           0 :   txn_context_msg->has_slot_ctx  = true;
    1868           0 :   txn_context_msg->slot_ctx.slot = slot_ctx->slot_bank.slot;
    1869           0 : }
    1870             : 
    1871             : /*  Similar to dump_instr_to_protobuf, but dumps individual transactions from a ledger replay.
    1872             : 
    1873             :     This is more reliable for BPF program invocations since solfuzz-agave's transaction replay harness
    1874             :     is more robust.
    1875             : 
    1876             :     The following arguments can be added when replaying ledger transactions:
    1877             :       --dump-txn-to-pb <0/1>
    1878             :         * If enabled, transactions will be dumped to the specified output directory
    1879             :       --dump-proto-sig-filter <base_58_enc_sig>
    1880             :         * If enabled, only transactions with the specified signature will be dumped
    1881             :         * Provided signature must be base58-encoded
    1882             :         * Default behavior if signature filter is not provided is to dump EVERY transaction
    1883             :       --dump-proto-output-dir <output_dir>
    1884             :         * Each file represents a single transaction as a serialized TxnContext Protobuf message
    1885             :         * File name format is "txn-<base58_enc_sig>.bin"
    1886             : */
    1887             : void
    1888           0 : fd_dump_txn_to_protobuf( fd_exec_txn_ctx_t *txn_ctx, fd_spad_t * spad ) {
    1889           0 :   FD_SCRATCH_SCOPE_BEGIN {
    1890             :     // Get base58-encoded tx signature
    1891           0 :     const fd_ed25519_sig_t * signatures = fd_txn_get_signatures( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
    1892           0 :     fd_ed25519_sig_t signature; fd_memcpy( signature, signatures[0], sizeof(fd_ed25519_sig_t) );
    1893           0 :     char encoded_signature[FD_BASE58_ENCODED_64_SZ];
    1894           0 :     ulong out_size;
    1895           0 :     fd_base58_encode_64( signature, &out_size, encoded_signature );
    1896             : 
    1897           0 :     if( txn_ctx->capture_ctx->dump_proto_sig_filter ) {
    1898             :       // Terminate early if the signature does not match
    1899           0 :       if( strcmp( txn_ctx->capture_ctx->dump_proto_sig_filter, encoded_signature ) ) {
    1900           0 :         return;
    1901           0 :       }
    1902           0 :     }
    1903             : 
    1904           0 :     fd_exec_test_txn_context_t txn_context_msg = FD_EXEC_TEST_TXN_CONTEXT_INIT_DEFAULT;
    1905           0 :     create_txn_context_protobuf_from_txn( &txn_context_msg, txn_ctx, spad );
    1906             : 
    1907             :     /* Output to file */
    1908           0 :     ulong out_buf_size = 100 * 1024 * 1024;
    1909           0 :     uint8_t * out = fd_scratch_alloc( alignof(uint8_t), out_buf_size );
    1910           0 :     pb_ostream_t stream = pb_ostream_from_buffer( out, out_buf_size );
    1911           0 :     if( pb_encode( &stream, FD_EXEC_TEST_TXN_CONTEXT_FIELDS, &txn_context_msg ) ) {
    1912           0 :       char output_filepath[256]; fd_memset( output_filepath, 0, sizeof(output_filepath) );
    1913           0 :       char * position = fd_cstr_init( output_filepath );
    1914           0 :       position = fd_cstr_append_cstr( position, txn_ctx->capture_ctx->dump_proto_output_dir );
    1915           0 :       position = fd_cstr_append_cstr( position, "/txn-" );
    1916           0 :       position = fd_cstr_append_cstr( position, encoded_signature );
    1917           0 :       position = fd_cstr_append_cstr(position, ".bin");
    1918           0 :       fd_cstr_fini(position);
    1919             : 
    1920           0 :       FILE * file = fopen(output_filepath, "wb");
    1921           0 :       if( file ) {
    1922           0 :         fwrite( out, 1, stream.bytes_written, file );
    1923           0 :         fclose( file );
    1924           0 :       }
    1925           0 :     }
    1926           0 :   } FD_SCRATCH_SCOPE_END;
    1927           0 : }
    1928             : 
    1929             : int
    1930        5190 : fd_execute_txn( fd_exec_txn_ctx_t * txn_ctx ) {
    1931        5190 :   FD_SCRATCH_SCOPE_BEGIN {
    1932        5190 :     uint use_sysvar_instructions = fd_executor_txn_uses_sysvar_instructions( txn_ctx );
    1933        5190 :     int ret = 0;
    1934             : 
    1935             : #ifdef VLOG
    1936             :     fd_txn_t const *txn = txn_ctx->txn_descriptor;
    1937             :     fd_rawtxn_b_t const *raw_txn = txn_ctx->_txn_raw;
    1938             :     uchar * sig = (uchar *)raw_txn->raw + txn->signature_off;
    1939             : #endif
    1940             : 
    1941        5190 :     bool dump_insn = txn_ctx->capture_ctx && txn_ctx->slot_ctx->slot_bank.slot >= txn_ctx->capture_ctx->dump_proto_start_slot && txn_ctx->capture_ctx->dump_insn_to_pb;
    1942             : 
    1943             :     /* Initialize log collection */
    1944        5190 :     fd_log_collector_init( &txn_ctx->log_collector, txn_ctx->slot_ctx->enable_exec_recording );
    1945             : 
    1946        8049 :     for ( ushort i = 0; i < txn_ctx->txn_descriptor->instr_cnt; i++ ) {
    1947             : #ifdef VLOG
    1948             :       FD_LOG_WARNING(( "Start of transaction for %d for %s", i, FD_BASE58_ENC_64_ALLOCA( sig ) ));
    1949             : #endif
    1950             : 
    1951        6339 :       if ( FD_UNLIKELY( use_sysvar_instructions ) ) {
    1952           0 :         ret = fd_sysvar_instructions_update_current_instr_idx( txn_ctx, i );
    1953           0 :         if( ret != FD_ACC_MGR_SUCCESS ) {
    1954           0 :           FD_LOG_WARNING(( "sysvar instructions failed to update instruction index" ));
    1955           0 :           return ret;
    1956           0 :         }
    1957           0 :       }
    1958             : 
    1959        6339 :       if( dump_insn ) {
    1960             :         // Capture the input and convert it into a Protobuf message
    1961           0 :         dump_instr_to_protobuf(txn_ctx, &txn_ctx->instr_infos[i], i);
    1962           0 :       }
    1963             : 
    1964             : 
    1965        6339 :       int exec_result = fd_execute_instr( txn_ctx, &txn_ctx->instr_infos[i] );
    1966             : #ifdef VLOG
    1967             :       FD_LOG_WARNING(( "fd_execute_instr result (%d) for %s", exec_result, FD_BASE58_ENC_64_ALLOCA( sig ) ));
    1968             : #endif
    1969        6339 :       if( exec_result != FD_EXECUTOR_INSTR_SUCCESS ) {
    1970        3480 :         if ( txn_ctx->instr_err_idx == INT_MAX )
    1971        3480 :         {
    1972        3480 :           txn_ctx->instr_err_idx = i;
    1973        3480 :         }
    1974             :   #ifdef VLOG
    1975             :         if ( 257037453 == txn_ctx->slot_ctx->slot_bank.slot ) {
    1976             :   #endif
    1977        3480 :           if (exec_result == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
    1978             :   #ifdef VLOG
    1979             :             FD_LOG_WARNING(( "fd_execute_instr failed (%d:%d) for %s",
    1980             :                              exec_result,
    1981             :                              txn_ctx->custom_err,
    1982             :                              FD_BASE58_ENC_64_ALLOCA( sig ) ));
    1983             :   #endif
    1984        2805 :           } else {
    1985             :   #ifdef VLOG
    1986             :             FD_LOG_WARNING(( "fd_execute_instr failed (%d) index %u for %s",
    1987             :               exec_result,
    1988             :               i,
    1989             :               FD_BASE58_ENC_64_ALLOCA( sig ) ));
    1990             :   #endif
    1991        2805 :           }
    1992             :   #ifdef VLOG
    1993             :         }
    1994             :   #endif
    1995        3480 :         return exec_result;
    1996        3480 :       }
    1997        6339 :     }
    1998        1710 :     int err = fd_executor_txn_check( txn_ctx->slot_ctx, txn_ctx );
    1999        1710 :     if ( err != FD_EXECUTOR_INSTR_SUCCESS) {
    2000           6 :       FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, txn_ctx->instr_err_idx );
    2001           6 :       FD_LOG_DEBUG(( "fd_executor_txn_check failed (%d)", err ));
    2002           6 :       return err;
    2003           6 :     }
    2004             : 
    2005        1704 :     return 0;
    2006        5190 :   } FD_SCRATCH_SCOPE_END;
    2007        5190 : }
    2008             : 
    2009             : int
    2010             : fd_executor_txn_check( fd_exec_slot_ctx_t const * slot_ctx,
    2011        1710 :                        fd_exec_txn_ctx_t *        txn ) {
    2012        1710 :   fd_rent_t const * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
    2013             : 
    2014        1710 :   ulong starting_lamports_l = 0;
    2015        1710 :   ulong starting_lamports_h = 0;
    2016             : 
    2017        1710 :   ulong ending_lamports_l = 0;
    2018        1710 :   ulong ending_lamports_h = 0;
    2019             : 
    2020             :   /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L63 */
    2021        8043 :   for( ulong idx = 0; idx < txn->accounts_cnt; idx++ ) {
    2022        6339 :     fd_borrowed_account_t * b = &txn->borrowed_accounts[idx];
    2023             : 
    2024             :     // Was this account written to?
    2025        6339 :     if( NULL != b->meta ) {
    2026        3480 :       fd_uwide_inc( &ending_lamports_h, &ending_lamports_l, ending_lamports_h, ending_lamports_l, b->meta->info.lamports );
    2027             : 
    2028             :       /* Rent states are defined as followed:
    2029             :          - lamports == 0                      -> Uninitialized
    2030             :          - 0 < lamports < rent_exempt_minimum -> RentPaying
    2031             :          - lamports >= rent_exempt_minimum    -> RentExempt
    2032             :          In Agave, 'self' refers to our 'after' state. */
    2033        3480 :       uchar after_uninitialized  = b->meta->info.lamports == 0;
    2034        3480 :       uchar after_rent_exempt    = b->meta->info.lamports >= fd_rent_exempt_minimum_balance( rent, b->meta->dlen );
    2035             : 
    2036             :       /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L96 */
    2037        3480 :       if( FD_LIKELY( memcmp( b->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) != 0 ) ) {
    2038             :         /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L44 */
    2039        3480 :         if( after_uninitialized || after_rent_exempt ) {
    2040             :           // no-op
    2041        3369 :         } else {
    2042             :           /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L45-L59 */
    2043         111 :           uchar before_uninitialized = b->starting_dlen == ULONG_MAX || b->starting_lamports == 0;
    2044         111 :           uchar before_rent_exempt   = b->starting_dlen != ULONG_MAX && b->starting_lamports >= fd_rent_exempt_minimum_balance( rent, b->starting_dlen );
    2045             : 
    2046             :           /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L50 */
    2047         111 :           if( before_uninitialized || before_rent_exempt ) {
    2048           3 :             FD_LOG_DEBUG(( "Rent exempt error for %s Curr len %lu Starting len %lu Curr lamports %lu Starting lamports %lu Curr exempt %lu Starting exempt %lu",
    2049           3 :                            FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
    2050           3 :                            b->meta->dlen,
    2051           3 :                            b->starting_dlen,
    2052           3 :                            b->meta->info.lamports,
    2053           3 :                            b->starting_lamports,
    2054           3 :                            fd_rent_exempt_minimum_balance( rent, b->meta->dlen ),
    2055           3 :                            fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
    2056             :             /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
    2057           3 :             return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
    2058             :           /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L56 */
    2059         108 :           } else if( (b->meta->dlen == b->starting_dlen) && b->meta->info.lamports <= b->starting_lamports ) {
    2060             :             // no-op
    2061         105 :           } else {
    2062           3 :             FD_LOG_DEBUG(( "Rent exempt error for %s Curr len %lu Starting len %lu Curr lamports %lu Starting lamports %lu Curr exempt %lu Starting exempt %lu",
    2063           3 :                            FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
    2064           3 :                            b->meta->dlen,
    2065           3 :                            b->starting_dlen,
    2066           3 :                            b->meta->info.lamports,
    2067           3 :                            b->starting_lamports,
    2068           3 :                            fd_rent_exempt_minimum_balance( rent, b->meta->dlen ),
    2069           3 :                            fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
    2070             :             /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
    2071           3 :             return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
    2072           3 :           }
    2073         111 :         }
    2074        3480 :       }
    2075             : 
    2076        3474 :       if( b->starting_lamports != ULONG_MAX ) {
    2077        3384 :         fd_uwide_inc( &starting_lamports_h, &starting_lamports_l, starting_lamports_h, starting_lamports_l, b->starting_lamports );
    2078        3384 :       }
    2079        3474 :     } else if( NULL != b->const_meta ) {
    2080             :       // Should these just kill the client?  They are impossible...
    2081        2859 :       if( b->starting_lamports != b->const_meta->info.lamports ) {
    2082           0 :         FD_LOG_DEBUG(("Const rec mismatch %s starting %lu %lu ending %lu %lu", FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ), b->starting_dlen, b->starting_lamports, b->const_meta->dlen, b->const_meta->info.lamports));
    2083           0 :         return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    2084           0 :       }
    2085        2859 :       if( b->starting_dlen != b->const_meta->dlen ) {
    2086           0 :         FD_LOG_DEBUG(("Const rec mismatch %s starting %lu %lu ending %lu %lu", FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ), b->starting_dlen, b->starting_lamports, b->const_meta->dlen, b->const_meta->info.lamports));
    2087           0 :         return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    2088           0 :       }
    2089        2859 :     }
    2090        6339 :   }
    2091             : 
    2092             :   /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/transaction_processor.rs#L839-L845 */
    2093        1704 :   if( FD_UNLIKELY( ending_lamports_l != starting_lamports_l || ending_lamports_h != starting_lamports_h ) ) {
    2094           0 :     FD_LOG_DEBUG(( "Lamport sum mismatch: starting %lx%lx ending %lx%lx", starting_lamports_h, starting_lamports_l, ending_lamports_h, ending_lamports_l ));
    2095           0 :     return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    2096           0 :   }
    2097             : 
    2098        1704 :   return FD_EXECUTOR_INSTR_SUCCESS;
    2099        1704 : }
    2100             : #undef VLOG
    2101             : 
    2102             : /* fd_executor_instr_strerror() returns the error message corresponding to err,
    2103             :    intended to be logged by log_collector, or an empty string if the error code
    2104             :    should be omitted in logs for whatever reason.  Omitted examples are success,
    2105             :    fatal (placeholder just in firedancer), custom error.
    2106             :    See also fd_log_collector_program_failure(). */
    2107             : FD_FN_CONST char const *
    2108       47391 : fd_executor_instr_strerror( int err ) {
    2109             : 
    2110       47391 :   switch( err ) {
    2111           0 :   case FD_EXECUTOR_INSTR_SUCCESS                                : return ""; // not used
    2112           0 :   case FD_EXECUTOR_INSTR_ERR_FATAL                              : return ""; // not used
    2113           0 :   case FD_EXECUTOR_INSTR_ERR_GENERIC_ERR                        : return "generic instruction error";
    2114        2790 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ARG                        : return "invalid program argument";
    2115        9567 :   case FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA                 : return "invalid instruction data";
    2116        7293 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA                   : return "invalid account data for instruction";
    2117          21 :   case FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL                 : return "account data too small for instruction";
    2118        1236 :   case FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS                 : return "insufficient funds for instruction";
    2119         228 :   case FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID               : return "incorrect program id for instruction";
    2120        6741 :   case FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE         : return "missing required signature for instruction";
    2121           6 :   case FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED            : return "instruction requires an uninitialized account";
    2122          75 :   case FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT              : return "instruction requires an initialized account";
    2123           0 :   case FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR                   : return "sum of account balances before and after instruction do not match";
    2124         318 :   case FD_EXECUTOR_INSTR_ERR_MODIFIED_PROGRAM_ID                : return "instruction illegally modified the program id of an account";
    2125          45 :   case FD_EXECUTOR_INSTR_ERR_EXTERNAL_ACCOUNT_LAMPORT_SPEND     : return "instruction spent from the balance of an account it does not own";
    2126          51 :   case FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED             : return "instruction modified data of an account it does not own";
    2127         192 :   case FD_EXECUTOR_INSTR_ERR_READONLY_LAMPORT_CHANGE            : return "instruction changed the balance of a read-only account";
    2128         303 :   case FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED             : return "instruction modified data of a read-only account";
    2129           0 :   case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_IDX              : return "instruction contains duplicate accounts";
    2130           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_MODIFIED                : return "instruction changed executable bit of an account";
    2131           0 :   case FD_EXECUTOR_INSTR_ERR_RENT_EPOCH_MODIFIED                : return "instruction modified rent epoch of an account";
    2132         993 :   case FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS                : return "insufficient account keys for instruction";
    2133           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_DATA_SIZE_CHANGED              : return "program other than the account's owner changed the size of the account data";
    2134           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE                 : return "instruction expected an executable account";
    2135         237 :   case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED                  : return "instruction tries to borrow reference for an account which is already borrowed";
    2136           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING             : return "instruction left account with an outstanding borrowed reference";
    2137           0 :   case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_OUT_OF_SYNC      : return "instruction modifications of multiply-passed account differ";
    2138           0 :   case FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR                         : return ""; // custom handling via txn_ctx->custom_err
    2139           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ERR                        : return "program returned invalid error code";
    2140         303 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED           : return "instruction changed executable accounts data";
    2141          87 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_LAMPORT_CHANGE          : return "instruction changed the balance of an executable account";
    2142           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT : return "executable accounts must be rent exempt";
    2143         165 :   case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID             : return "Unsupported program id";
    2144           0 :   case FD_EXECUTOR_INSTR_ERR_CALL_DEPTH                         : return "Cross-program invocation call depth too deep";
    2145           0 :   case FD_EXECUTOR_INSTR_ERR_MISSING_ACC                        : return "An account required by the instruction is missing";
    2146          12 :   case FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED             : return "Cross-program invocation reentrancy not allowed for this instruction";
    2147           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED           : return "Length of the seed is too long for address generation";
    2148           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_SEEDS                      : return "Provided seeds do not result in a valid address";
    2149           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC                    : return "Failed to reallocate account data";
    2150       10299 :   case FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED            : return "Computational budget exceeded";
    2151           3 :   case FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION               : return "Cross-program invocation with unauthorized signer or writable account";
    2152           0 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_ENVIRONMENT_SETUP_FAILURE  : return "Failed to create program execution environment";
    2153          18 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPLETE         : return "Program failed to complete";
    2154           0 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPILE          : return "Program failed to compile";
    2155           3 :   case FD_EXECUTOR_INSTR_ERR_ACC_IMMUTABLE                      : return "Account is immutable";
    2156           6 :   case FD_EXECUTOR_INSTR_ERR_INCORRECT_AUTHORITY                : return "Incorrect authority provided";
    2157           0 :   case FD_EXECUTOR_INSTR_ERR_BORSH_IO_ERROR                     : return "Failed to serialize or deserialize account data"; // truncated
    2158           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT                : return "An account does not have enough lamports to be rent-exempt";
    2159        6150 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER                  : return "Invalid account owner";
    2160          84 :   case FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW                : return "Program arithmetic overflowed";
    2161         165 :   case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR                 : return "Unsupported sysvar";
    2162           0 :   case FD_EXECUTOR_INSTR_ERR_ILLEGAL_OWNER                      : return "Provided owner is not allowed";
    2163           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED      : return "Accounts data allocations exceeded the maximum allowed per transaction";
    2164           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED                  : return "Max accounts exceeded";
    2165           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED       : return "Max instruction trace length exceeded";
    2166           0 :   case FD_EXECUTOR_INSTR_ERR_BUILTINS_MUST_CONSUME_CUS          : return "Builtin programs must consume compute units";
    2167           0 :   default: break;
    2168       47391 :   }
    2169             : 
    2170           0 :   return "";
    2171       47391 : }
    2172             : 
    2173             : // This is purely linker magic to force the inclusion of the yaml type walker so that it is
    2174             : // available for debuggers
    2175             : void
    2176           0 : fd_debug_symbology(void) {
    2177           0 :   (void)fd_get_types_yaml();
    2178           0 : }

Generated by: LCOV version 1.14