LCOV - code coverage report
Current view: top level - flamenco/runtime/program - fd_program_cache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 238 331 71.9 %
Date: 2025-08-05 05:04:49 Functions: 12 14 85.7 %

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

Generated by: LCOV version 1.14