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

Generated by: LCOV version 1.14