LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_executor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 641 908 70.6 %
Date: 2026-05-19 08:05:42 Functions: 36 37 97.3 %

          Line data    Source code
       1             : #include "fd_executor.h"
       2             : #include "fd_bank.h"
       3             : #include "fd_runtime.h"
       4             : #include "fd_runtime_err.h"
       5             : #include "fd_acc_pool.h"
       6             : 
       7             : #include "fd_system_ids.h"
       8             : #include "program/fd_bpf_loader_program.h"
       9             : #include "program/fd_compute_budget_program.h"
      10             : #include "program/fd_precompiles.h"
      11             : #include "program/fd_system_program.h"
      12             : #include "program/fd_builtin_programs.h"
      13             : #include "program/fd_vote_program.h"
      14             : #include "program/fd_zk_elgamal_proof_program.h"
      15             : #include "sysvar/fd_sysvar_cache.h"
      16             : #include "sysvar/fd_sysvar_instructions.h"
      17             : #include "sysvar/fd_sysvar_rent.h"
      18             : #include "tests/fd_dump_pb.h"
      19             : 
      20             : #include "../accdb/fd_accdb_sync.h"
      21             : #include "../log_collector/fd_log_collector.h"
      22             : 
      23             : #include "../../ballet/base58/fd_base58.h"
      24             : 
      25             : #include "../../util/bits/fd_uwide.h"
      26             : 
      27             : #include "../../disco/pack/fd_pack_tip_prog_blacklist.h"
      28             : 
      29             : #include <assert.h>
      30             : #include <math.h>
      31             : #include <stdio.h>   /* snprintf(3) */
      32             : #include <fcntl.h>   /* openat(2) */
      33             : #include <unistd.h>  /* write(3) */
      34             : #include <time.h>
      35             : 
      36             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/rent_state.rs#L5-L15 */
      37             : struct fd_rent_state {
      38             :   uint  discriminant;
      39             :   ulong lamports;
      40             :   ulong data_size;
      41             : };
      42             : typedef struct fd_rent_state fd_rent_state_t;
      43             : 
      44           0 : #define FD_RENT_STATE_UNINITIALIZED (0U)
      45           0 : #define FD_RENT_STATE_RENT_PAYING   (1U)
      46         549 : #define FD_RENT_STATE_RENT_EXEMPT   (2U)
      47             : 
      48             : #define MAP_PERFECT_NAME fd_native_program_fn_lookup_tbl
      49             : #define MAP_PERFECT_LG_TBL_SZ 3
      50             : #define MAP_PERFECT_T fd_native_prog_info_t
      51        7662 : #define MAP_PERFECT_HASH_C 1069U
      52             : #define MAP_PERFECT_KEY key.uc
      53             : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
      54             : #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)
      55             : #define MAP_PERFECT_COMPLEX_KEY 1
      56        7662 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
      57             : 
      58        7662 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>29)&0x7U)
      59             : 
      60             : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
      61             :                              a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
      62             :                                           PERFECT_HASH( (a08 | (a09<<8) | (a10<<16) | (a11<<24)) )
      63        7662 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr + 8UL ) )
      64             : 
      65             : #define MAP_PERFECT_0  ( VOTE_PROG_ID            ), .fn = fd_vote_program_execute,                      .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
      66             : #define MAP_PERFECT_1  ( SYS_PROG_ID             ), .fn = fd_system_program_execute,                    .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
      67             : #define MAP_PERFECT_2  ( COMPUTE_BUDGET_PROG_ID  ), .fn = fd_compute_budget_program_execute,            .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
      68             : #define MAP_PERFECT_3  ( ZK_EL_GAMAL_PROG_ID     ), .fn = fd_executor_zk_elgamal_proof_program_execute, .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
      69             : #define MAP_PERFECT_4  ( BPF_LOADER_1_PROG_ID    ), .fn = fd_bpf_loader_program_execute,                .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
      70             : #define MAP_PERFECT_5  ( BPF_LOADER_2_PROG_ID    ), .fn = fd_bpf_loader_program_execute,                .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
      71             : #define MAP_PERFECT_6  ( BPF_UPGRADEABLE_PROG_ID ), .fn = fd_bpf_loader_program_execute,                .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
      72             : #define MAP_PERFECT_7  ( LOADER_V4_PROG_ID       ), .fn = NULL,                                         .is_bpf_loader = 1, .feature_enable_offset = offsetof( fd_features_t, enable_loader_v4 )
      73             : 
      74             : #include "../../util/tmpl/fd_map_perfect.c"
      75             : #undef PERFECT_HASH
      76             : 
      77             : uchar
      78        2454 : fd_executor_pubkey_is_bpf_loader( fd_pubkey_t const * pubkey ) {
      79        2454 :   fd_native_prog_info_t const null_function = {0};
      80        2454 :   return fd_native_program_fn_lookup_tbl_query( pubkey, &null_function )->is_bpf_loader;
      81        2454 : }
      82             : 
      83             : uchar
      84             : fd_executor_program_is_active( fd_bank_t *         bank,
      85        2604 :                                fd_pubkey_t const * pubkey ) {
      86        2604 :   fd_native_prog_info_t const null_function = {0};
      87        2604 :   ulong feature_offset = fd_native_program_fn_lookup_tbl_query( pubkey, &null_function )->feature_enable_offset;
      88             : 
      89        2604 :   return feature_offset==ULONG_MAX ||
      90        2604 :          FD_FEATURE_ACTIVE_BANK_OFFSET( bank, feature_offset );
      91        2604 : }
      92             : 
      93             : /* fd_executor_lookup_native_program returns the appropriate instruction processor for the given
      94             :    native program ID. Returns NULL if given ID is not a recognized native program.
      95             :    https://github.com/anza-xyz/agave/blob/v2.2.6/program-runtime/src/invoke_context.rs#L520-L544 */
      96             : static int
      97             : fd_executor_lookup_native_program( fd_pubkey_t const *       pubkey,
      98             :                                    fd_account_meta_t const * meta,
      99             :                                    fd_bank_t *               bank,
     100             :                                    fd_exec_instr_fn_t *      native_prog_fn,
     101        2604 :                                    uchar *                   is_precompile ) {
     102             :   /* First lookup to see if the program key is a precompile */
     103        2604 :   *is_precompile = 0;
     104        2604 :   *native_prog_fn = fd_executor_lookup_native_precompile_program( pubkey );
     105        2604 :   if( FD_UNLIKELY( *native_prog_fn!=NULL ) ) {
     106           0 :     *is_precompile = 1;
     107           0 :     return 0;
     108           0 :   }
     109             : 
     110        2604 :   fd_pubkey_t const * owner = (fd_pubkey_t const *)meta->owner;
     111             : 
     112             :   /* Native programs should be owned by the native loader...
     113             :      This will not be the case though once core programs are migrated to BPF. */
     114        2604 :   int is_native_program = !memcmp( owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) );
     115             : 
     116        2604 :   if( !is_native_program ) {
     117        2454 :     if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( owner ) ) ) {
     118           0 :       return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
     119           0 :     }
     120        2454 :   }
     121             : 
     122        2604 :   fd_pubkey_t const * lookup_pubkey = is_native_program ? pubkey : owner;
     123             : 
     124             :   /* Migrated programs must be executed via the corresponding BPF
     125             :      loader(s), not natively. This check is performed at the transaction
     126             :      level, but we re-check to please the instruction level (and below)
     127             :      fuzzers. */
     128        2604 :   uchar has_migrated;
     129        2604 :   if( FD_UNLIKELY( fd_is_migrating_builtin_program( bank, lookup_pubkey, &has_migrated ) && has_migrated ) ) {
     130           0 :     *native_prog_fn = NULL;
     131           0 :     return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
     132           0 :   }
     133             : 
     134             :   /* We perform feature gate checks here to emulate the absence of
     135             :      a native program in Agave's ProgramCache when the program's feature
     136             :      gate is not activated.
     137             :      https://github.com/anza-xyz/agave/blob/v3.0.3/program-runtime/src/invoke_context.rs#L546-L549 */
     138             : 
     139        2604 :   if( FD_UNLIKELY( !fd_executor_program_is_active( bank, lookup_pubkey ) ) ) {
     140           0 :     *native_prog_fn = NULL;
     141           0 :     return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
     142           0 :   }
     143             : 
     144        2604 :   fd_native_prog_info_t const null_function = {0};
     145        2604 :   *native_prog_fn                           = fd_native_program_fn_lookup_tbl_query( lookup_pubkey, &null_function )->fn;
     146        2604 :   return 0;
     147        2604 : }
     148             : 
     149             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L117-L136 */
     150             : static uchar
     151             : fd_executor_rent_transition_allowed( fd_rent_state_t const * pre_rent_state,
     152         183 :                                      fd_rent_state_t const * post_rent_state ) {
     153         183 :   switch( post_rent_state->discriminant ) {
     154           0 :     case FD_RENT_STATE_UNINITIALIZED:
     155         183 :     case FD_RENT_STATE_RENT_EXEMPT: {
     156         183 :       return 1;
     157           0 :     }
     158           0 :     case FD_RENT_STATE_RENT_PAYING: {
     159           0 :       switch( pre_rent_state->discriminant ) {
     160           0 :         case FD_RENT_STATE_UNINITIALIZED:
     161           0 :         case FD_RENT_STATE_RENT_EXEMPT: {
     162           0 :           return 0;
     163           0 :         }
     164           0 :         case FD_RENT_STATE_RENT_PAYING: {
     165           0 :           return post_rent_state->data_size==pre_rent_state->data_size &&
     166           0 :                  post_rent_state->lamports<=pre_rent_state->lamports;
     167           0 :         }
     168           0 :         default: {
     169           0 :           FD_LOG_CRIT(( "unexpected pre-rent state discriminant %u", pre_rent_state->discriminant ));
     170           0 :         }
     171           0 :       }
     172           0 :     }
     173           0 :     default: {
     174           0 :       FD_LOG_CRIT(( "unexpected post-rent state discriminant %u", post_rent_state->discriminant ));
     175           0 :     }
     176         183 :   }
     177         183 : }
     178             : 
     179             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L61-L77 */
     180             : static int
     181             : fd_executor_check_rent_state_with_account( fd_pubkey_t const *     pubkey,
     182             :                                            fd_rent_state_t const * pre_rent_state,
     183         183 :                                            fd_rent_state_t const * post_rent_state ) {
     184         183 :   if( FD_UNLIKELY( memcmp( pubkey, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) &&
     185         183 :                    !fd_executor_rent_transition_allowed( pre_rent_state, post_rent_state ) ) ) {
     186           0 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
     187           0 :   }
     188         183 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     189         183 : }
     190             : 
     191             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L87-L101 */
     192             : fd_rent_state_t
     193         366 : fd_executor_get_account_rent_state( fd_account_meta_t const * meta, fd_rent_t const * rent ) {
     194             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L88-L89 */
     195         366 :   if( meta->lamports==0UL ) {
     196           0 :     return (fd_rent_state_t){
     197           0 :       .discriminant = FD_RENT_STATE_UNINITIALIZED
     198           0 :     };
     199           0 :   }
     200             : 
     201             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L90-L94 */
     202         366 :   if( meta->lamports>=fd_rent_exempt_minimum_balance( rent, meta->dlen ) ) {
     203         366 :     return (fd_rent_state_t){
     204         366 :       .discriminant = FD_RENT_STATE_RENT_EXEMPT
     205         366 :     };
     206         366 :   }
     207             : 
     208             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L95-L99 */
     209           0 :   return (fd_rent_state_t){
     210           0 :     .discriminant = FD_RENT_STATE_RENT_PAYING,
     211           0 :     .lamports     = meta->lamports,
     212           0 :     .data_size    = meta->dlen
     213           0 :   };
     214         366 : }
     215             : 
     216             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L293-L342 */
     217             : static int
     218             : fd_validate_fee_payer( fd_pubkey_t const * pubkey,
     219             :                        fd_account_meta_t * meta,
     220             :                        fd_rent_t const *   rent,
     221         183 :                        ulong               fee ) {
     222             : 
     223             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L301-L304 */
     224         183 :   if( FD_UNLIKELY( meta->lamports==0UL ) ) {
     225           0 :     FD_BASE58_ENCODE_32_BYTES( pubkey->uc, pubkey_b58 );
     226           0 :     FD_LOG_DEBUG(( "Fee payer doesn't exist %s", pubkey_b58 ));
     227           0 :     return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     228           0 :   }
     229             : 
     230             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L305-L308 */
     231         183 :   int system_account_kind = fd_get_system_account_kind( meta );
     232         183 :   if( FD_UNLIKELY( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_UNKNOWN ) ) {
     233           0 :     return FD_RUNTIME_TXN_ERR_INVALID_ACCOUNT_FOR_FEE;
     234           0 :   }
     235             : 
     236             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L309-L318 */
     237         183 :   ulong min_balance = 0UL;
     238         183 :   if( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_NONCE ) {
     239           0 :     min_balance = fd_rent_exempt_minimum_balance( rent, FD_SYSTEM_PROGRAM_NONCE_DLEN );
     240           0 :   }
     241             : 
     242             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L320-L327 */
     243         183 :   if( FD_UNLIKELY( min_balance>meta->lamports || fee>meta->lamports-min_balance ) ) {
     244           0 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
     245           0 :   }
     246             : 
     247             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L329 */
     248         183 :   fd_rent_state_t payer_pre_rent_state = fd_executor_get_account_rent_state( meta, rent );
     249             : 
     250             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L330-L332 */
     251         183 :   int err = fd_account_meta_checked_sub_lamports( meta, fee );
     252         183 :   if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
     253           0 :     return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
     254           0 :   }
     255             : 
     256             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L334 */
     257         183 :   fd_rent_state_t payer_post_rent_state = fd_executor_get_account_rent_state( meta, rent );
     258             : 
     259             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L335-L342 */
     260         183 :   return fd_executor_check_rent_state_with_account( pubkey, &payer_pre_rent_state, &payer_post_rent_state );
     261         183 : }
     262             : 
     263             : static int
     264             : fd_executor_check_status_cache( fd_txncache_t *     status_cache,
     265             :                                 fd_bank_t *         bank,
     266             :                                 fd_txn_in_t const * txn_in,
     267         183 :                                 fd_txn_out_t *      txn_out ) {
     268         183 :   if( FD_UNLIKELY( !status_cache ) ) {
     269         183 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     270         183 :   }
     271             : 
     272           0 :   if( FD_UNLIKELY( txn_out->accounts.nonce_idx_in_txn!=ULONG_MAX ) ) {
     273             :     /* In Agave, durable nonce transactions are inserted to the status
     274             :        cache the same as any others, but this is only to serve RPC
     275             :        requests, they do not need to be in there for correctness as the
     276             :        nonce mechanism itself prevents double spend.  We skip this logic
     277             :        entirely to simplify and improve performance of the txn cache. */
     278           0 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     279           0 :   }
     280             : 
     281             :   /* Compute the blake3 hash of the transaction message
     282             :      https://github.com/anza-xyz/agave/blob/v2.1.7/sdk/program/src/message/versions/mod.rs#L159-L167 */
     283           0 :   fd_blake3_t b3[1];
     284           0 :   fd_blake3_init( b3 );
     285           0 :   fd_blake3_append( b3, "solana-tx-message-v1", 20UL );
     286           0 :   fd_blake3_append( b3, ((uchar *)txn_in->txn->payload + TXN( txn_in->txn )->message_off),(ulong)( txn_in->txn->payload_sz - TXN( txn_in->txn )->message_off ) );
     287           0 :   fd_blake3_fini( b3, &txn_out->details.blake_txn_msg_hash );
     288             : 
     289           0 :   fd_hash_t * blockhash = (fd_hash_t *)((uchar *)txn_in->txn->payload + TXN( txn_in->txn )->recent_blockhash_off);
     290           0 :   int found = fd_txncache_query( status_cache, bank->txncache_fork_id, blockhash->uc, txn_out->details.blake_txn_msg_hash.uc );
     291           0 :   if( FD_UNLIKELY( found ) ) return FD_RUNTIME_TXN_ERR_ALREADY_PROCESSED;
     292             : 
     293           0 :   if( FD_UNLIKELY( txn_in->bundle.is_bundle ) ) {
     294             :     /* It is possible for users to send transactions in a bundle with
     295             :        identical transaction message hashes.  The message hashes are
     296             :        only added to the txncache when transactions are committed.  So
     297             :        message hashes must be checked against the txncache AND previous
     298             :        transactions in the bundle. */
     299           0 :     for( ulong i=0UL; i<txn_in->bundle.prev_txn_cnt; i++ ) {
     300           0 :       fd_txn_out_t const * prev_txn_out = txn_in->bundle.prev_txn_outs[i];
     301           0 :       if( FD_UNLIKELY( !memcmp( &prev_txn_out->details.blake_txn_msg_hash, &txn_out->details.blake_txn_msg_hash, sizeof(fd_hash_t) ) ) ) {
     302           0 :         return FD_RUNTIME_TXN_ERR_ALREADY_PROCESSED;
     303           0 :       }
     304           0 :     }
     305           0 :   }
     306             : 
     307           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     308           0 : }
     309             : 
     310             : /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L71-L136 */
     311             : static int
     312             : fd_executor_check_transaction_age_and_compute_budget_limits( fd_bank_t *         bank,
     313             :                                                              fd_txn_in_t const * txn_in,
     314         186 :                                                              fd_txn_out_t *      txn_out ) {
     315             : 
     316             :   /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L94-L123 */
     317         186 :   int err = fd_sanitize_compute_unit_limits( txn_out );
     318         186 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     319           0 :     return err;
     320           0 :   }
     321             : 
     322             :   /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L124-L131 */
     323         186 :   err = fd_check_transaction_age( bank, txn_in, txn_out );
     324         186 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     325           3 :     return err;
     326           3 :   }
     327             : 
     328         183 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     329         186 : }
     330             : 
     331             : /* https://github.com/anza-xyz/agave/blob/v2.0.9/runtime/src/bank.rs#L3239-L3251 */
     332             : static inline ulong
     333         186 : get_transaction_account_lock_limit( fd_bank_t * bank ) {
     334         186 :   return fd_ulong_if( FD_FEATURE_ACTIVE_BANK( bank, increase_tx_account_lock_limit ), MAX_TX_ACCOUNT_LOCKS, 64UL );
     335         186 : }
     336             : 
     337             : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L61-L75 */
     338             : int
     339             : fd_executor_check_transactions( fd_runtime_t *      runtime,
     340             :                                 fd_bank_t *         bank,
     341             :                                 fd_txn_in_t const * txn_in,
     342         186 :                                 fd_txn_out_t *      txn_out ) {
     343             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L68-L73 */
     344         186 :   int err = fd_executor_check_transaction_age_and_compute_budget_limits( bank, txn_in, txn_out );
     345         186 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     346           3 :     return err;
     347           3 :   }
     348             : 
     349             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L74 */
     350         183 :   err = fd_executor_check_status_cache( runtime->status_cache, bank, txn_in, txn_out );
     351         183 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     352           0 :     return err;
     353           0 :   }
     354             : 
     355         183 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     356         183 : }
     357             : 
     358             : /* `verify_transaction()` is the first function called in the
     359             :    transaction execution pipeline. It is responsible for deserializing
     360             :    the transaction, verifying the message hash (sigverify), verifying
     361             :    the precompiles, and processing compute budget instructions. We
     362             :    leave sigverify out for now to easily bypass this function's
     363             :    checks for fuzzing.
     364             : 
     365             :    TODO: Maybe support adding sigverify in here, and toggling it
     366             :    on/off with a flag.
     367             : 
     368             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L5725-L5753 */
     369             : int
     370             : fd_executor_verify_transaction( fd_bank_t const *   bank,
     371             :                                 fd_txn_in_t const * txn_in,
     372         189 :                                 fd_txn_out_t *      txn_out ) {
     373         189 :   int err = FD_RUNTIME_EXECUTE_SUCCESS;
     374             : 
     375             :   /* SIMD-0406: enforce limit on number of instruction accounts.
     376             : 
     377             :      TODO: when limit_instruction_accounts is activated everywhere,
     378             :      remove this and make the transaction parser check stricter.
     379             : 
     380             :      https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/runtime-transaction/src/runtime_transaction/sdk_transactions.rs#L93-L99 */
     381         189 :   if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, limit_instruction_accounts ) ) ) {
     382           6 :     fd_txn_t const * txn = TXN( txn_in->txn );
     383           9 :     for( ushort i=0; i<txn->instr_cnt; i++ ) {
     384           6 :       if( FD_UNLIKELY( txn->instr[i].acct_cnt > FD_BPF_INSTR_ACCT_MAX ) ) {
     385           3 :         return FD_RUNTIME_TXN_ERR_SANITIZE_FAILURE;
     386           3 :       }
     387           6 :     }
     388           6 :   }
     389             : 
     390             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L566-L569 */
     391         186 :   err = fd_executor_compute_budget_program_execute_instructions( bank, txn_in, txn_out );
     392         186 :   if( FD_UNLIKELY( err ) ) return err;
     393             : 
     394         186 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     395         186 : }
     396             : 
     397             : /* This function contains special casing for loading and collecting rent from
     398             :    each transaction account. The logic is as follows:
     399             :      1. If the account is the instructions sysvar, then load in the compiled
     400             :         instructions from the transactions into the sysvar's data.
     401             :      2. If the account is a fee payer, then it is already loaded.
     402             :      3. Otherwise load in the account from the accounts DB. If the account is
     403             :         writable and exists, try to collect rent from it.
     404             : 
     405             :    Returns the loaded transaction account size, which is the value that
     406             :    must be used when accumulating and checking against the
     407             :    transactions's loaded account data size limit.
     408             : 
     409             :    Agave relies on this function to actually load accounts from their
     410             :    accounts db. However, since our accounts model is slightly different,
     411             :    our account loading logic is handled earlier in the transaction
     412             :    execution pipeline within `fd_executor_setup_accounts_for_txn()`.
     413             :    Therefore, the name of this function is slightly misleading - we
     414             :    don't actually load accounts here, but we still need to collect
     415             :    rent from writable accounts and accumulate the transaction's
     416             :    total loaded account size.
     417             : 
     418             :    https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L199-L228 */
     419             : static ulong
     420             : load_transaction_account( fd_runtime_t *      runtime,
     421             :                           fd_bank_t *         bank,
     422             :                           fd_txn_in_t const * txn_in,
     423             :                           fd_txn_out_t *      txn_out,
     424             :                           fd_pubkey_t const * pubkey,
     425             :                           fd_account_meta_t * meta,
     426             :                           uchar               unknown_acc,
     427         351 :                           ulong               txn_idx ) {
     428             : 
     429             :   /* Handling the sysvar instructions account explictly.
     430             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L817-L824 */
     431         351 :   if( FD_UNLIKELY( !memcmp( pubkey, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) ) ) {
     432             :     /* The sysvar instructions account cannot be "loaded" since it's
     433             :        constructed by the SVM and modified within each transaction's
     434             :        instruction execution only, so it incurs a loaded size cost
     435             :        of 0. */
     436           0 :     fd_sysvar_instructions_serialize_account( runtime, txn_in, txn_out, txn_idx );
     437           0 :     return 0UL;
     438           0 :   }
     439             : 
     440             :   /* This next block calls `account_loader::load_transaction_account()`
     441             :      which loads the account from the accounts db. If the account exists
     442             :      and is writable, collect rent from it.
     443             : 
     444             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
     445         351 :   if( FD_LIKELY( !unknown_acc ) ) {
     446             :     /* SIMD-0186 introduces a base account size of 64 bytes for all
     447             :        transaction counts that exist prior to the transaction's
     448             :        execution.
     449             : 
     450             :        https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L204-L208 */
     451         327 :     ulong base_account_size = FD_FEATURE_ACTIVE_BANK( bank, formalize_loaded_transaction_data_size ) ? FD_TRANSACTION_ACCOUNT_BASE_SIZE : 0UL;
     452             : 
     453             :     /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
     454         327 :     return fd_ulong_sat_add( base_account_size, meta->dlen );
     455         327 :   }
     456             : 
     457             :   /* The rest of this function is a no-op for us since we already set up
     458             :      the transaction accounts for unknown accounts within
     459             :      `fd_executor_setup_accounts_for_txn()`. We also do not need to
     460             :      add a base cost to the loaded account size because the SIMD
     461             :      states that accounts that do not exist prior to the transaction's
     462             :      execution should not incur a loaded size cost.
     463             :      https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L566-L577 */
     464          24 :   return 0UL;
     465         351 : }
     466             : 
     467             : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L494-L515 */
     468             : static int
     469             : fd_increase_calculated_data_size( fd_txn_out_t * txn_out,
     470         720 :                                   ulong          data_size_delta ) {
     471             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L500-L503 */
     472         720 :   if( FD_UNLIKELY( data_size_delta>UINT_MAX ) ) {
     473           0 :     return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
     474           0 :   }
     475             : 
     476             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L505-L507 */
     477         720 :   txn_out->details.loaded_accounts_data_size = fd_ulong_sat_add( txn_out->details.loaded_accounts_data_size, data_size_delta );
     478             : 
     479         720 :   if( FD_UNLIKELY( txn_out->details.loaded_accounts_data_size>txn_out->details.compute_budget.loaded_accounts_data_size_limit ) ) {
     480           0 :     return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
     481           0 :   }
     482             : 
     483         720 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     484         720 : }
     485             : 
     486             : /* This function is represented as a closure in Agave.
     487             :    https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L578-L640 */
     488             : static int
     489             : fd_collect_loaded_account( fd_runtime_t *            runtime,
     490             :                            fd_txn_out_t *            txn_out,
     491             :                            fd_account_meta_t const * account_meta,
     492             :                            ulong                     loaded_acc_size,
     493             :                            fd_pubkey_t *             additional_loaded_account_keys,
     494         534 :                            ulong *                   additional_loaded_account_keys_cnt ) {
     495             : 
     496             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L586-L590 */
     497         534 :   int err = fd_increase_calculated_data_size( txn_out, loaded_acc_size );
     498         534 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     499           0 :     return err;
     500           0 :   }
     501             : 
     502             :   /* The remainder of this function is a deep-nested set of if
     503             :      statements. I've inverted the logic to make it easier to read.
     504             :      The purpose of the following code is to ensure that loader v3
     505             :      programdata accounts are accounted for exactly once in the account
     506             :      loading logic.
     507             : 
     508             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L611 */
     509         534 :   if( FD_LIKELY( memcmp( account_meta->owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
     510         519 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     511         519 :   }
     512             : 
     513             :   /* Try to read the program state
     514             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L612-L634 */
     515          15 :   fd_bpf_state_t loader_state[1];
     516          15 :   err = fd_bpf_loader_program_get_state( account_meta, loader_state );
     517          15 :   if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
     518           0 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     519           0 :   }
     520             : 
     521             :   /* Make sure the account is a v3 program */
     522          15 :   if( loader_state->discriminant!=FD_BPF_STATE_PROGRAM ) {
     523           9 :     return FD_RUNTIME_EXECUTE_SUCCESS;
     524           9 :   }
     525             : 
     526             :   /* Iterate through the account keys and make sure the programdata
     527             :      account is not present so it doesn't get loaded twice.
     528             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L617 */
     529          18 :   for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
     530          15 :     if( FD_UNLIKELY( !memcmp( &txn_out->accounts.keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
     531           3 :       return FD_RUNTIME_EXECUTE_SUCCESS;
     532           3 :     }
     533          15 :   }
     534             : 
     535             :   /* Check that the programdata account has not been already counted
     536             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L618 */
     537           3 :   for( ushort i=0; i<*additional_loaded_account_keys_cnt; i++ ) {
     538           0 :     if( FD_UNLIKELY( !memcmp( &additional_loaded_account_keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
     539           0 :       return FD_RUNTIME_EXECUTE_SUCCESS;
     540           0 :     }
     541           0 :   }
     542             : 
     543             :   /* Programdata account size check */
     544           3 :   fd_accdb_ro_t const * programdata_ref = NULL;
     545           3 :   for( ushort i=0; i<runtime->accounts.executable_cnt; i++ ) {
     546           3 :     fd_accdb_ro_t const * ro = &runtime->accounts.executable[i];
     547           3 :     if( fd_pubkey_eq( fd_accdb_ref_address( ro ), &loader_state->inner.program.programdata_address ) ) {
     548           3 :       programdata_ref = ro;
     549           3 :       break;
     550           3 :     }
     551           3 :   }
     552           3 :   if( FD_UNLIKELY( !programdata_ref ) ) return FD_RUNTIME_EXECUTE_SUCCESS;
     553           3 :   ulong programdata_sz = fd_accdb_ref_data_sz( programdata_ref );
     554             : 
     555             :   /* Try to accumulate the programdata's data size
     556             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L625-L630 */
     557           3 :   ulong programdata_size_delta = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE, programdata_sz );
     558           3 :   err = fd_increase_calculated_data_size( txn_out, programdata_size_delta );
     559           3 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     560           0 :     return err;
     561           0 :   }
     562             : 
     563             :   /* Add the programdata account to the list of loaded programdata accounts
     564             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L631 */
     565           3 :   fd_memcpy(
     566           3 :     &additional_loaded_account_keys[(*additional_loaded_account_keys_cnt)++],
     567           3 :     &loader_state->inner.program.programdata_address,
     568           3 :     sizeof(fd_pubkey_t) );
     569             : 
     570           3 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     571           3 : }
     572             : 
     573             : /* Simplified transaction loading logic for SIMD-0186 which does the
     574             :    following:
     575             :    - Calculates the loaded data size for each address lookup table
     576             :    - Calculates the loaded data size for each transaction account
     577             :    - Calculates the loaded data size for each v3 programdata account
     578             :      not directly referenced in the transaction accounts
     579             :    - Collects rent from all referenced transaction accounts (excluding
     580             :      the fee payer)
     581             :    - Validates that each program invoked in a top-level instruction
     582             :      exists, is executable, and is owned by either the native loader
     583             :      or a bpf loader
     584             : 
     585             :    https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L550-L689
     586             :    https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L518-L548 */
     587             : int
     588             : fd_executor_load_transaction_accounts( fd_runtime_t *      runtime,
     589             :                                        fd_bank_t *         bank,
     590             :                                        fd_txn_in_t const * txn_in,
     591         183 :                                        fd_txn_out_t *      txn_out ) {
     592             :   /* Programdata accounts that are loaded by this transaction.
     593             :      We keep track of these to ensure they are not counted twice.
     594             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L559 */
     595         183 :   fd_pubkey_t additional_loaded_account_keys[ FD_TXN_ACCT_ADDR_MAX ] = { 0 };
     596         183 :   ulong       additional_loaded_account_keys_cnt                     = 0UL;
     597             : 
     598             :   /* Charge a base fee for each address lookup table.
     599             :      https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L570-L576 */
     600         183 :   ulong aluts_size = fd_ulong_sat_mul( TXN( txn_in->txn )->addr_table_lookup_cnt,
     601         183 :                                         FD_ADDRESS_LOOKUP_TABLE_BASE_SIZE );
     602         183 :   int err = fd_increase_calculated_data_size( txn_out, aluts_size );
     603         183 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     604           0 :     return err;
     605           0 :   }
     606             : 
     607             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L642-L660 */
     608         717 :   for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
     609         534 :     fd_account_meta_t * meta = txn_out->accounts.account[i].meta;
     610             : 
     611         534 :     uchar unknown_acc = !!( !fd_runtime_get_account_at_index( txn_in, txn_out, i, fd_runtime_account_check_exists ) ||
     612         534 :                             meta->lamports==0UL);
     613             : 
     614             :     /* Collect the fee payer account separately (since it was already)
     615             :         loaded during fee payer validation.
     616             : 
     617             :         https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L644-L648 */
     618         534 :     if( FD_UNLIKELY( i==FD_FEE_PAYER_TXN_IDX ) ) {
     619         183 :       ulong loaded_acc_size = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE,
     620         183 :                                                 meta->dlen );
     621         183 :       int err = fd_collect_loaded_account(
     622         183 :         runtime,
     623         183 :         txn_out,
     624         183 :         meta,
     625         183 :         loaded_acc_size,
     626         183 :         additional_loaded_account_keys,
     627         183 :         &additional_loaded_account_keys_cnt );
     628         183 :       if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     629           0 :         return err;
     630           0 :       }
     631         183 :       continue;
     632         183 :     }
     633             : 
     634             :     /* Load and collect any remaining accounts
     635             :        https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L652-L659 */
     636         351 :     ulong loaded_acc_size = load_transaction_account( runtime, bank, txn_in, txn_out, &txn_out->accounts.keys[i], meta, unknown_acc, i );
     637         351 :     int err = fd_collect_loaded_account(
     638         351 :       runtime,
     639         351 :       txn_out,
     640         351 :       meta,
     641         351 :       loaded_acc_size,
     642         351 :       additional_loaded_account_keys,
     643         351 :       &additional_loaded_account_keys_cnt );
     644         351 :     if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
     645           0 :       return err;
     646           0 :     }
     647         351 :   }
     648             : 
     649             :   /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L662-L686 */
     650         183 :   ushort instr_cnt = TXN( txn_in->txn )->instr_cnt;
     651         309 :   for( ushort i=0; i<instr_cnt; i++ ) {
     652         126 :     fd_txn_instr_t const * instr = &TXN( txn_in->txn )->instr[i];
     653             : 
     654             :     /* Mimicking `load_account()` here with 0-lamport check as well.
     655             :        https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L663-L666 */
     656         126 :     fd_accdb_ref_t * ref = fd_runtime_get_account_at_index(
     657         126 :         txn_in, txn_out, instr->program_id, fd_runtime_account_check_exists );
     658         126 :     if( FD_UNLIKELY( !ref ) ) {
     659           0 :       return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
     660           0 :     }
     661         126 :     fd_accdb_ro_t * ro = fd_accdb_ref_ro( ref );
     662         126 :     if( FD_UNLIKELY( fd_accdb_ref_lamports( ro )==0UL ) ) {
     663           0 :       return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
     664           0 :     }
     665             : 
     666             :     /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L677-L681 */
     667         126 :     fd_pubkey_t const * owner_id = fd_accdb_ref_owner( ro );
     668         126 :     if( FD_UNLIKELY( !fd_pubkey_eq( owner_id, &fd_solana_native_loader_id ) &&
     669         126 :                      !fd_executor_pubkey_is_bpf_loader( owner_id ) ) ) {
     670           0 :       return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
     671           0 :     }
     672         126 :   }
     673             : 
     674         183 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     675         183 : }
     676             : 
     677             : /* https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
     678             : int
     679             : fd_executor_validate_account_locks( fd_bank_t *          bank,
     680         186 :                                     fd_txn_out_t const * txn_out ) {
     681             :   /* Ensure the number of account keys does not exceed the transaction lock limit
     682             :      https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L121 */
     683         186 :   ulong tx_account_lock_limit = get_transaction_account_lock_limit( bank );
     684         186 :   if( FD_UNLIKELY( txn_out->accounts.cnt>tx_account_lock_limit ) ) {
     685           0 :     return FD_RUNTIME_TXN_ERR_TOO_MANY_ACCOUNT_LOCKS;
     686           0 :   }
     687             : 
     688             :   /* Duplicate account check
     689             :      https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L123 */
     690         732 :   for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
     691        1299 :     for( ushort j=(ushort)(i+1U); j<txn_out->accounts.cnt; j++ ) {
     692         753 :       if( FD_UNLIKELY( !memcmp( &txn_out->accounts.keys[i], &txn_out->accounts.keys[j], sizeof(fd_pubkey_t) ) ) ) {
     693           0 :         return FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE;
     694           0 :       }
     695         753 :     }
     696         546 :   }
     697             : 
     698             :   /* https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L124-L126 */
     699         186 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     700         186 : }
     701             : 
     702             : /* https://github.com/anza-xyz/agave/blob/v2.3.1/compute-budget/src/compute_budget_limits.rs#L62-L70 */
     703             : static ulong
     704         183 : fd_get_prioritization_fee( fd_compute_budget_details_t const * compute_budget_details ) {
     705         183 :   uint128 micro_lamport_fee = fd_uint128_sat_mul( compute_budget_details->compute_unit_price, compute_budget_details->compute_unit_limit );
     706         183 :   uint128 fee = fd_uint128_sat_add( micro_lamport_fee, MICRO_LAMPORTS_PER_LAMPORT-1UL ) / MICRO_LAMPORTS_PER_LAMPORT;
     707         183 :   return fee>(uint128)ULONG_MAX ? ULONG_MAX : (ulong)fee;
     708         183 : }
     709             : 
     710             : static void
     711             : fd_executor_calculate_fee( fd_txn_out_t *   txn_out,
     712             :                            fd_txn_t const * txn_descriptor,
     713             :                            uchar const *    payload,
     714             :                            ulong *          ret_execution_fee,
     715         183 :                            ulong *          ret_priority_fee ) {
     716             :   /* The execution fee is just the signature fee. The priority fee
     717             :      is calculated based on the compute budget details.
     718             :      https://github.com/anza-xyz/agave/blob/v3.0.3/fee/src/lib.rs#L65-L84 */
     719             : 
     720             :   // let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature);
     721         183 :   ulong num_signatures = txn_descriptor->signature_cnt;
     722         309 :   for (ushort i=0; i<txn_descriptor->instr_cnt; ++i ) {
     723         126 :     fd_txn_instr_t const * txn_instr  = &txn_descriptor->instr[i];
     724         126 :     fd_pubkey_t *          program_id = &txn_out->accounts.keys[txn_instr->program_id];
     725         126 :     if( !memcmp(program_id->uc, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t)) ||
     726         126 :         !memcmp(program_id->uc, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t)) ||
     727         126 :         !memcmp(program_id->uc, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t)) ) {
     728           0 :       if( !txn_instr->data_sz ) {
     729           0 :         continue;
     730           0 :       }
     731           0 :       uchar const * data = payload + txn_instr->data_off;
     732           0 :       num_signatures     = fd_ulong_sat_add(num_signatures, (ulong)(data[0]));
     733           0 :     }
     734         126 :   }
     735         183 :   *ret_execution_fee = FD_RUNTIME_FEE_STRUCTURE_LAMPORTS_PER_SIGNATURE * num_signatures;
     736         183 :   *ret_priority_fee  = fd_get_prioritization_fee( &txn_out->details.compute_budget );
     737         183 : }
     738             : 
     739             : /* This function creates a rollback account for just the fee payer. Although Agave
     740             :    also sets up rollback accounts for both the fee payer and nonce account here,
     741             :    we already set up the rollback nonce account in earlier sanitization checks. Here
     742             :    we have to capture the entire fee payer record so that if the transaction fails,
     743             :    the fee payer state can be rolled back to it's state pre-transaction, and then debited
     744             :    any transaction fees.
     745             : 
     746             :    Our implementation is slightly different than Agave's in several ways:
     747             :    1. The rollback nonce account has already been set up when checking the transaction age
     748             :    2. When the nonce and fee payer accounts are the same...
     749             :       - Agave copies the data from the rollback nonce account into the rollback fee payer account,
     750             :         and then uses that new fee payer account as the rollback account.
     751             :       - We simply set the rent epoch and lamports of the rollback nonce account (since the other fields
     752             :         of the account do not change)
     753             : 
     754             :    https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/rollback_accounts.rs#L34-L77 */
     755             : static void
     756             : fd_executor_create_rollback_fee_payer_account( fd_runtime_t *      runtime,
     757             :                                                fd_bank_t *         bank,
     758             :                                                fd_txn_in_t const * txn_in,
     759             :                                                fd_txn_out_t *      txn_out,
     760         183 :                                                ulong               total_fee ) {
     761         183 :   fd_pubkey_t * fee_payer_key = &txn_out->accounts.keys[FD_FEE_PAYER_TXN_IDX];
     762             : 
     763             :   /* When setting the data of the rollback fee payer, there is an edge
     764             :      case where the fee payer is the nonce account.  In this case, we
     765             :      can just deduct fees from the nonce account and return, because
     766             :      we save the nonce account in the commit phase anyways. */
     767         183 :   if( FD_UNLIKELY( txn_out->accounts.nonce_idx_in_txn==FD_FEE_PAYER_TXN_IDX ) ) {
     768           0 :     txn_out->accounts.rollback_fee_payer = txn_out->accounts.rollback_nonce;
     769         183 :   } else {
     770         183 :     uchar * fee_payer_data = txn_out->accounts.rollback_fee_payer_mem;
     771         183 :     txn_out->accounts.rollback_fee_payer = fd_type_pun( fee_payer_data );
     772             : 
     773         183 :     fd_account_meta_t const * meta = NULL;
     774         183 :     if( FD_UNLIKELY( txn_in->bundle.is_bundle ) ) {
     775          75 :       int is_found = 0;
     776         120 :       for( ulong i=txn_in->bundle.prev_txn_cnt; i>0UL && !is_found; i-- ) {
     777          45 :         fd_txn_out_t const * prev_txn_out = txn_in->bundle.prev_txn_outs[ i-1 ];
     778          45 :         for( ushort j=0UL; j<prev_txn_out->accounts.cnt; j++ ) {
     779          45 :           if( fd_pubkey_eq( &prev_txn_out->accounts.keys[ j ], fee_payer_key ) && prev_txn_out->accounts.is_writable[j] ) {
     780          45 :             meta = prev_txn_out->accounts.account[j].meta;
     781          45 :             is_found = 1;
     782          45 :             break;
     783          45 :           }
     784          45 :         }
     785          45 :       }
     786          75 :     }
     787             : 
     788         183 :     if( meta ) {
     789             :       /* Account modified in a previous transaction */
     790          45 :       fd_memcpy( fee_payer_data, (uchar *)meta, sizeof(fd_account_meta_t) + meta->dlen );
     791         138 :     } else {
     792             :       /* Copy from account database */
     793         138 :       fd_funk_txn_xid_t xid = fd_bank_xid( bank );
     794         138 :       fd_accdb_ro_t fee_payer_ro[1];
     795         138 :       if( FD_UNLIKELY( !fd_accdb_open_ro( runtime->accdb, fee_payer_ro, &xid, fee_payer_key ) ) ) {
     796           0 :         FD_BASE58_ENCODE_32_BYTES( fee_payer_key->uc, fee_payer_key_b58 );
     797           0 :         FD_LOG_CRIT(( "accdb query for fee payer account failed: xid=%lu:%lu address=%s", xid.ul[0], xid.ul[1], fee_payer_key_b58 ));
     798           0 :       }
     799         138 :       fd_memcpy( fee_payer_data,
     800         138 :                  fee_payer_ro->meta,
     801         138 :                  sizeof(fd_account_meta_t) );
     802         138 :       fd_memcpy( fee_payer_data+sizeof(fd_account_meta_t),
     803         138 :                  fd_accdb_ref_data_const( fee_payer_ro ),
     804         138 :                  fd_accdb_ref_data_sz   ( fee_payer_ro ) );
     805         138 :       fd_accdb_close_ro( runtime->accdb, fee_payer_ro );
     806         138 :     }
     807         183 :   }
     808             : 
     809             :   /* Deduct the transaction fees from the rollback account. Because of prior checks, this should never fail. */
     810         183 :   if( FD_UNLIKELY( fd_account_meta_checked_sub_lamports( txn_out->accounts.rollback_fee_payer, total_fee ) ) ) {
     811           0 :     FD_LOG_ERR(( "fd_executor_create_rollback_fee_payer_account(): failed to deduct fees from rollback account" ));
     812           0 :   }
     813         183 : }
     814             : 
     815             : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L557-L634 */
     816             : int
     817             : fd_executor_validate_transaction_fee_payer( fd_runtime_t *      runtime,
     818             :                                             fd_bank_t *         bank,
     819             :                                             fd_txn_in_t const * txn_in,
     820         183 :                                             fd_txn_out_t *      txn_out ) {
     821             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L574-L580 */
     822         183 :   fd_accdb_ref_t * ref = fd_runtime_get_account_at_index(
     823         183 :       txn_in, txn_out, FD_FEE_PAYER_TXN_IDX, fd_runtime_account_check_fee_payer_writable );
     824         183 :   if( FD_UNLIKELY( !ref ) ) {
     825           0 :     FD_BASE58_ENCODE_32_BYTES( txn_out->accounts.keys[FD_FEE_PAYER_TXN_IDX].uc, pubkey_b58 );
     826           0 :     FD_LOG_DEBUG(( "Fee payer isn't writable %s", pubkey_b58 ));
     827           0 :     return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     828           0 :   }
     829             : 
     830         183 :   fd_pubkey_t *       fee_payer_key  = &txn_out->accounts.keys[FD_FEE_PAYER_TXN_IDX];
     831         183 :   fd_account_meta_t * fee_payer_meta = txn_out->accounts.account[FD_FEE_PAYER_TXN_IDX].meta;
     832             : 
     833             :   /* Calculate transaction fees
     834             :      https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L597-L606 */
     835         183 :   ulong execution_fee = 0UL;
     836         183 :   ulong priority_fee  = 0UL;
     837             : 
     838         183 :   fd_executor_calculate_fee( txn_out, TXN( txn_in->txn ), txn_in->txn->payload, &execution_fee, &priority_fee );
     839         183 :   ulong total_fee = fd_ulong_sat_add( execution_fee, priority_fee );
     840             : 
     841             :   /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L609-L616 */
     842         183 :   int err = fd_validate_fee_payer( fee_payer_key, fee_payer_meta, &bank->f.rent, total_fee );
     843         183 :   if( FD_UNLIKELY( err ) ) return err;
     844             : 
     845             :   /* Create the rollback fee payer account
     846             :      https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L620-L626 */
     847         183 :   fd_executor_create_rollback_fee_payer_account( runtime, bank, txn_in, txn_out, total_fee );
     848             : 
     849             :   /* Set the starting lamports (to avoid unbalanced lamports issues in instruction execution) */
     850         183 :   runtime->accounts.starting_lamports[FD_FEE_PAYER_TXN_IDX] = fee_payer_meta->lamports;
     851             : 
     852         183 :   txn_out->details.execution_fee = execution_fee;
     853         183 :   txn_out->details.priority_fee  = priority_fee;
     854             : 
     855         183 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     856         183 : }
     857             : 
     858             : /* Simply unpacks the account keys from the serialized transaction and
     859             :    sets them in the txn_out. */
     860             : void
     861             : fd_executor_setup_txn_account_keys( fd_txn_in_t const * txn_in,
     862         189 :                                     fd_txn_out_t *      txn_out ) {
     863         189 :   txn_out->accounts.cnt = (uchar)TXN( txn_in->txn )->acct_addr_cnt;
     864         189 :   fd_pubkey_t * tx_accs = (fd_pubkey_t *)((uchar *)txn_in->txn->payload + TXN( txn_in->txn )->acct_addr_off);
     865             : 
     866             :   // Set up accounts in the transaction body and perform checks
     867         738 :   for( ulong i = 0UL; i < TXN( txn_in->txn )->acct_addr_cnt; i++ ) {
     868         549 :     txn_out->accounts.keys[i] = tx_accs[i];
     869         549 :   }
     870         189 : }
     871             : 
     872             : /* Resolves any address lookup tables referenced in the transaction and adds
     873             :    them to the transaction's account keys. Returns 0 on success or if the transaction
     874             :    is a legacy transaction, and an FD_RUNTIME_TXN_ERR_* on failure. */
     875             : int
     876             : fd_executor_setup_txn_alut_account_keys( fd_runtime_t *      runtime,
     877             :                                          fd_bank_t *         bank,
     878             :                                          fd_txn_in_t const * txn_in,
     879         186 :                                          fd_txn_out_t *      txn_out ) {
     880         186 :   if( TXN( txn_in->txn )->transaction_version == FD_TXN_V0 ) {
     881             :     /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/runtime/src/bank/address_lookup_table.rs#L44-L48 */
     882         111 :     fd_sysvar_cache_t const * sysvar_cache = &bank->f.sysvar_cache;
     883         111 :     fd_slot_hashes_t slot_hashes_view[1];
     884         111 :     if( FD_UNLIKELY( !fd_sysvar_cache_slot_hashes_view( sysvar_cache, slot_hashes_view ) ) ) {
     885           0 :       FD_LOG_DEBUG(( "fd_executor_setup_txn_alut_account_keys(): failed to get slot hashes" ));
     886           0 :       return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
     887           0 :     }
     888         111 :     fd_funk_txn_xid_t xid       = fd_bank_xid( bank );
     889         111 :     fd_acct_addr_t *  accts_alt = (fd_acct_addr_t *) fd_type_pun( &txn_out->accounts.keys[txn_out->accounts.cnt] );
     890         111 :     int err = fd_runtime_load_txn_address_lookup_tables( txn_in,
     891         111 :                                                          TXN( txn_in->txn ),
     892         111 :                                                          txn_in->txn->payload,
     893         111 :                                                          runtime->accdb,
     894         111 :                                                          &xid,
     895         111 :                                                          bank->f.slot,
     896         111 :                                                          slot_hashes_view,
     897         111 :                                                          accts_alt );
     898         111 :     txn_out->accounts.cnt += TXN( txn_in->txn )->addr_table_adtl_cnt;
     899         111 :     if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) return err;
     900             : 
     901         111 :   }
     902         186 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     903         186 : }
     904             : 
     905             : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L319-L357 */
     906             : static inline int
     907             : fd_txn_ctx_push( fd_runtime_t *      runtime,
     908             :                  fd_txn_in_t const * txn_in,
     909             :                  fd_txn_out_t *      txn_out,
     910        2955 :                  fd_instr_info_t *   instr ) {
     911             :   /* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
     912             :      https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L327-L328 */
     913        2955 :   ulong starting_lamports_h = 0UL;
     914        2955 :   ulong starting_lamports_l = 0UL;
     915        2955 :   int err = fd_instr_info_sum_account_lamports( instr,
     916        2955 :                                                 txn_out,
     917        2955 :                                                 &starting_lamports_h,
     918        2955 :                                                 &starting_lamports_l );
     919        2955 :   if( FD_UNLIKELY( err ) ) {
     920           0 :     return err;
     921           0 :   }
     922        2955 :   instr->starting_lamports_h = starting_lamports_h;
     923        2955 :   instr->starting_lamports_l = starting_lamports_l;
     924             : 
     925             :   /* Check that the caller's lamport sum has not changed.
     926             :      https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L329-L340 */
     927        2955 :   if( runtime->instr.stack_sz>0 ) {
     928             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L330 */
     929        2805 :     fd_exec_instr_ctx_t const * caller_instruction_context = &runtime->instr.stack[ runtime->instr.stack_sz-1 ];
     930             : 
     931             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L331-L332 */
     932        2805 :     ulong original_caller_lamport_sum_h = caller_instruction_context->instr->starting_lamports_h;
     933        2805 :     ulong original_caller_lamport_sum_l = caller_instruction_context->instr->starting_lamports_l;
     934             : 
     935             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L333-L334 */
     936        2805 :     ulong current_caller_lamport_sum_h = 0UL;
     937        2805 :     ulong current_caller_lamport_sum_l = 0UL;
     938        2805 :     int err = fd_instr_info_sum_account_lamports( caller_instruction_context->instr,
     939        2805 :                                                   caller_instruction_context->txn_out,
     940        2805 :                                                   &current_caller_lamport_sum_h,
     941        2805 :                                                   &current_caller_lamport_sum_l );
     942        2805 :     if( FD_UNLIKELY( err ) ) {
     943           0 :       return err;
     944           0 :     }
     945             : 
     946             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L335-L339 */
     947        2805 :     if( FD_UNLIKELY( current_caller_lamport_sum_h!=original_caller_lamport_sum_h ||
     948        2805 :                      current_caller_lamport_sum_l!=original_caller_lamport_sum_l ) ) {
     949         303 :       return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
     950         303 :     }
     951        2805 :   }
     952             : 
     953             :   /* Note that we don't update the trace length here - since the caller
     954             :      allocates out of the trace array, they are also responsible for
     955             :      incrementing the trace length variable.
     956             :      https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L347-L351 */
     957        2652 :   if( FD_UNLIKELY( runtime->instr.trace_length>FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
     958           0 :     return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
     959           0 :   }
     960             : 
     961             :   /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L352-L356 */
     962        2652 :   if( FD_UNLIKELY( runtime->instr.stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
     963          48 :     return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
     964          48 :   }
     965        2604 :   runtime->instr.stack_sz++;
     966             : 
     967             :   /* A beloved refactor moves sysvar instructions updating to the instruction level as of v2.2.12...
     968             :      https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L396-L407 */
     969        2604 :   int idx = fd_runtime_find_index_of_account( txn_out, &fd_sysvar_instructions_id );
     970        2604 :   if( FD_UNLIKELY( idx!=-1 ) ) {
     971             :     /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L397-L400 */
     972           0 :     fd_accdb_ref_t * ref = fd_runtime_get_account_at_index( txn_in, txn_out, (ushort)idx, NULL );
     973           0 :     if( FD_UNLIKELY( !ref ) ) {
     974           0 :       return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     975           0 :     }
     976             : 
     977           0 :     ulong refcnt = runtime->accounts.refcnt[idx];
     978             :     /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L401-L402 */
     979           0 :     if( FD_UNLIKELY( refcnt!=0UL ) ) {
     980           0 :       return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED;
     981           0 :     }
     982           0 :     refcnt++;
     983             : 
     984             :     /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L403-L406 */
     985           0 :     fd_sysvar_instructions_update_current_instr_idx( txn_out->accounts.account[idx].meta, (ushort)runtime->instr.current_idx );
     986           0 :     refcnt--;
     987           0 :   }
     988             : 
     989        2604 :   return FD_EXECUTOR_INSTR_SUCCESS;
     990        2604 : }
     991             : 
     992             : /* Pushes a new instruction onto the instruction stack and trace. This check loops through all instructions in the current call stack
     993             :    and checks for reentrancy violations. If successful, simply increments the instruction stack and trace size and returns. It is
     994             :    the responsibility of the caller to populate the newly pushed instruction fields, which are undefined otherwise.
     995             : 
     996             :    https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L246-L290 */
     997             : int
     998             : fd_instr_stack_push( fd_runtime_t *      runtime,
     999             :                      fd_txn_in_t const * txn_in,
    1000             :                      fd_txn_out_t *      txn_out,
    1001        2955 :                      fd_instr_info_t *   instr ) {
    1002             :   /* Agave keeps a vector of vectors called program_indices that stores the program_id index for each instruction within the transaction.
    1003             :      https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L347-L402
    1004             :      If and only if the program_id is the native loader, then the vector for respective specific instruction (account_indices) is empty.
    1005             :      https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L350-L358
    1006             :      While trying to push a new instruction onto the instruction stack, if the vector for the respective instruction is empty, Agave throws UnsupportedProgramId
    1007             :      https://github.com/anza-xyz/agave/blob/v2.1.7/program-runtime/src/invoke_context.rs#L253-L255
    1008             :      The only way for the vector to be empty is if the program_id is the native loader, so we can a program_id check here
    1009             :      */
    1010             : 
    1011             :   /* https://github.com/anza-xyz/agave/blob/v2.2.0/program-runtime/src/invoke_context.rs#L250-L252 */
    1012        2955 :   fd_pubkey_t const * program_id_pubkey = NULL;
    1013        2955 :   int err = fd_runtime_get_key_of_account_at_index( txn_out,
    1014        2955 :                                                     instr->program_id,
    1015        2955 :                                                     &program_id_pubkey );
    1016        2955 :   if( FD_UNLIKELY( err ) ) {
    1017           0 :     return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
    1018           0 :   }
    1019             : 
    1020             :   /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L256-L286 */
    1021        2955 :   if( runtime->instr.stack_sz ) {
    1022             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L261-L285 */
    1023        2805 :     uchar contains = 0;
    1024        2805 :     uchar is_last  = 0;
    1025             : 
    1026             :     // Checks all previous instructions in the stack for reentrancy
    1027        5802 :     for( uchar level=0; level<runtime->instr.stack_sz; level++ ) {
    1028        2997 :       fd_exec_instr_ctx_t * instr_ctx = &runtime->instr.stack[level];
    1029             :       // Optimization: compare program id index instead of pubkey since account keys are unique
    1030        2997 :       if( instr->program_id == instr_ctx->instr->program_id ) {
    1031             :         // Reentrancy not allowed unless caller is calling itself
    1032        2997 :         if( level == runtime->instr.stack_sz-1 ) {
    1033        2805 :           is_last = 1;
    1034        2805 :         }
    1035        2997 :         contains = 1;
    1036        2997 :       }
    1037        2997 :     }
    1038             :     /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L282-L285 */
    1039        2805 :     if( FD_UNLIKELY( contains && !is_last ) ) {
    1040           0 :       return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
    1041           0 :     }
    1042        2805 :   }
    1043             :   /* "Push" a new instruction onto the stack by simply incrementing the stack and trace size counters
    1044             :      https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L289 */
    1045        2955 :   return fd_txn_ctx_push( runtime, txn_in, txn_out, instr );
    1046        2955 : }
    1047             : 
    1048             : /* Pops an instruction from the instruction stack. Agave's implementation performs instruction balancing checks every time pop is called,
    1049             :    but error codes returned from `pop` are only used if the program's execution was successful. Therefore, we can optimize our code by only
    1050             :    checking for unbalanced instructions if the program execution was successful within fd_execute_instr.
    1051             : 
    1052             :    https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L293-L298 */
    1053             : int
    1054             : fd_instr_stack_pop( fd_runtime_t *          runtime,
    1055             :                     fd_txn_out_t *          txn_out,
    1056        2604 :                     fd_instr_info_t const * instr ) {
    1057             :   /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L362-L364 */
    1058        2604 :   if( FD_UNLIKELY( runtime->instr.stack_sz==0 ) ) {
    1059           0 :     return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
    1060           0 :   }
    1061        2604 :   runtime->instr.stack_sz--;
    1062             : 
    1063             :   /* Verify all executable accounts have no outstanding refs
    1064             :      https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L367-L371 */
    1065       12030 :   for( ushort i=0; i<instr->acct_cnt; i++ ) {
    1066        9426 :     ushort idx_in_txn = instr->accounts[i].index_in_transaction;
    1067        9426 :     fd_account_meta_t const * meta = txn_out->accounts.account[ idx_in_txn ].meta;
    1068        9426 :     ulong refcnt = runtime->accounts.refcnt[idx_in_txn];
    1069        9426 :     if( FD_UNLIKELY( meta->executable && refcnt!=0UL ) ) {
    1070           0 :       return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING;
    1071           0 :     }
    1072        9426 :   }
    1073             : 
    1074             :   /* Verify lamports are balanced before and after instruction
    1075             :      https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L366-L380 */
    1076        2604 :   ulong ending_lamports_h = 0UL;
    1077        2604 :   ulong ending_lamports_l = 0UL;
    1078        2604 :   int err = fd_instr_info_sum_account_lamports( instr,
    1079        2604 :                                                 txn_out,
    1080        2604 :                                                 &ending_lamports_h,
    1081        2604 :                                                 &ending_lamports_l );
    1082        2604 :   if( FD_UNLIKELY( err ) ) {
    1083           0 :     return err;
    1084           0 :   }
    1085        2604 :   if( FD_UNLIKELY( ending_lamports_l != instr->starting_lamports_l || ending_lamports_h != instr->starting_lamports_h ) ) {
    1086           0 :    return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
    1087           0 :   }
    1088             : 
    1089        2604 :   return FD_EXECUTOR_INSTR_SUCCESS;
    1090        2604 : }
    1091             : 
    1092             : /* This function mimics Agave's `.and(self.pop())` functionality,
    1093             :    where we always pop the instruction stack no matter what the error code is.
    1094             :    https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L480 */
    1095             : int
    1096             : fd_execute_instr_end( fd_exec_instr_ctx_t *   instr_ctx,
    1097             :                       fd_instr_info_t const * instr,
    1098        2604 :                       int                     instr_exec_result ) {
    1099        2604 :   int stack_pop_err = fd_instr_stack_pop( instr_ctx->runtime, instr_ctx->txn_out, instr );
    1100             : 
    1101             :   /* Only report the stack pop error on success */
    1102        2604 :   if( FD_UNLIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS && stack_pop_err ) ) {
    1103           0 :     FD_TXN_PREPARE_ERR_OVERWRITE( instr_ctx->txn_out );
    1104           0 :     FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, stack_pop_err, instr_ctx->txn_out->err.exec_err_idx );
    1105           0 :     instr_exec_result = stack_pop_err;
    1106           0 :   }
    1107             : 
    1108        2604 :   return instr_exec_result;
    1109        2604 : }
    1110             : 
    1111             : int
    1112             : fd_execute_instr( fd_runtime_t *      runtime,
    1113             :                   fd_bank_t *         bank,
    1114             :                   fd_txn_in_t const * txn_in,
    1115             :                   fd_txn_out_t *      txn_out,
    1116        2955 :                   fd_instr_info_t *   instr ) {
    1117        2955 :   fd_sysvar_cache_t const * sysvar_cache = &bank->f.sysvar_cache;
    1118        2955 :   int instr_exec_result = fd_instr_stack_push( runtime, txn_in, txn_out, instr );
    1119        2955 :   if( FD_UNLIKELY( instr_exec_result ) ) {
    1120         351 :     FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
    1121         351 :     FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
    1122         351 :     return instr_exec_result;
    1123         351 :   }
    1124             : 
    1125             :   /* `process_executable_chain()`
    1126             :       https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L512-L619 */
    1127        2604 :   fd_exec_instr_ctx_t * ctx = &runtime->instr.stack[ runtime->instr.stack_sz - 1 ];
    1128        2604 :   *ctx = (fd_exec_instr_ctx_t) {
    1129        2604 :     .instr        = instr,
    1130        2604 :     .sysvar_cache = sysvar_cache,
    1131        2604 :     .runtime      = runtime,
    1132        2604 :     .txn_in       = txn_in,
    1133        2604 :     .txn_out      = txn_out,
    1134        2604 :     .bank         = bank,
    1135        2604 :   };
    1136        2604 :   fd_base58_encode_32( txn_out->accounts.keys[ instr->program_id ].uc, NULL, ctx->program_id_base58 );
    1137             : 
    1138             :   /* Look up the native program. We check for precompiles within the lookup function as well.
    1139             :      https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/message_processor.rs#L88 */
    1140        2604 :   fd_exec_instr_fn_t native_prog_fn;
    1141        2604 :   uchar              is_precompile;
    1142        2604 :   int                err = fd_executor_lookup_native_program( &txn_out->accounts.keys[ instr->program_id ],
    1143        2604 :                                                               txn_out->accounts.account[ instr->program_id ].meta,
    1144        2604 :                                                               bank,
    1145        2604 :                                                               &native_prog_fn,
    1146        2604 :                                                               &is_precompile );
    1147             : 
    1148        2604 :   if( FD_UNLIKELY( err ) ) {
    1149           0 :     FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
    1150           0 :     FD_TXN_ERR_FOR_LOG_INSTR( txn_out, err, txn_out->err.exec_err_idx );
    1151           0 :     return fd_execute_instr_end( ctx, instr, err );
    1152           0 :   }
    1153             : 
    1154        2604 :   if( FD_LIKELY( native_prog_fn!=NULL ) ) {
    1155             :     /* If this branch is taken, we've found an entrypoint to execute. */
    1156        2604 :     fd_log_collector_program_invoke( ctx );
    1157             : 
    1158             :     /* Only reset the return data when executing a native builtin program (not a precompile)
    1159             :        https://github.com/anza-xyz/agave/blob/v2.1.6/program-runtime/src/invoke_context.rs#L536-L537 */
    1160        2604 :     if( FD_LIKELY( !is_precompile ) ) {
    1161        2604 :       txn_out->details.return_data.len = 0;
    1162        2604 :     }
    1163             : 
    1164             :     /* Execute the native program. */
    1165        2604 :     instr_exec_result = native_prog_fn( ctx );
    1166        2604 :   } else {
    1167             :     /* Unknown program. In this case specifically, we should not log the program id. */
    1168           0 :     instr_exec_result = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
    1169           0 :     FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
    1170           0 :     FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
    1171           0 :     return fd_execute_instr_end( ctx, instr, instr_exec_result );
    1172           0 :   }
    1173             : 
    1174        2604 :   if( FD_LIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1175             :     /* Log success */
    1176        2358 :     fd_log_collector_program_success( ctx );
    1177        2358 :   } else {
    1178             :     /* Log failure cases.
    1179             :        We assume that the correct type of error is stored in ctx.
    1180             :        Syscalls are expected to log when the error is generated, while
    1181             :        native programs will be logged here.
    1182             :        (This is because syscall errors often carry data with them.)
    1183             : 
    1184             :        TODO: This hackily handles cases where the exec_err and exec_err_kind
    1185             :        is not set yet. We should change our native programs to set
    1186             :        this in their respective processors. */
    1187         246 :     if( !txn_out->err.exec_err ) {
    1188         165 :       FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
    1189         165 :       FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
    1190         165 :       fd_log_collector_program_failure( ctx );
    1191         165 :     } else {
    1192          81 :       fd_log_collector_program_failure( ctx );
    1193          81 :       FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
    1194          81 :       FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
    1195          81 :     }
    1196         246 :   }
    1197             : 
    1198        2604 :   return fd_execute_instr_end( ctx, instr, instr_exec_result );
    1199        2604 : }
    1200             : 
    1201             : static void
    1202             : fd_executor_reclaim_account( fd_account_meta_t * meta,
    1203         270 :                              ulong               slot ) {
    1204         270 :   if( FD_UNLIKELY( meta->lamports==0UL ) ) {
    1205          18 :     memset( meta, 0, sizeof(fd_account_meta_t) );
    1206          18 :   }
    1207         270 :   meta->slot = slot;
    1208         270 : }
    1209             : 
    1210             : static void
    1211             : fd_executor_setup_txn_account( fd_runtime_t *      runtime,
    1212             :                                fd_bank_t *         bank,
    1213             :                                fd_txn_in_t const * txn_in,
    1214             :                                fd_txn_out_t *      txn_out,
    1215             :                                ushort              idx,
    1216             :                                uchar * *           writable_accs_mem,
    1217         546 :                                ulong *             writable_accs_idx_out ) {
    1218             :   /* To setup a transaction account, we need to first retrieve a
    1219             :      read-only handle to the account from the database. */
    1220             : 
    1221         546 :   fd_pubkey_t *   address  = &txn_out->accounts.keys[ idx ];
    1222         546 :   fd_accdb_rw_t * ref_slot = &txn_out->accounts.account[ idx ];
    1223             : 
    1224         546 :   fd_accdb_rw_t * account = NULL;
    1225         546 :   int is_found_in_bundle = 0;
    1226         546 :   if( txn_in->bundle.is_bundle ) {
    1227             :     /* If we are in a bundle, that means that the latest version of an
    1228             :        account may be a transaction account from a previous transaction
    1229             :        and not in the accounts database.  This means we have to
    1230             :        reference the previous transaction's account.  Because we are in
    1231             :        a bundle, we know that the transaction accounts for all previous
    1232             :        bundle transactions are valid.  We will also assume that the
    1233             :        transactions are in execution order.
    1234             : 
    1235             :        TODO: This lookup can be made more performant by using a map
    1236             :        from pubkey to the bundle transaction index and only inserting
    1237             :        or updating when the account is writable. */
    1238             : 
    1239         261 :     for( ulong i=txn_in->bundle.prev_txn_cnt; i>0UL && !is_found_in_bundle; i-- ) {
    1240         102 :       fd_txn_out_t * prev_txn_out = txn_in->bundle.prev_txn_outs[ i-1 ];
    1241         174 :       for( ushort j=0UL; j<prev_txn_out->accounts.cnt; j++ ) {
    1242         159 :         if( fd_pubkey_eq( &prev_txn_out->accounts.keys[ j ], address ) && prev_txn_out->accounts.is_writable[j] ) {
    1243             :           /* Found the account in a previous transaction. Move ownership
    1244             :              of reference from previous transaction to this one. */
    1245          87 :           fd_memcpy( ref_slot, prev_txn_out->accounts.account[ j ].ref, sizeof(fd_accdb_rw_t) );
    1246          87 :           account = ref_slot;
    1247          87 :           is_found_in_bundle = 1;
    1248             : 
    1249             :           /* If the account being carried forward from the previous txn
    1250             :              had queued an update to the vote/stakes caches and is
    1251             :              writable in the new transaction, unmark the update to avoid
    1252             :              double-counting the update. */
    1253          87 :           if( FD_UNLIKELY( txn_out->accounts.is_writable[ idx ] ) ) {
    1254          78 :             if( prev_txn_out->accounts.stake_update[ j ] ) {
    1255           3 :               prev_txn_out->accounts.stake_update[ j ] = 0;
    1256           3 :               txn_out->accounts.stake_update[ idx ] = 1;
    1257           3 :             }
    1258          78 :             if( prev_txn_out->accounts.vote_update[ j ] ) {
    1259           0 :               prev_txn_out->accounts.vote_update[ j ] = 0;
    1260           0 :               txn_out->accounts.vote_update[ idx ] = 1;
    1261           0 :             }
    1262          78 :           }
    1263          87 :           break;
    1264          87 :         }
    1265         159 :       }
    1266         102 :     }
    1267         159 :   }
    1268             : 
    1269         546 :   if( FD_LIKELY( !account ) ) {
    1270         459 :     fd_funk_txn_xid_t xid = fd_bank_xid( bank );
    1271         459 :     account = (fd_accdb_rw_t *)fd_accdb_open_ro( runtime->accdb, ref_slot->ro, &xid, address );
    1272             :     /* creates a database reference, which is explicitly dropped here
    1273             :        or in commit/cancel */
    1274         459 :   }
    1275             : 
    1276         546 :   if( txn_out->accounts.is_writable[ idx ] ) {
    1277             :     /* If the account is writable or a fee payer, then we need to create
    1278             :        staging regions for the account.  If the account exists, copy the
    1279             :        account data into the staging area; otherwise, initialize a new
    1280             :        metadata. */
    1281         363 :     uchar * new_raw_data = writable_accs_mem[ *writable_accs_idx_out ];
    1282         363 :     ulong   dlen         = !!account ? fd_accdb_ref_data_sz( (fd_accdb_ro_t *)account ) : 0UL;
    1283         363 :     (*writable_accs_idx_out)++;
    1284             : 
    1285         363 :     if( FD_LIKELY( account ) ) {
    1286             :       /* Create copy of account, release reference of original */
    1287         357 :       fd_memcpy( new_raw_data, account->meta, sizeof(fd_account_meta_t)+dlen );
    1288         357 :       fd_accdb_close_ro( runtime->accdb, (fd_accdb_ro_t *)account );
    1289         357 :     } else {
    1290             :       /* Account did not exist, set up metadata */
    1291           6 :       fd_account_meta_init( (fd_account_meta_t *)new_raw_data );
    1292           6 :     }
    1293             : 
    1294         363 :     account = fd_accdb_rw_init_nodb(
    1295         363 :         (fd_accdb_rw_t *)ref_slot,
    1296         363 :         address,
    1297         363 :         (fd_account_meta_t *)new_raw_data,
    1298         363 :         FD_RUNTIME_ACC_SZ_MAX
    1299         363 :     );
    1300             : 
    1301         363 :   } else {
    1302             :     /* If the account is not writable, then we can simply initialize
    1303             :        the txn account with the read-only accountsdb record.  However,
    1304             :        if the account does not exist, we need to initialize a new
    1305             :        metadata. */
    1306         183 :     if( FD_UNLIKELY( fd_pubkey_eq( address, &fd_sysvar_instructions_id ) ) ) {
    1307           0 :       fd_account_meta_t * meta = fd_account_meta_init( (void *)runtime->accounts.sysvar_instructions_mem );
    1308           0 :       account = (fd_accdb_rw_t *)fd_accdb_ro_init_nodb( (fd_accdb_ro_t *)ref_slot, address, meta );
    1309         183 :     } else if( FD_LIKELY( account && !is_found_in_bundle ) ) {
    1310             :       /* transfer ownership of reference to runtime struct
    1311             :          account is freed in cancel/commit */
    1312         162 :     } else if( FD_LIKELY( account && is_found_in_bundle ) ) {
    1313             :       /* If the account is found in the bundle and marked read-only we
    1314             :          just need to initialize a reference to the account that doesn't
    1315             :          reference the database. */
    1316           9 :       account = (fd_accdb_rw_t *)fd_accdb_ro_init_nodb( (fd_accdb_ro_t *)ref_slot, address, account->meta );
    1317          12 :     } else {
    1318          12 :       account = (fd_accdb_rw_t *)fd_accdb_ro_init_nodb( (fd_accdb_ro_t *)ref_slot, address, &FD_ACCOUNT_META_DEFAULT );
    1319          12 :     }
    1320         183 :   }
    1321             : 
    1322         546 :   runtime->accounts.starting_lamports[idx] = fd_accdb_ref_lamports( account->ro );
    1323         546 :   runtime->accounts.starting_dlen[idx]     = fd_accdb_ref_data_sz ( account->ro );
    1324         546 :   runtime->accounts.refcnt[idx]            = 0UL;
    1325         546 : }
    1326             : 
    1327             : static void
    1328             : fd_executor_setup_executable_account( fd_runtime_t *            runtime,
    1329             :                                       fd_bank_t *               bank,
    1330             :                                       fd_txn_in_t const *       txn_in,
    1331             :                                       fd_account_meta_t const * program_meta,
    1332          15 :                                       ushort *                  executable_idx ) {
    1333          15 :   fd_bpf_state_t program_loader_state[1];
    1334          15 :   int err = fd_bpf_loader_program_get_state( program_meta, program_loader_state );
    1335          15 :   if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1336           0 :     return;
    1337           0 :   }
    1338             : 
    1339          15 :   if( program_loader_state->discriminant!=FD_BPF_STATE_PROGRAM ) {
    1340           9 :     return;
    1341           9 :   }
    1342             : 
    1343             :   /* Attempt to load the program data account from funk.  This prevents
    1344             :      any unknown program data accounts from getting loaded into the
    1345             :      executable accounts list.  If such a program is invoked, the call
    1346             :      will fail at the instruction execution level since the programdata
    1347             :      account will not exist within the executable accounts list. */
    1348           6 :   fd_pubkey_t *     programdata_acc = &program_loader_state->inner.program.programdata_address;
    1349           6 :   fd_funk_txn_xid_t xid             = fd_bank_xid( bank );
    1350             : 
    1351           6 :   fd_accdb_ro_t * ro = &runtime->accounts.executable[ *executable_idx ];
    1352             : 
    1353           6 :   int is_found = 0;
    1354           6 :   if( FD_UNLIKELY( txn_in->bundle.is_bundle ) ) {
    1355           9 :     for( ulong i=txn_in->bundle.prev_txn_cnt; i>0UL && !is_found; i-- ) {
    1356           3 :       fd_txn_out_t * prev_txn_out = txn_in->bundle.prev_txn_outs[ i-1 ];
    1357           9 :       for( ushort j=0; j<prev_txn_out->accounts.cnt; j++ ) {
    1358           9 :         if( fd_pubkey_eq( &prev_txn_out->accounts.keys[ j ], programdata_acc ) && prev_txn_out->accounts.is_writable[ j ] ) {
    1359             :           /* If the most recent transaction shows the account closed,
    1360             :              return early so the loaded size is not increased. */
    1361           3 :           fd_account_meta_t * meta = prev_txn_out->accounts.account[ j ].meta;
    1362           3 :           if( FD_UNLIKELY( meta->lamports==0UL ) ) return;
    1363           3 :           ro = fd_accdb_ro_init_nodb( ro, programdata_acc, meta );
    1364           3 :           is_found = 1;
    1365           3 :           break;
    1366           3 :         }
    1367           9 :       }
    1368           3 :     }
    1369           6 :   }
    1370             : 
    1371           6 :   if( FD_LIKELY( !is_found ) ) {
    1372           3 :     ro = fd_accdb_open_ro( runtime->accdb, ro, &xid, programdata_acc );
    1373           3 :   }
    1374           6 :   if( FD_LIKELY( ro ) ) (*executable_idx)++;
    1375           6 : }
    1376             : 
    1377             : void
    1378             : fd_executor_setup_accounts_for_txn( fd_runtime_t *      runtime,
    1379             :                                     fd_bank_t *         bank,
    1380             :                                     fd_txn_in_t const * txn_in,
    1381         186 :                                     fd_txn_out_t *      txn_out ) {
    1382             : 
    1383             :   /* At this point, the total number of writable accounts in the
    1384             :      transaction is known.  We can now attempt to get the required
    1385             :      amount of memory from the account memory pool. */
    1386             : 
    1387         186 :   ushort writable_account_cnt = 0U;
    1388         732 :   for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
    1389         546 :     if( fd_runtime_account_is_writable_idx( txn_in, txn_out, i ) ) {
    1390         363 :       txn_out->accounts.is_writable[ i ] = 1;
    1391         363 :       writable_account_cnt++;
    1392         363 :     } else {
    1393         183 :       txn_out->accounts.is_writable[ i ] = 0;
    1394         183 :     }
    1395         546 :   }
    1396             : 
    1397             :   /* At this point we know which accounts are writable, but we don't
    1398             :      know if we will need to create an account for the rollback fee
    1399             :      payer or nonce account.  To avoid a potential deadlock, we want to
    1400             :      request the worst-case number of accounts (# writable accounts + 2
    1401             :      rollback accounts) for the transaction in one call to
    1402             :      fd_acc_pool_acquire. */
    1403             : 
    1404         186 :   ulong   writable_accs_idx = 0UL;
    1405         186 :   uchar * writable_accs_mem[ MAX_TX_ACCOUNT_LOCKS + 2UL ];
    1406         186 :   fd_acc_pool_acquire( runtime->acc_pool, writable_account_cnt + 2UL, writable_accs_mem );
    1407         186 :   txn_out->accounts.rollback_fee_payer_mem = writable_accs_mem[ writable_account_cnt ];
    1408         186 :   txn_out->accounts.rollback_nonce_mem     = writable_accs_mem[ writable_account_cnt+1UL ];
    1409             : 
    1410         186 :   ushort executable_idx = 0U;
    1411         732 :   for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
    1412         546 :     fd_executor_setup_txn_account( runtime, bank, txn_in, txn_out, i, writable_accs_mem, &writable_accs_idx );
    1413         546 :     fd_account_meta_t * meta = txn_out->accounts.account[ i ].meta;
    1414             : 
    1415         546 :     if( FD_UNLIKELY( meta && memcmp( meta->owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
    1416          15 :       fd_executor_setup_executable_account( runtime, bank, txn_in, meta, &executable_idx );
    1417          15 :     }
    1418         546 :   }
    1419             : 
    1420         186 :   txn_out->accounts.is_setup         = 1;
    1421         186 :   txn_out->accounts.nonce_idx_in_txn = ULONG_MAX;
    1422         186 :   runtime->accounts.executable_cnt   = executable_idx;
    1423         186 : }
    1424             : 
    1425             : int
    1426             : fd_executor_txn_verify( fd_txn_p_t *  txn_p,
    1427           0 :                         fd_sha512_t * shas[ FD_TXN_ACTUAL_SIG_MAX ] ) {
    1428           0 :   fd_txn_t * txn = TXN( txn_p );
    1429             : 
    1430           0 :   uchar * signatures = txn_p->payload + txn->signature_off;
    1431           0 :   uchar * pubkeys    = txn_p->payload + txn->acct_addr_off;
    1432           0 :   uchar * msg        = txn_p->payload + txn->message_off;
    1433           0 :   ulong   msg_sz     = txn_p->payload_sz - txn->message_off;
    1434             : 
    1435           0 :   int res = fd_ed25519_verify_batch_single_msg( msg, msg_sz, signatures, pubkeys, shas, txn->signature_cnt );
    1436           0 :   if( FD_UNLIKELY( res != FD_ED25519_SUCCESS ) ) {
    1437           0 :     return FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
    1438           0 :   }
    1439             : 
    1440           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    1441           0 : }
    1442             : 
    1443             : static int
    1444             : fd_executor_txn_check( fd_runtime_t * runtime,
    1445             :                        fd_bank_t *    bank,
    1446         138 :                        fd_txn_out_t * txn_out ) {
    1447         138 :   fd_rent_t const * rent = &bank->f.rent;
    1448             : 
    1449         138 :   ulong starting_lamports_l = 0UL;
    1450         138 :   ulong starting_lamports_h = 0UL;
    1451         138 :   ulong ending_lamports_l   = 0UL;
    1452         138 :   ulong ending_lamports_h   = 0UL;
    1453             : 
    1454             :   /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L63 */
    1455         510 :   for( ulong i=0UL; i<txn_out->accounts.cnt; i++ ) {
    1456         372 :     if( !txn_out->accounts.is_writable[i] ) continue;
    1457             : 
    1458             :     /* Tips for bundles are collected in the bank: a user submitting a
    1459             :        bundle must include a instruction that transfers lamports to
    1460             :        a specific tip account.  Tips accumulated through the slot. */
    1461         270 :     if( fd_pack_tip_is_tip_account( fd_type_pun_const( txn_out->accounts.keys[i].uc ) ) ) {
    1462           0 :       txn_out->details.tips += fd_ulong_sat_sub( fd_accdb_ref_lamports( txn_out->accounts.account[i].ro ), runtime->accounts.starting_lamports[i] );
    1463           0 :     }
    1464             : 
    1465         270 :     ulong               starting_lamports = runtime->accounts.starting_lamports[i];
    1466         270 :     ulong               starting_dlen     = runtime->accounts.starting_dlen[i];
    1467         270 :     fd_account_meta_t * meta              = txn_out->accounts.account[i].meta;
    1468         270 :     fd_pubkey_t *       pubkey            = &txn_out->accounts.keys[i];
    1469             : 
    1470         270 :     fd_uwide_inc( &ending_lamports_h, &ending_lamports_l, ending_lamports_h, ending_lamports_l, meta->lamports );
    1471         270 :     fd_uwide_inc( &starting_lamports_h, &starting_lamports_l, starting_lamports_h, starting_lamports_l, starting_lamports );
    1472             : 
    1473             :     /* Rent states are defined as followed:
    1474             :         - lamports == 0                      -> Uninitialized
    1475             :         - 0 < lamports < rent_exempt_minimum -> RentPaying
    1476             :         - lamports >= rent_exempt_minimum    -> RentExempt
    1477             :         In Agave, 'self' refers to our 'after' state. */
    1478         270 :     uchar after_uninitialized = meta->lamports==0UL;
    1479         270 :     uchar after_rent_exempt   = meta->lamports>=fd_rent_exempt_minimum_balance( rent, meta->dlen );
    1480             : 
    1481             :     /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L96 */
    1482         270 :     if( FD_LIKELY( memcmp( pubkey, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) ) ) {
    1483             :       /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L44 */
    1484         270 :       if( after_uninitialized || after_rent_exempt ) {
    1485             :         // no-op
    1486         255 :       } else {
    1487             :         /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L45-L59 */
    1488          15 :         uchar before_uninitialized = starting_lamports==0UL;
    1489          15 :         uchar before_rent_exempt   = starting_lamports>=fd_rent_exempt_minimum_balance( rent, starting_dlen );
    1490             : 
    1491             :         /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L50 */
    1492          15 :         if( FD_UNLIKELY( before_uninitialized || before_rent_exempt ) ) {
    1493             :           /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
    1494           0 :           return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
    1495             :         /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L56 */
    1496          15 :         } else if( (meta->dlen==starting_dlen) && meta->lamports<=starting_lamports ) {
    1497             :           // no-op
    1498          15 :         } else {
    1499             :           /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
    1500           0 :           return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
    1501           0 :         }
    1502          15 :       }
    1503         270 :     }
    1504             : 
    1505         270 :     if     ( !memcmp( meta->owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) txn_out->accounts.stake_update[i] = 1;
    1506         267 :     else if( !memcmp( meta->owner, &fd_solana_vote_program_id,  sizeof(fd_pubkey_t) ) ) txn_out->accounts.vote_update[i] = 1;
    1507             : 
    1508         270 :     if( FD_LIKELY( !runtime->fuzz.enabled || runtime->fuzz.reclaim_accounts ) ) {
    1509         270 :       fd_executor_reclaim_account( meta, bank->f.slot );
    1510         270 :     }
    1511         270 :   }
    1512             : 
    1513             :   /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/transaction_processor.rs#L839-L845 */
    1514         138 :   if( FD_UNLIKELY( ending_lamports_l!=starting_lamports_l || ending_lamports_h!=starting_lamports_h ) ) {
    1515           0 :     return FD_RUNTIME_TXN_ERR_UNBALANCED_TRANSACTION;
    1516           0 :   }
    1517             : 
    1518         138 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    1519         138 : }
    1520             : 
    1521             : 
    1522             : int
    1523             : fd_execute_txn( fd_runtime_t *      runtime,
    1524             :                 fd_bank_t *         bank,
    1525             :                 fd_txn_in_t const * txn_in,
    1526         183 :                 fd_txn_out_t *      txn_out ) {
    1527         183 :   fd_accdb_user_t * accdb = runtime->accdb;
    1528             : 
    1529         183 :   bool dump_insn = runtime->log.dump_proto_ctx &&
    1530         183 :                    bank->f.slot>=runtime->log.dump_proto_ctx->dump_proto_start_slot &&
    1531         183 :                    runtime->log.dump_proto_ctx->dump_instr_to_pb;
    1532         183 :   (void)dump_insn;
    1533             : 
    1534         183 :   fd_txn_t const * txn = TXN( txn_in->txn );
    1535             : 
    1536             :   /* Initialize log collection. */
    1537         183 :   if( !!runtime->log.log_collector ) fd_log_collector_init( runtime->log.log_collector, runtime->log.enable_log_collector );
    1538             : 
    1539         264 :   for( ushort i=0; i<TXN( txn_in->txn )->instr_cnt; i++ ) {
    1540             :     /* Set up the instr info for the current instruction */
    1541         126 :     fd_instr_info_t * instr_info = &runtime->instr.trace[runtime->instr.trace_length++];
    1542         126 :     fd_instr_info_init_from_txn_instr(
    1543         126 :         instr_info,
    1544         126 :         txn_in,
    1545         126 :         txn_out,
    1546         126 :         &txn->instr[i]
    1547         126 :     );
    1548             : 
    1549         126 :     if( FD_UNLIKELY( dump_insn ) ) {
    1550             :       // Capture the input and convert it into a Protobuf message
    1551           0 :       fd_dump_instr_to_protobuf( runtime, bank, txn_in, txn_out, instr_info, i );
    1552           0 :     }
    1553             : 
    1554             :     /* Update the current executing instruction index */
    1555         126 :     runtime->instr.current_idx = i;
    1556             : 
    1557             :     /* Execute the current instruction */
    1558         126 :     ulong account_refs_pre = accdb->base.ro_active + accdb->base.rw_active;
    1559         126 :     int instr_exec_result = fd_execute_instr( runtime, bank, txn_in, txn_out, instr_info );
    1560         126 :     ulong account_refs_post = accdb->base.ro_active + accdb->base.rw_active;
    1561         126 :     if( FD_UNLIKELY( account_refs_post != account_refs_pre ) ) {
    1562           0 :       FD_BASE58_ENCODE_64_BYTES( fd_txn_get_signatures( txn, txn_in->txn->payload )[0], txn_b58 );
    1563           0 :       FD_LOG_CRIT(( "fd_execute_instr(txn=%s,instr_idx=%u) leaked %lu account references",
    1564           0 :                     txn_b58, i, account_refs_post-account_refs_pre ));
    1565           0 :     }
    1566         126 :     if( FD_UNLIKELY( instr_exec_result!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1567          45 :       if( txn_out->err.exec_err_idx==INT_MAX ) {
    1568          45 :         txn_out->err.exec_err_idx = i;
    1569          45 :       }
    1570          45 :       return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
    1571          45 :     }
    1572         126 :   }
    1573             : 
    1574             :   /* TODO: This function needs to be split out of fd_execute_txn and be placed
    1575             :       into the replay tile once it is implemented. */
    1576         138 :   return fd_executor_txn_check( runtime, bank, txn_out );
    1577         183 : }
    1578             : 
    1579             : int
    1580             : fd_executor_consume_cus( fd_txn_out_t * txn_out,
    1581        2604 :                          ulong          cus ) {
    1582        2604 :   ulong new_cus   =  txn_out->details.compute_budget.compute_meter - cus;
    1583        2604 :   int   underflow = (txn_out->details.compute_budget.compute_meter < cus);
    1584        2604 :   if( FD_UNLIKELY( underflow ) ) {
    1585           0 :     txn_out->details.compute_budget.compute_meter = 0UL;
    1586           0 :     return FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED;
    1587           0 :   }
    1588        2604 :   txn_out->details.compute_budget.compute_meter = new_cus;
    1589        2604 :   return FD_EXECUTOR_INSTR_SUCCESS;
    1590        2604 : }
    1591             : 
    1592             : /* fd_executor_instr_strerror() returns the error message corresponding to err,
    1593             :    intended to be logged by log_collector, or an empty string if the error code
    1594             :    should be omitted in logs for whatever reason.  Omitted examples are success,
    1595             :    fatal (placeholder just in firedancer), custom error.
    1596             :    See also fd_log_collector_program_failure(). */
    1597             : FD_FN_CONST char const *
    1598         144 : fd_executor_instr_strerror( int err ) {
    1599             : 
    1600         144 :   switch( err ) {
    1601           0 :   case FD_EXECUTOR_INSTR_SUCCESS                                : return ""; // not used
    1602           0 :   case FD_EXECUTOR_INSTR_ERR_FATAL                              : return ""; // not used
    1603           0 :   case FD_EXECUTOR_INSTR_ERR_GENERIC_ERR                        : return "generic instruction error";
    1604           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ARG                        : return "invalid program argument";
    1605           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA                 : return "invalid instruction data";
    1606           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA                   : return "invalid account data for instruction";
    1607           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL                 : return "account data too small for instruction";
    1608           0 :   case FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS                 : return "insufficient funds for instruction";
    1609           0 :   case FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID               : return "incorrect program id for instruction";
    1610           0 :   case FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE         : return "missing required signature for instruction";
    1611           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED            : return "instruction requires an uninitialized account";
    1612           0 :   case FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT              : return "instruction requires an initialized account";
    1613           0 :   case FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR                   : return "sum of account balances before and after instruction do not match";
    1614           0 :   case FD_EXECUTOR_INSTR_ERR_MODIFIED_PROGRAM_ID                : return "instruction illegally modified the program id of an account";
    1615           0 :   case FD_EXECUTOR_INSTR_ERR_EXTERNAL_ACCOUNT_LAMPORT_SPEND     : return "instruction spent from the balance of an account it does not own";
    1616           0 :   case FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED             : return "instruction modified data of an account it does not own";
    1617           0 :   case FD_EXECUTOR_INSTR_ERR_READONLY_LAMPORT_CHANGE            : return "instruction changed the balance of a read-only account";
    1618          48 :   case FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED             : return "instruction modified data of a read-only account";
    1619           0 :   case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_IDX              : return "instruction contains duplicate accounts";
    1620           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_MODIFIED                : return "instruction changed executable bit of an account";
    1621           0 :   case FD_EXECUTOR_INSTR_ERR_RENT_EPOCH_MODIFIED                : return "instruction modified rent epoch of an account";
    1622           0 :   case FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS                : return "insufficient account keys for instruction";
    1623           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";
    1624           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE                 : return "instruction expected an executable account";
    1625           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED                  : return "instruction tries to borrow reference for an account which is already borrowed";
    1626           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING             : return "instruction left account with an outstanding borrowed reference";
    1627           0 :   case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_OUT_OF_SYNC      : return "instruction modifications of multiply-passed account differ";
    1628           0 :   case FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR                         : return ""; // custom handling via txn_ctx->err.custom_err
    1629           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ERR                        : return "program returned invalid error code";
    1630           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED           : return "instruction changed executable accounts data";
    1631           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_LAMPORT_CHANGE          : return "instruction changed the balance of an executable account";
    1632           0 :   case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT : return "executable accounts must be rent exempt";
    1633           0 :   case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID             : return "Unsupported program id";
    1634           0 :   case FD_EXECUTOR_INSTR_ERR_CALL_DEPTH                         : return "Cross-program invocation call depth too deep";
    1635           0 :   case FD_EXECUTOR_INSTR_ERR_MISSING_ACC                        : return "An account required by the instruction is missing";
    1636           0 :   case FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED             : return "Cross-program invocation reentrancy not allowed for this instruction";
    1637           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED           : return "Length of the seed is too long for address generation";
    1638           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_SEEDS                      : return "Provided seeds do not result in a valid address";
    1639          96 :   case FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC                    : return "Failed to reallocate account data";
    1640           0 :   case FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED            : return "Computational budget exceeded";
    1641           0 :   case FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION               : return "Cross-program invocation with unauthorized signer or writable account";
    1642           0 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_ENVIRONMENT_SETUP_FAILURE  : return "Failed to create program execution environment";
    1643           0 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPLETE         : return "Program failed to complete";
    1644           0 :   case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPILE          : return "Program failed to compile";
    1645           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_IMMUTABLE                      : return "Account is immutable";
    1646           0 :   case FD_EXECUTOR_INSTR_ERR_INCORRECT_AUTHORITY                : return "Incorrect authority provided";
    1647           0 :   case FD_EXECUTOR_INSTR_ERR_BORSH_IO_ERROR                     : return "Failed to serialize or deserialize account data"; // truncated
    1648           0 :   case FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT                : return "An account does not have enough lamports to be rent-exempt";
    1649           0 :   case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER                  : return "Invalid account owner";
    1650           0 :   case FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW                : return "Program arithmetic overflowed";
    1651           0 :   case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR                 : return "Unsupported sysvar";
    1652           0 :   case FD_EXECUTOR_INSTR_ERR_ILLEGAL_OWNER                      : return "Provided owner is not allowed";
    1653           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED      : return "Accounts data allocations exceeded the maximum allowed per transaction";
    1654           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED                  : return "Max accounts exceeded";
    1655           0 :   case FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED       : return "Max instruction trace length exceeded";
    1656           0 :   case FD_EXECUTOR_INSTR_ERR_BUILTINS_MUST_CONSUME_CUS          : return "Builtin programs must consume compute units";
    1657           0 :   default: break;
    1658         144 :   }
    1659             : 
    1660           0 :   return "";
    1661         144 : }

Generated by: LCOV version 1.14