LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_executor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 468 975 48.0 %
Date: 2026-01-12 05:12:51 Functions: 34 41 82.9 %

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

Generated by: LCOV version 1.14