LCOV - code coverage report
Current view: top level - flamenco/runtime/program - fd_program_cache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 273 369 74.0 %
Date: 2025-09-18 04:41:32 Functions: 14 16 87.5 %

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

Generated by: LCOV version 1.14