LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_executor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 654 1274 51.3 %
Date: 2024-11-13 11:58:15 Functions: 26 38 68.4 %

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

Generated by: LCOV version 1.14