LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_executor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 618 810 76.3 %
Date: 2026-07-11 05:27:41 Functions: 33 34 97.1 %

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

Generated by: LCOV version 1.14