LCOV - code coverage report
Current view: top level - flamenco/runtime/program - fd_program_cache.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 7 10 70.0 %
Date: 2025-09-18 04:41:32 Functions: 2 30 6.7 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h
       2             : #define HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h
       3             : 
       4             : #include "../../fd_flamenco_base.h"
       5             : #include "../fd_acc_mgr.h"
       6             : #include "../context/fd_exec_slot_ctx.h"
       7             : #include "../../vm/syscall/fd_vm_syscall.h"
       8             : #include "../fd_system_ids.h"
       9             : 
      10             : /* fd_program_cache contains the core logic for the program cache's
      11             :    behavior, including accesses, insertions, updates, and verifies.
      12             :    Specifically, the program cache operates in a lazy manner where
      13             :    programs are only inserted / reverified when they are referenced
      14             :    in a transaction.
      15             : 
      16             :    The program cache lives in Funk and is fork-aware. The size of the
      17             :    program cache is bounded by the size of the Funk instance.
      18             : 
      19             :    On bootup, the program cache starts out empty. As programs (defined
      20             :    as accounts owned by one of the BPF loaders that can be invoked
      21             :    by the user) are referenced as account keys within transactions
      22             :    (either statically or through address lookup tables),
      23             :    `fd_program_cache_update_program()` accepts a pubkey as input and
      24             :    will attempt to either insert the program into the cache (performing
      25             :    sBPF + ELF validations) if missing, or reverify the program if
      26             :    needed.
      27             : 
      28             :    During transaction execution, we accumulate any program pubkeys that
      29             :    are deployed, extended, or upgraded (basically, any calls to
      30             :    `fd_deploy_program()` in the BPF loaders) since those instructions
      31             :    may have changed the program's executable data (closed / retracted
      32             :    programs also do, but see comments below on how we handle that).
      33             :    After a transaction is executed successfully, we iterate through
      34             :    these accumulated program keys and queue them for reverification.
      35             :    To mark a program for reverification, we simply update the
      36             :    `last_slot_modified` field to the current slot. When the program is
      37             :    next referenced in a transaction, `fd_program_cache_update_program()`
      38             :    will check if the program was modified since the last time it was
      39             :    reverified, and reverify the program accordingly to update the cache
      40             :    entry's sBPF info, calldests, etc. Once the program has been
      41             :    reverified, the entry's `last_slot_verified` field will be updated
      42             :    to the current slot.
      43             : 
      44             :    When a program is reverified and updated in the cache, we clone the
      45             :    record down to the current funk transaction from an ancestor, and
      46             :    then modify the record within the current funk transaction.
      47             : 
      48             :    A program cache entry may also be reverified after crossing an
      49             :    epoch boundary, even if it was not modified recently. This is because
      50             :    the network's active feature set may have changed, and any previously
      51             :    {valid,invalid} programs may now be {valid,invalid}. Therefore,
      52             :    if a program is referenced in a transaction that has not been
      53             :    reverified since the last epoch (regardless of it having been
      54             :    modified or not), it will be reverified and updated.
      55             : 
      56             :    If a program fails verification due to invalid ELF headers / sBPF
      57             :    loading failures, then we update the `failed_verification` tag,
      58             :    set `last_slot_verified` to the current slot, and set
      59             :    `last_slot_modified` to 0. All other fields have undefined value. A
      60             :    future program upgrade / network feature set change could make this
      61             :    program valid again.
      62             : 
      63             :    A key invariant with the program cache design is that it does not
      64             :    evict any entries - it only grows through the lifetime of the
      65             :    client's execution. Because of this invariant, we do not touch cache
      66             :    entries for programs which are closed, and instead let
      67             :    transaction-level account loading and BPF loader program checks
      68             :    catch cases where a user tries to invoke a closed program.
      69             : 
      70             :    Another key invariant is that for any given program, we will insert /
      71             :    update its cache entry at most ONCE per slot, and this will happen
      72             :    the FIRST time the program is referenced in a transaction before it
      73             :    is dispatched to the exec tiles. This is important because it ensures
      74             :    that the replay tile does not race with the exec tiles by updating
      75             :    the cache entry for a program while an exec tile is already executing
      76             :    it. Even if a program is upgraded in the same slot after it has been
      77             :    reverified, the network forbids invoking programs in the same slot as
      78             :    they are deployed / upgraded, so we can wait for a future slot to
      79             :    update the program cache entry when it is invoked next.
      80             : 
      81             :    EDGE CASES (and how we handle them):
      82             :    - Deploying programs
      83             :      - Deploying a program
      84             :          - `fd_program_cache_queue_program_for_reverification()` will
      85             :            not do anything because the program does not exist in the
      86             :            cache yet, and so there is nothing to update. The next time
      87             :            the program is referenced in a transaction,
      88             :            `fd_program_cache_update_program()` will see that the program
      89             :            is missing in the cache and will perform ELF / sBPF
      90             :            verifications and insert the entry into the cache.
      91             :      - Deploying and invoking in the same slot
      92             :          - BPF loader checks will fail because the program account's
      93             :            slot value will be equal to the current slot, which
      94             :            gets caught by the `DelayedVisibility` checks.
      95             :    - Upgrading programs
      96             :       - Upgrading a program
      97             :          - The program account will have been referenced as writable
      98             :            in the transaction, and thus
      99             :            `fd_program_cache_queue_program_for_reverification()` will
     100             :            update the `last_slot_modified` to queue the program
     101             :            for reverification the next time it is referenced in a
     102             :            transaction.
     103             :       - Upgrading and invoking in the same transaction
     104             :         - Same as "deploy" case
     105             :    - Closing programs
     106             :       - Closing a program
     107             :         - We do not touch the program cache here. We let account
     108             :           loading / BPF loader checks handle cases where a user may try
     109             :           to invoke a closed program.
     110             :       - Closing + invoking a program in the same transaction
     111             :          - The program account's state will be set to uninitialized /
     112             :            retracted, so any future instructions that invoke the program
     113             :            will fail when the BPF loader checks the account state.
     114             :       - Closing + invoking a program in separate transactions
     115             :          - The program account's owner will be set to the system program
     116             :            and thus fail account loading checks.
     117             : 
     118             :    TL;DR: Deploys and upgrades are treated the same way - if the cache
     119             :    entry exists, it's queued for reverification. If it doesn't, the
     120             :    program cache will verify and add it to the cache the next time it's
     121             :    invoked. Cases where the program is closed / retracted are not
     122             :    explicitly handled.
     123             : 
     124             :    Q: Is the program cache concurrency-safe?
     125             :    A: Yes. In any given slot, the replay tile first iterates through
     126             :       each transaction in a single-threaded manner. For each
     127             :       transaction, it will read the accounts and will call
     128             :       `fd_program_cache_update_program()` for each program, and then
     129             :       dispatch the transaction to the exec tiles. This means
     130             :       that the first time any program is referenced in any transaction
     131             :       in the slot, the replay tile will update the associated program
     132             :       cache entry before dispatching the transaction to any of the exec
     133             :       tiles. Furthermore, we are not allowed to insert / reverify a
     134             :       program cache entry more than once within a single slot. This
     135             :       guarantees that any read / write accesses for a particular program
     136             :       in the program cache by the exec / writer tiles will occur after
     137             :       the cache entries have been processed by the replay tile in any
     138             :       given slot. Furthermore, if the program was upgraded, the writer
     139             :       tile simply updates a single header in the existing program cache
     140             :       entry `last_slot_modified`, which is behind a blocking write lock.
     141             :       Note that if there is read-write or write-write-contention
     142             :       between two transactions for any accounts, the scheduler will
     143             :       ensure that those two transactions are scheduled and finalized
     144             :       in a serial manner. */
     145             : 
     146             : /* `fd_program_cache_entry` defines the structure for a single
     147             :    program cache entry. */
     148             : struct fd_program_cache_entry {
     149             :   ulong magic;
     150             : 
     151             :    /* For any programs that fail verification, we retain this flag for
     152             :       to prevent any reverification attempts for the remainder of the
     153             :       epoch / until the program is modified again (instead of removing
     154             :       them from the cache). When `failed_verification` is set,
     155             :       the values of all other fields except for
     156             :       `last_slot_verified` are undefined. Any invocations of a
     157             :       program that fails verification will continue to fail until the
     158             :       program is queued for reverification by an eligible BPF loader
     159             :       instruction (e.g. the program is upgraded). */
     160             :    uchar failed_verification;
     161             : 
     162             :    /* Stores the last slot the program was modified. This field is
     163             :       updated at the end of a transaction for a program account that was
     164             :       referenced as a writable account within an instruction. For any
     165             :       programs that are freshly added to the cache, this field is set to
     166             :       0. */
     167             :    ulong last_slot_modified;
     168             : 
     169             :    /* Stores the last slot verification checks were ran for a program.
     170             :       Programs are reverified if they are mentioned in the current
     171             :       transaction, and if one of the following are true:
     172             :       - It is the first time they are referenced in a transaction in the
     173             :         current epoch
     174             :       - The program was a writable account in a transaction
     175             : 
     176             :       We reverify referenced programs at least once every epoch
     177             :       regardless of whether they were modified or not because changes
     178             :       in the active feature set may cause existing deployed programs
     179             :       to be invalided (e.g. stricter ELF / VM / sBPF checks). */
     180             :    ulong last_slot_verified;
     181             : 
     182             :    ulong entry_pc;
     183             :    ulong text_cnt;
     184             :    ulong text_off;
     185             :    ulong text_sz;
     186             : 
     187             :    ulong rodata_sz;
     188             : 
     189             :   /* We use offsets to store the calldests and rodata in order to keep
     190             :      the cache entry gaddr-aware so it can be passed around different
     191             :      tiles. */
     192             :    ulong calldests_shmem_off;
     193             :    ulong rodata_off;
     194             : 
     195             :    /* SBPF version, SIMD-0161 */
     196             :    ulong sbpf_version;
     197             : };
     198             : typedef struct fd_program_cache_entry fd_program_cache_entry_t;
     199             : 
     200             : static inline uchar *
     201          33 : fd_program_cache_get_rodata( fd_program_cache_entry_t const * cache_entry ) {
     202          33 :   return (uchar *)cache_entry + cache_entry->rodata_off;
     203          33 : }
     204             : 
     205             : static inline fd_sbpf_calldests_t *
     206          33 : fd_program_cache_get_calldests_shmem( fd_program_cache_entry_t const * cache_entry ) {
     207          33 :   return (fd_sbpf_calldests_t *)((uchar *)cache_entry + cache_entry->calldests_shmem_off);
     208          33 : }
     209             : 
     210             : static inline fd_sbpf_calldests_t *
     211           0 : fd_program_cache_get_calldests( fd_program_cache_entry_t const * cache_entry ) {
     212           0 :   return fd_sbpf_calldests_join( fd_program_cache_get_calldests_shmem( cache_entry ) );
     213           0 : }
     214             : 
     215             : /* arbitrary unique value, in this case
     216             :    echo -n "fd_program_cache_entry" | sha512sum | head -c 16 */
     217          51 : #define FD_PROGRAM_CACHE_ENTRY_MAGIC 0xb45640baf006ddf6
     218             : 
     219             : FD_PROTOTYPES_BEGIN
     220             : 
     221             : fd_program_cache_entry_t *
     222             : fd_program_cache_entry_new( void *                     mem,
     223             :                             fd_sbpf_elf_info_t const * elf_info,
     224             :                             ulong                      last_slot_modified,
     225             :                             ulong                      last_slot_verified );
     226             : 
     227             : ulong
     228             : fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info );
     229             : 
     230             : /* Gets the program cache funk record key for a given program pubkey. */
     231             : fd_funk_rec_key_t
     232             : fd_program_cache_key( fd_pubkey_t const * pubkey );
     233             : 
     234             : /* Loads a single program cache entry for a given pubkey. Returns 0 on
     235             :    success and -1 on failure. On success, `*cache_entry` holds a pointer
     236             :    to the program cache entry. */
     237             : int
     238             : fd_program_cache_load_entry( fd_funk_t const *                 funk,
     239             :                              fd_funk_txn_t const *             funk_txn,
     240             :                              fd_pubkey_t const *               program_pubkey,
     241             :                              fd_program_cache_entry_t const ** cache_entry );
     242             : 
     243             : /* Parses the programdata from a program account. Returns a pointer to
     244             :    the program data and sets `out_program_data_len` on success. Returns
     245             :    NULL on failure or if the program account is not owned by a BPF
     246             :    loader program ID, and leaves `out_program_data_len` in an undefined
     247             :    state. Reasons for failure vary on the loader version. See the
     248             :    respective functions in this file for more details.
     249             : 
     250             :    This is essentially load_program_with_pubkey(), sans the ELF parsing
     251             :    and whatnot which is done in load_program_from_bytes(). */
     252             : uchar const *
     253             : fd_program_cache_get_account_programdata( fd_funk_t const *        funk,
     254             :                                           fd_funk_txn_t const *    funk_txn,
     255             :                                           fd_txn_account_t const * program_acc,
     256             :                                           ulong *                  out_program_data_len );
     257             : 
     258             : /* Updates the program cache for a single program. This function is
     259             :    called for every program that is referenced in a transaction, plus
     260             :    every single account in a lookup table referenced in the transaction.
     261             :    This function...
     262             :    - Accepts a pubkey and reads the programdata from the account
     263             :    - Creates a program cache entry for the program if it doesn't exist
     264             :      in the cache already
     265             :    - Reverifies programs if either...
     266             :       - The program was recently modified in a STRICTLY PRIOR SLOT
     267             :       - The program has not been verified yet for the current epoch
     268             :    - Invalidated programs that fail ELF / sBPF verification
     269             :    - Updates the program cache entry for the program after
     270             :      reverification (syscalls, calldests, etc)
     271             : 
     272             :    With this design, the program cache is designed to only grow as new
     273             :    programs are deployed / invoked. If a program fails verification, it
     274             :    stays in the cache so that repeated calls won't DOS the validator by
     275             :    forcing reverifications (since we won't be able to distinguish failed
     276             :    verifications from new deployments).
     277             : 
     278             :    When a program is reverified (and the cache entry already exists in
     279             :    some ancestor funk transaction), we clone the record down to the
     280             :    current funk transaction and acquire a blocking write lock on the
     281             :    cloned funk record. */
     282             : void
     283             : fd_program_cache_update_program( fd_exec_slot_ctx_t * slot_ctx,
     284             :                                  fd_pubkey_t const *  program_key,
     285             :                                  fd_spad_t *          runtime_spad );
     286             : 
     287             : /* Queues a single program account for reverification. This function
     288             :    queries the cache for an existing entry and queues it for
     289             :    reverification by setting the `last_slot_modified` field in the
     290             :    program cache entry to the current slot. If the cache entry
     291             :    for the program does not exist yet (e.g. newly deployed programs),
     292             :    this function does nothing and instead,
     293             :    `fd_program_cache_publish_failed_verification_rec()` will insert
     294             :    the program into the cache.
     295             : 
     296             :    If the record exists in the program cache, this function will
     297             :    acquire a write-lock on the program cache entry. */
     298             : void
     299             : fd_program_cache_queue_program_for_reverification( fd_funk_t *         funk,
     300             :                                                    fd_funk_txn_t *     funk_txn,
     301             :                                                    fd_pubkey_t const * program_key,
     302             :                                                    ulong               current_slot );
     303             : 
     304             : FD_PROTOTYPES_END
     305             : 
     306             : #endif /* HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h */

Generated by: LCOV version 1.14