LCOV - code coverage report
Current view: top level - flamenco/runtime/program - fd_program_cache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 272 368 73.9 %
Date: 2025-10-13 04:42:14 Functions: 15 17 88.2 %

          Line data    Source code
       1             : #include "fd_program_cache.h"
       2             : #include "fd_bpf_loader_program.h"
       3             : #include "fd_loader_v4_program.h"
       4             : #include "../../vm/fd_vm.h"
       5             : #include "../sysvar/fd_sysvar_epoch_schedule.h"
       6             : #include "../../../funk/fd_funk_rec.h"
       7             : 
       8             : #include <assert.h>
       9             : 
      10             : fd_program_cache_entry_t *
      11             : fd_program_cache_entry_new( void *                     mem,
      12             :                             fd_sbpf_elf_info_t const * elf_info,
      13             :                             ulong                      last_slot_modified,
      14          36 :                             ulong                      last_slot_verified ) {
      15          36 :   fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
      16          36 :   cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
      17             : 
      18             :   /* Failed verification flag */
      19          36 :   cache_entry->failed_verification = 0;
      20             : 
      21             :   /* Last slot the program was modified */
      22          36 :   cache_entry->last_slot_modified = last_slot_modified;
      23             : 
      24             :   /* Last slot verification checks were ran for this program */
      25          36 :   cache_entry->last_slot_verified = last_slot_verified;
      26             : 
      27          36 :   ulong l = FD_LAYOUT_INIT;
      28             : 
      29             :   /* calldests backing memory */
      30          36 :   l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
      31          36 :   cache_entry->calldests_shmem_off = l;
      32             : 
      33             :   /* rodata backing memory */
      34          36 :   l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) );
      35          36 :   cache_entry->rodata_off = l;
      36             : 
      37             :   /* SBPF version */
      38          36 :   cache_entry->sbpf_version = elf_info->sbpf_version;
      39             : 
      40          36 :   return cache_entry;
      41          36 : }
      42             : 
      43             : /* Sets up defined fields for a record that failed verification. */
      44             : static void
      45             : fd_program_cache_entry_set_failed_verification( void * mem,
      46          12 :                                                 ulong  last_slot_verified ) {
      47          12 :   fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
      48          12 :   cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
      49             : 
      50             :   /* Failed verification flag */
      51          12 :   cache_entry->failed_verification = 1;
      52             : 
      53             :   /* Last slot the program was modified */
      54          12 :   cache_entry->last_slot_modified = 0UL;
      55             : 
      56             :   /* Last slot verification checks were ran for this program */
      57          12 :   cache_entry->last_slot_verified = last_slot_verified;
      58             : 
      59             :   /* All other fields are undefined. */
      60          12 : }
      61             : 
      62             : ulong
      63          36 : fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ) {
      64          36 :   ulong l = FD_LAYOUT_INIT;
      65          36 :   l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
      66          36 :   l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) );
      67          36 :   l = FD_LAYOUT_APPEND( l, 8UL, elf_info->bin_sz );
      68          36 :   l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
      69          36 :   return l;
      70          36 : }
      71             : 
      72             : /* Returns the footprint of a record that failed verification. Note that
      73             :    all other fields are undefined besides the `failed_verification`,
      74             :    `last_slot_verified`, and `last_slot_modified` fields, so we can
      75             :    just return the core struct's size. */
      76             : static inline FD_FN_PURE ulong
      77          12 : fd_program_cache_failed_verification_entry_footprint( void ) {
      78          12 :   ulong l = FD_LAYOUT_INIT;
      79          12 :   l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
      80          12 :   l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
      81          12 :   return l;
      82          12 : }
      83             : 
      84             : fd_funk_rec_key_t
      85         210 : fd_program_cache_key( fd_pubkey_t const * pubkey ) {
      86         210 :   fd_funk_rec_key_t id;
      87         210 :   memcpy( id.uc, pubkey, sizeof(fd_pubkey_t) );
      88         210 :   memset( id.uc + sizeof(fd_pubkey_t), 0, sizeof(fd_funk_rec_key_t) - sizeof(fd_pubkey_t) );
      89             : 
      90         210 :   id.uc[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_ELF_CACHE;
      91             : 
      92         210 :   return id;
      93         210 : }
      94             : 
      95             : /* Parse ELF info from programdata. */
      96             : static int
      97             : fd_program_cache_parse_elf_info( fd_bank_t *          bank,
      98             :                                  fd_sbpf_elf_info_t * elf_info,
      99             :                                  uchar const *        program_data,
     100          45 :                                  ulong                program_data_len ) {
     101          45 :   uint min_sbpf_version, max_sbpf_version;
     102          45 :   fd_bpf_get_sbpf_versions( &min_sbpf_version,
     103          45 :                             &max_sbpf_version,
     104          45 :                             fd_bank_slot_get( bank ),
     105          45 :                             fd_bank_features_query( bank ) );
     106             : 
     107          45 :   fd_sbpf_loader_config_t config = { 0 };
     108          45 :   config.elf_deploy_checks = 0;
     109          45 :   config.sbpf_min_version = min_sbpf_version;
     110          45 :   config.sbpf_max_version = max_sbpf_version;
     111             : 
     112          45 :   if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, program_data, program_data_len, &config )<0 ) ) {
     113           9 :     FD_LOG_DEBUG(( "fd_sbpf_elf_peek() failed" ));
     114           9 :     return -1;
     115           9 :   }
     116          36 :   return 0;
     117          45 : }
     118             : 
     119             : /* Similar to the below function, but gets the executable program content for the v4 loader.
     120             :    Unlike the v3 loader, the programdata is stored in a single program account. The program must
     121             :    NOT be retracted to be added to the cache. Returns a pointer to the programdata on success,
     122             :    and NULL on failure.
     123             : 
     124             :    Reasons for failure include:
     125             :    - The program state cannot be read from the account data or is in the `retracted` state. */
     126             : static uchar const *
     127             : fd_get_executable_program_content_for_v4_loader( fd_txn_account_t const * program_acc,
     128           0 :                                                  ulong *                  program_data_len ) {
     129           0 :   int err;
     130             : 
     131             :   /* Get the current loader v4 state. This implicitly also checks the dlen. */
     132           0 :   fd_loader_v4_state_t const * state = fd_loader_v4_get_state( program_acc, &err );
     133           0 :   if( FD_UNLIKELY( err ) ) {
     134           0 :     return NULL;
     135           0 :   }
     136             : 
     137             :   /* The program must be deployed or finalized. */
     138           0 :   if( FD_UNLIKELY( fd_loader_v4_status_is_retracted( state ) ) ) {
     139           0 :     return NULL;
     140           0 :   }
     141             : 
     142             :   /* This subtraction is safe because get_state() implicitly checks the
     143             :      dlen. */
     144           0 :   *program_data_len = fd_txn_account_get_data_len( program_acc )-LOADER_V4_PROGRAM_DATA_OFFSET;
     145           0 :   return fd_txn_account_get_data( program_acc )+LOADER_V4_PROGRAM_DATA_OFFSET;
     146           0 : }
     147             : 
     148             : /* Gets the programdata for a v3 loader-owned account by decoding the account data
     149             :    as well as the programdata account. Returns a pointer to the programdata on success,
     150             :    and NULL on failure.
     151             : 
     152             :    Reasons for failure include:
     153             :    - The program account data cannot be decoded or is not in the `program` state.
     154             :    - The programdata account is not large enough to hold at least `PROGRAMDATA_METADATA_SIZE` bytes. */
     155             : static uchar const *
     156             : fd_get_executable_program_content_for_upgradeable_loader( fd_funk_t const *         funk,
     157             :                                                           fd_funk_txn_xid_t const * xid,
     158             :                                                           fd_txn_account_t const *  program_acc,
     159           0 :                                                           ulong *                   program_data_len ) {
     160           0 :   fd_bpf_upgradeable_loader_state_t program_account_state[1];
     161           0 :   if( FD_UNLIKELY( !fd_bincode_decode_static(
     162           0 :       bpf_upgradeable_loader_state,
     163           0 :       program_account_state,
     164           0 :       fd_txn_account_get_data( program_acc ),
     165           0 :       fd_txn_account_get_data_len( program_acc ),
     166           0 :       NULL ) ) ) {
     167           0 :     return NULL;
     168           0 :   }
     169           0 :   if( !fd_bpf_upgradeable_loader_state_is_program( program_account_state ) ) {
     170           0 :     return NULL;
     171           0 :   }
     172             : 
     173           0 :   fd_pubkey_t * programdata_address = &program_account_state->inner.program.programdata_address;
     174           0 :   FD_TXN_ACCOUNT_DECL( programdata_acc );
     175           0 :   if( fd_txn_account_init_from_funk_readonly( programdata_acc, programdata_address, funk, xid )!=FD_ACC_MGR_SUCCESS ) {
     176           0 :     return NULL;
     177           0 :   }
     178             : 
     179             :   /* We don't actually need to decode here, just make sure that the account
     180             :      can be decoded successfully. */
     181           0 :   fd_bincode_decode_ctx_t ctx_programdata = {
     182           0 :     .data    = fd_txn_account_get_data( programdata_acc ),
     183           0 :     .dataend = fd_txn_account_get_data( programdata_acc ) + fd_txn_account_get_data_len( programdata_acc ),
     184           0 :   };
     185             : 
     186           0 :   ulong total_sz = 0UL;
     187           0 :   if( FD_UNLIKELY( fd_bpf_upgradeable_loader_state_decode_footprint( &ctx_programdata, &total_sz ) ) ) {
     188           0 :     return NULL;
     189           0 :   }
     190             : 
     191           0 :   if( FD_UNLIKELY( fd_txn_account_get_data_len( programdata_acc )<PROGRAMDATA_METADATA_SIZE ) ) {
     192           0 :     return NULL;
     193           0 :   }
     194             : 
     195           0 :   *program_data_len = fd_txn_account_get_data_len( programdata_acc ) - PROGRAMDATA_METADATA_SIZE;
     196           0 :   return fd_txn_account_get_data( programdata_acc ) + PROGRAMDATA_METADATA_SIZE;
     197           0 : }
     198             : 
     199             : /* Gets the programdata for a v1/v2 loader-owned account by returning a
     200             :    pointer to the account data. Returns a pointer to the programdata on
     201             :    success. Given the txn account API always returns a handle to the
     202             :    account data, this function should NEVER return NULL (since the
     203             :    programdata of v1 and v2 loader) accounts start at the beginning of
     204             :    the data. */
     205             : static uchar const *
     206             : fd_get_executable_program_content_for_v1_v2_loaders( fd_txn_account_t const * program_acc,
     207          45 :                                                      ulong *                  program_data_len ) {
     208          45 :   *program_data_len = fd_txn_account_get_data_len( program_acc );
     209          45 :   return fd_txn_account_get_data( program_acc );
     210          45 : }
     211             : 
     212             : uchar const *
     213             : fd_program_cache_get_account_programdata( fd_funk_t const *         funk,
     214             :                                           fd_funk_txn_xid_t const * xid,
     215             :                                           fd_txn_account_t const *  program_acc,
     216          45 :                                           ulong *                   out_program_data_len ) {
     217             :   /* v1/v2 loaders: Programdata is just the account data.
     218             :      v3 loader: Programdata lives in a separate account. Deserialize the
     219             :                 program account and lookup the programdata account.
     220             :                 Deserialize the programdata account.
     221             :      v4 loader: Programdata lives in the program account, offset by
     222             :                 LOADER_V4_PROGRAM_DATA_OFFSET. */
     223          45 :   if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
     224           0 :     return fd_get_executable_program_content_for_upgradeable_loader( funk, xid, program_acc, out_program_data_len );
     225          45 :   } else if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) {
     226           0 :     return fd_get_executable_program_content_for_v4_loader( program_acc, out_program_data_len );
     227          45 :   } else if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) ||
     228          45 :              !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ) {
     229          45 :     return fd_get_executable_program_content_for_v1_v2_loaders( program_acc, out_program_data_len );
     230          45 :   }
     231           0 :   return NULL;
     232          45 : }
     233             : 
     234             : /* This function is used to validate an sBPF program and set the
     235             :    program's flags accordingly. The return code only signifies whether
     236             :    the program was successfully validated or not. Regardless of the
     237             :    return code, the program should still be added to the cache.
     238             :    `cache_entry` is expected to be a pre-allocated struct with enough
     239             :    space to hold its field members + calldests info.
     240             : 
     241             :    Reasons for failure include:
     242             :    - Insufficient memory in the spad to allocate memory for local
     243             :      objects.
     244             :    - The sBPF program fails to be loaded or validated validated.
     245             : 
     246             :    On a failure that doesn't kill the client, the `failed_verification`
     247             :    flag for the record is set to 1, and the `last_slot_verified` field
     248             :    is set to the current slot.
     249             : 
     250             :    On success, `cache_entry` is updated with the loaded sBPF program
     251             :    metadata, as well as the `last_slot_modified`, `last_slot_verified`,
     252             :    and `failed_verification` flags. */
     253             : static int
     254             : fd_program_cache_validate_sbpf_program( fd_bank_t *                bank,
     255             :                                         fd_sbpf_elf_info_t const * elf_info,
     256             :                                         uchar const *              program_data,
     257             :                                         ulong                      program_data_len,
     258             :                                         fd_spad_t *                runtime_spad,
     259          36 :                                         fd_program_cache_entry_t * cache_entry /* out */ ) {
     260          36 :   ulong               prog_align     = fd_sbpf_program_align();
     261          36 :   ulong               prog_footprint = fd_sbpf_program_footprint( elf_info );
     262          36 :   void              * prog_mem       = fd_spad_alloc_check( runtime_spad, prog_align, prog_footprint );
     263          36 :   fd_sbpf_program_t * prog           = fd_sbpf_program_new( prog_mem , elf_info, fd_program_cache_get_rodata( cache_entry ) );
     264          36 :   if( FD_UNLIKELY( !prog ) ) {
     265           0 :     FD_LOG_DEBUG(( "fd_sbpf_program_new() failed" ));
     266           0 :     cache_entry->failed_verification = 1;
     267           0 :     return -1;
     268           0 :   }
     269             : 
     270             :   /* Allocate syscalls */
     271             : 
     272          36 :   void               * syscalls_mem = fd_spad_alloc_check( runtime_spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() );
     273          36 :   fd_sbpf_syscalls_t * syscalls     = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( syscalls_mem ) );
     274          36 :   if( FD_UNLIKELY( !syscalls ) ) {
     275           0 :     FD_LOG_CRIT(( "Call to fd_sbpf_syscalls_new() failed" ));
     276           0 :   }
     277             : 
     278          36 :   fd_vm_syscall_register_slot( syscalls,
     279          36 :                                fd_bank_slot_get( bank ),
     280          36 :                                fd_bank_features_query( bank ),
     281          36 :                                0 );
     282             : 
     283             :   /* Load program. */
     284             : 
     285          36 :   fd_sbpf_loader_config_t config = { 0 };
     286          36 :   if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, program_data, program_data_len, syscalls, &config ) ) ) {
     287           0 :     FD_LOG_DEBUG(( "fd_sbpf_program_load() failed" ));
     288           0 :     cache_entry->failed_verification = 1;
     289           0 :     fd_sbpf_syscalls_leave( syscalls );
     290           0 :     return -1;
     291           0 :   }
     292             : 
     293             :   /* Validate the program. */
     294             : 
     295          36 :   fd_vm_t _vm[ 1UL ];
     296          36 :   fd_vm_t * vm = fd_vm_join( fd_vm_new( _vm ) );
     297          36 :   if( FD_UNLIKELY( !vm ) ) {
     298           0 :     FD_LOG_CRIT(( "fd_vm_new() or fd_vm_join() failed" ));
     299           0 :   }
     300             : 
     301          36 :   int direct_mapping = FD_FEATURE_ACTIVE( fd_bank_slot_get( bank ), fd_bank_features_query( bank ), bpf_account_data_direct_mapping );
     302             : 
     303          36 :   vm = fd_vm_init( vm,
     304          36 :                    NULL, /* OK since unused in `fd_vm_validate()` */
     305          36 :                    0UL,
     306          36 :                    0UL,
     307          36 :                    prog->rodata,
     308          36 :                    prog->rodata_sz,
     309          36 :                    prog->text,
     310          36 :                    prog->info.text_cnt,
     311          36 :                    prog->info.text_off,
     312          36 :                    prog->info.text_sz,
     313          36 :                    prog->entry_pc,
     314          36 :                    prog->calldests,
     315          36 :                    elf_info->sbpf_version,
     316          36 :                    syscalls,
     317          36 :                    NULL,
     318          36 :                    NULL,
     319          36 :                    NULL,
     320          36 :                    0U,
     321          36 :                    NULL,
     322          36 :                    0,
     323          36 :                    direct_mapping,
     324          36 :                    0 );
     325             : 
     326          36 :   if( FD_UNLIKELY( !vm ) ) {
     327           0 :     FD_LOG_CRIT(( "fd_vm_init() failed" ));
     328           0 :   }
     329             : 
     330          36 :   int res = fd_vm_validate( vm );
     331          36 :   fd_sbpf_syscalls_leave( syscalls );
     332          36 :   if( FD_UNLIKELY( res ) ) {
     333           3 :     FD_LOG_DEBUG(( "fd_vm_validate() failed" ));
     334           3 :     cache_entry->failed_verification = 1;
     335           3 :     return -1;
     336           3 :   }
     337             : 
     338             :   /* FIXME: Super expensive memcpy. */
     339          33 :   if( FD_LIKELY( prog->calldests_shmem ) ) {
     340          33 :     fd_memcpy( fd_program_cache_get_calldests_shmem( cache_entry ), prog->calldests_shmem, fd_sbpf_calldests_footprint( prog->info.text_cnt ) );
     341          33 :   } else {
     342           0 :     fd_memset( fd_program_cache_get_calldests_shmem( cache_entry ), 0, fd_sbpf_calldests_footprint( prog->info.text_cnt ) );
     343           0 :   }
     344             : 
     345          33 :   cache_entry->entry_pc            = prog->entry_pc;
     346          33 :   cache_entry->text_off            = prog->info.text_off;
     347          33 :   cache_entry->text_cnt            = prog->info.text_cnt;
     348          33 :   cache_entry->text_sz             = prog->info.text_sz;
     349          33 :   cache_entry->rodata_sz           = prog->rodata_sz;
     350          33 :   cache_entry->failed_verification = 0;
     351             : 
     352          33 :   return 0;
     353          36 : }
     354             : 
     355             : /* Publishes an in-prepare funk record for a program that failed
     356             :    verification. Creates a default sBPF validated program with the
     357             :    `failed_verification` flag set to 1 and `last_slot_verified` set to
     358             :    the current slot. The passed-in funk record is expected to be in a
     359             :    prepare. */
     360             : static void
     361             : fd_program_cache_publish_failed_verification_rec( fd_funk_t *             funk,
     362             :                                                   fd_funk_rec_prepare_t * prepare,
     363             :                                                   fd_funk_rec_t *         rec,
     364           9 :                                                   ulong                   current_slot ) {
     365             :   /* Truncate the record to have a minimal footprint */
     366           9 :   ulong record_sz = fd_program_cache_failed_verification_entry_footprint();
     367           9 :   void * data = fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), 0UL, record_sz, NULL );
     368           9 :   if( FD_UNLIKELY( data==NULL ) ) {
     369           0 :     FD_LOG_ERR(( "fd_funk_val_truncate() failed to truncate record to size %lu", record_sz ));
     370           0 :   }
     371             : 
     372           9 :   fd_program_cache_entry_set_failed_verification( data, current_slot );
     373           9 :   fd_funk_rec_publish( funk, prepare );
     374           9 : }
     375             : 
     376             : /* Validates an SBPF program and adds it to the program cache.
     377             :    Reasons for verification failure include:
     378             :    - The ELF info cannot be parsed or validated from the programdata.
     379             :    - The sBPF program fails to be validated.
     380             : 
     381             :    The program will still be added to the cache even if verifications
     382             :    fail. This is to prevent a DOS vector where an attacker could spam
     383             :    invocations to programs that failed verification. */
     384             : static void
     385             : fd_program_cache_create_cache_entry( fd_bank_t *               bank,
     386             :                                      fd_funk_t *               funk,
     387             :                                      fd_funk_txn_xid_t const * xid,
     388             :                                      fd_txn_account_t const *  program_acc,
     389          27 :                                      fd_spad_t *               runtime_spad ) {
     390          27 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     391          27 :     ulong current_slot = fd_bank_slot_get( bank );
     392             : 
     393             :     /* Prepare the funk record for the program cache. */
     394          27 :     fd_pubkey_t const * program_pubkey = program_acc->pubkey;
     395          27 :     fd_funk_rec_key_t   id             = fd_program_cache_key( program_pubkey );
     396             : 
     397             :     /* Try to get the programdata for the account. If it doesn't exist,
     398             :        simply return without publishing anything. The program could have
     399             :        been closed, but we do not want to touch the cache in this case. */
     400          27 :     ulong         program_data_len = 0UL;
     401          27 :     uchar const * program_data     = fd_program_cache_get_account_programdata( funk, xid, program_acc, &program_data_len );
     402             : 
     403             :     /* This prepare should never fail. */
     404          27 :     int funk_err = FD_FUNK_SUCCESS;
     405          27 :     fd_funk_rec_prepare_t prepare[1];
     406          27 :     fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, &id, prepare, &funk_err );
     407          27 :     if( rec == NULL || funk_err != FD_FUNK_SUCCESS ) {
     408           0 :       FD_LOG_CRIT(( "fd_funk_rec_prepare() failed: %i-%s", funk_err, fd_funk_strerror( funk_err ) ));
     409           0 :     }
     410             : 
     411             :     /* In Agave's load_program_with_pubkey(), if program data cannot be
     412             :        obtained, a tombstone cache entry of type Closed or
     413             :        FailedVerification is created.  For correctness, we could just
     414             :        not insert a cache entry when there is no valid program data.
     415             :        Nonetheless, for purely conformance on instruction error log
     416             :        messages reasons, specifically "Program is not deployed" vs
     417             :        "Program is not cached", we would like to have a cache entry
     418             :        precisely when Agave does, such that we match Agave exactly on
     419             :        this error log.  So, we insert a cache entry here. */
     420          27 :     if( FD_UNLIKELY( program_data==NULL ) ) {
     421           0 :       fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
     422           0 :       return;
     423           0 :     }
     424             : 
     425          27 :     fd_sbpf_elf_info_t elf_info = {0};
     426          27 :     if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) {
     427           6 :       fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
     428           6 :       return;
     429           6 :     }
     430             : 
     431          21 :     ulong val_sz = fd_program_cache_entry_footprint( &elf_info );
     432          21 :     void * val = fd_funk_val_truncate(
     433          21 :         rec,
     434          21 :         fd_funk_alloc( funk ),
     435          21 :         fd_funk_wksp( funk ),
     436          21 :         0UL,
     437          21 :         val_sz,
     438          21 :         &funk_err );
     439          21 :     if( FD_UNLIKELY( funk_err ) ) {
     440           0 :       FD_LOG_ERR(( "fd_funk_val_truncate(sz=%lu) for account failed (%i-%s)", val_sz, funk_err, fd_funk_strerror( funk_err ) ));
     441           0 :     }
     442             : 
     443             :     /* Note that the cache entry points to the funk record data
     444             :        and writes into the record directly to avoid an expensive memcpy.
     445             :        Since this record is fresh, we should set the last slot modified
     446             :        to 0. */
     447          21 :     fd_program_cache_entry_t * cache_entry = fd_program_cache_entry_new( val, &elf_info, 0UL, current_slot );
     448          21 :     int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, cache_entry );
     449          21 :     if( FD_UNLIKELY( res ) ) {
     450           3 :       fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
     451           3 :       return;
     452           3 :     }
     453             : 
     454          18 :     fd_funk_rec_publish( funk, prepare );
     455          27 :   } FD_SPAD_FRAME_END;
     456          27 : }
     457             : 
     458             : int
     459             : fd_program_cache_load_entry( fd_funk_t const *                 funk,
     460             :                              fd_funk_txn_xid_t const *         xid,
     461             :                              fd_pubkey_t const *               program_pubkey,
     462         120 :                              fd_program_cache_entry_t const ** cache_entry ) {
     463         120 :   fd_funk_rec_key_t id = fd_program_cache_key( program_pubkey );
     464             : 
     465         120 :   for(;;) {
     466         120 :     fd_funk_rec_query_t query[1];
     467         120 :     fd_funk_rec_t const * rec = fd_funk_rec_query_try_global(funk, xid, &id, NULL, query);
     468             : 
     469         120 :     if( FD_UNLIKELY( !rec ) ) {
     470             :       /* If rec is NULL, we shouldn't inspect query below because it
     471             :          would contain uninitialized fields. */
     472          39 :       return -1;
     473          39 :     }
     474             : 
     475          81 :     void const * data = fd_funk_val_const( rec, fd_funk_wksp(funk) );
     476             : 
     477          81 :     *cache_entry = (fd_program_cache_entry_t const *)data;
     478             : 
     479             :     /* This test is actually too early. It should happen after the
     480             :        data is actually consumed.
     481             : 
     482             :        TODO: this is likely fine because nothing else is modifying the
     483             :        program cache records at the same time. */
     484          81 :     if( FD_LIKELY( fd_funk_rec_query_test( query ) == FD_FUNK_SUCCESS ) ) {
     485          81 :       if( FD_UNLIKELY( (*cache_entry)->magic != FD_PROGRAM_CACHE_ENTRY_MAGIC ) ) FD_LOG_ERR(( "invalid magic" ));
     486          81 :       return 0;
     487          81 :     }
     488             : 
     489             :     /* Try again */
     490          81 :   }
     491         120 : }
     492             : 
     493             : void
     494             : fd_program_cache_update_program( fd_bank_t *               bank,
     495             :                                  fd_funk_t *               funk,
     496             :                                  fd_funk_txn_xid_t const * xid,
     497             :                                  fd_pubkey_t const *       program_key,
     498          51 :                                  fd_spad_t *               runtime_spad ) {
     499          51 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     500          51 :   FD_TXN_ACCOUNT_DECL( exec_rec );
     501          51 :   fd_funk_rec_key_t id = fd_program_cache_key( program_key );
     502             : 
     503             :   /* No need to touch the cache if the account no longer exists. */
     504          51 :   if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( exec_rec,
     505          51 :                                                            program_key,
     506          51 :                                                            funk,
     507          51 :                                                            xid ) ) ) {
     508           3 :     return;
     509           3 :   }
     510             : 
     511             :   /* The account owner must be a BPF loader to even be considered. */
     512          48 :   if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( fd_txn_account_get_owner( exec_rec ) ) ) ) {
     513           3 :     return;
     514           3 :   }
     515             : 
     516             :   /* If the program is not present in the cache yet, then we should run
     517             :      verifications and add it to the cache.
     518             :      `fd_program_cache_create_cache_entry()` will insert the program
     519             :      into the cache and update the entry's flags accordingly if it fails
     520             :      verification. */
     521          45 :   fd_program_cache_entry_t const * existing_entry = NULL;
     522          45 :   int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry );
     523          45 :   if( FD_UNLIKELY( err ) ) {
     524          27 :     fd_program_cache_create_cache_entry( bank, funk, xid, exec_rec, runtime_spad );
     525          27 :     return;
     526          27 :   }
     527             : 
     528             :   /* At this point, the program exists in the cache but we may need to
     529             :      reverify it and update the program cache. A program does NOT
     530             :      require reverification if both of the following conditions are met:
     531             :      - The program was already reverified in the current epoch
     532             :      - EITHER of the following are met:
     533             :        - The program was not modified in a recent slot (i.e. the program
     534             :          was already reverified since the last time it was modified)
     535             :        - The program was already verified in the current slot
     536             : 
     537             :       We must reverify the program for the current epoch if it has not
     538             :       been reverified yet. Additionally, we cannot break the invariant
     539             :       that a program cache entry will get reverified more than once
     540             :       per slot, otherwise the replay and exec tiles may race. */
     541          18 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     542          18 :   ulong current_slot                         = fd_bank_slot_get( bank );
     543          18 :   ulong current_epoch                        = fd_bank_epoch_get( bank );
     544          18 :   ulong last_epoch_verified                  = fd_slot_to_epoch( epoch_schedule, existing_entry->last_slot_verified, NULL );
     545          18 :   ulong last_slot_modified                   = existing_entry->last_slot_modified;
     546          18 :   if( FD_LIKELY( last_epoch_verified==current_epoch &&
     547          18 :                  ( last_slot_modified<existing_entry->last_slot_verified ||
     548          18 :                    current_slot==existing_entry->last_slot_verified ) ) ) {
     549           0 :     return;
     550           0 :   }
     551             : 
     552             :   /* Get the program data from the account. If the programdata does not
     553             :      exist, there is no need to touch the cache - if someone tries to
     554             :      invoke this program, account loading checks will fail. */
     555          18 :   ulong         program_data_len = 0UL;
     556          18 :   uchar const * program_data     = fd_program_cache_get_account_programdata( funk,
     557          18 :                                                                              xid,
     558          18 :                                                                              exec_rec,
     559          18 :                                                                              &program_data_len );
     560          18 :   if( FD_UNLIKELY( program_data==NULL ) ) {
     561             :     /* Unlike in fd_program_cache_create_cache_entry(), where we need to
     562             :        insert an entry into the cache when we cannot obtain valid
     563             :        program data, here we could simply return because we know, at
     564             :        this point, that the program is already in the cache.  So we
     565             :        don't need to do anything extra for matching Agave on cache entry
     566             :        presence. */
     567           0 :     return;
     568           0 :   }
     569             : 
     570             :   /* From here on out, we need to reverify the program. */
     571             : 
     572             :   /* Parse out the ELF info so we can determine the record footprint.
     573             :      If the parsing fails, then we need to make sure to later publish
     574             :      a failed verification record. */
     575          18 :   uchar              failed_elf_parsing = 0;
     576          18 :   ulong              record_sz          = 0UL;
     577          18 :   fd_sbpf_elf_info_t elf_info           = {0};
     578          18 :   if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) {
     579           3 :     failed_elf_parsing = 1;
     580           3 :     record_sz          = fd_program_cache_failed_verification_entry_footprint();
     581          15 :   } else {
     582          15 :     failed_elf_parsing = 0;
     583          15 :     record_sz = fd_program_cache_entry_footprint( &elf_info );
     584          15 :   }
     585             : 
     586             :   /* Resize the record to the new footprint if needed */
     587          18 :   uchar data[ record_sz ];
     588          18 :   fd_program_cache_entry_t * writable_entry = fd_type_pun( data );
     589             : 
     590             :   /* If the ELF header parsing failed, publish a failed verification
     591             :      record. */
     592          18 :   if( FD_UNLIKELY( failed_elf_parsing ) ) {
     593           3 :     fd_program_cache_entry_set_failed_verification( writable_entry, current_slot );
     594          15 :   } else {
     595             :     /* Validate the sBPF program. This will set the program's flags
     596             :        accordingly. We publish the funk record regardless of the return
     597             :        code. */
     598          15 :     writable_entry = fd_program_cache_entry_new( data, &elf_info, last_slot_modified, current_slot );
     599          15 :     int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, writable_entry );
     600          15 :     if( FD_UNLIKELY( res ) ) {
     601           0 :       FD_LOG_DEBUG(( "fd_program_cache_validate_sbpf_program() failed" ));
     602           0 :     }
     603          15 :   }
     604             : 
     605          18 :   fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), record_sz, writable_entry );
     606             : 
     607          51 : } FD_SPAD_FRAME_END;
     608          51 : }
     609             : 
     610             : void
     611             : fd_program_cache_queue_program_for_reverification( fd_funk_t *               funk,
     612             :                                                    fd_funk_txn_xid_t const * xid,
     613             :                                                    fd_pubkey_t const *       program_key,
     614          12 :                                                    ulong                     current_slot ) {
     615             : 
     616             :   /* We want to access the program cache entry for this pubkey and
     617             :      queue it for reverification. If it exists, clone it down to the
     618             :      current funk txn and update the entry's `last_slot_modified`
     619             :      field.
     620             : 
     621             :      This read is thread-safe because if you have transaction A that
     622             :      modifies the program and transaction B that references the program,
     623             :      the dispatcher will not attempt to execute transaction B until
     624             :      transaction A is finalized. */
     625          12 :   fd_program_cache_entry_t const * existing_entry = NULL;
     626          12 :   int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry );
     627          12 :   if( FD_UNLIKELY( err ) ) {
     628           3 :     return;
     629           3 :   }
     630             : 
     631             :   /* Ensure the record is in the current funk transaction */
     632           9 :   fd_funk_rec_key_t id = fd_program_cache_key( program_key );
     633           9 :   fd_program_cache_entry_t entry = *existing_entry;
     634           9 :   entry.last_slot_modified = current_slot;
     635           9 :   fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t), &entry );
     636           9 : }
     637             : 
     638             : int
     639             : fd_funk_rec_insert_para( fd_funk_t *               funk,
     640             :                          fd_funk_txn_xid_t const * xid,
     641             :                          fd_funk_rec_key_t const * key,
     642             :                          ulong                     val_align,
     643             :                          ulong                     val_sz,
     644          27 :                          void *                    val ) {
     645          27 :   if( FD_UNLIKELY( !funk           ) ) FD_LOG_ERR(( "NULL funk" ));
     646          27 :   if( FD_UNLIKELY( !xid            ) ) FD_LOG_ERR(( "NULL xid"  ));
     647          27 :   if( FD_UNLIKELY( !key            ) ) FD_LOG_ERR(( "NULL key"  ));
     648          27 :   if( FD_UNLIKELY( !val && val_sz  ) ) FD_LOG_ERR(( "NULL val"  ));
     649             : 
     650          27 :   fd_funk_xid_key_pair_t pair[1];
     651          27 :   fd_funk_txn_xid_copy( pair->xid, xid );
     652          27 :   fd_funk_rec_key_copy( pair->key, key );
     653             : 
     654          27 :   for(;;) {
     655             :     /* See if the record already exists. */
     656          27 :     fd_funk_rec_query_t query[1];
     657          27 :     int err = fd_funk_rec_map_query_try( funk->rec_map, pair, NULL, query, 0 );
     658          27 :     if( err == FD_MAP_SUCCESS ) {
     659          27 :       fd_funk_rec_t * rec = fd_funk_rec_map_query_ele( query );
     660             :       /* Set the value of the record */
     661          27 :       if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) {
     662           0 :         FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" ));
     663           0 :         return err;
     664           0 :       }
     665          27 :       memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz );
     666          27 :       return FD_FUNK_SUCCESS;
     667          27 :     }
     668             : 
     669             :     /* The record doesn't exist. Create it. */
     670           0 :     fd_funk_rec_prepare_t prepare[1];
     671           0 :     fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, key, prepare, &err );
     672           0 :     if( FD_UNLIKELY( !rec ) ) {
     673           0 :       FD_LOG_CRIT(( "fd_funk_rec_prepare returned err=%d", err ));
     674           0 :       return err;
     675           0 :     }
     676             :     /* Set the value of the record */
     677           0 :     if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) {
     678           0 :       FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" ));
     679           0 :       return err;
     680           0 :     }
     681           0 :     memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz );
     682           0 :     fd_funk_rec_publish( funk, prepare );
     683           0 :     return FD_FUNK_SUCCESS;
     684           0 :   }
     685          27 : }

Generated by: LCOV version 1.14