LCOV - code coverage report
Current view: top level - flamenco/runtime/program - fd_program_cache.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 1 1 100.0 %
Date: 2025-08-05 05:04:49 Functions: 0 0 -

          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. A future program upgrade / network feature
      60             :    set change could make this program valid again.
      61             : 
      62             :    A key invariant with the program cache design is that it does not
      63             :    evict any entries - it only grows through the lifetime of the
      64             :    client's execution. Because of this invariant, we do not touch cache
      65             :    entries for programs which are closed, and instead let
      66             :    transaction-level account loading and BPF loader program checks
      67             :    catch cases where a user tries to invoke a closed program.
      68             : 
      69             :    Another key invariant is that for any given program, we will insert /
      70             :    update its cache entry at most ONCE per slot, and this will happen
      71             :    the FIRST time the program is referenced in a transaction before it
      72             :    is dispatched to the exec tiles. This is important because it ensures
      73             :    that the replay tile does not race with the exec tiles by updating
      74             :    the cache entry for a program while an exec tile is already executing
      75             :    it. Even if a program is upgraded in the same slot after it has been
      76             :    reverified, the network forbids invoking programs in the same slot as
      77             :    they are deployed / upgraded, so we can wait for a future slot to
      78             :    update the program cache entry when it is invoked next.
      79             : 
      80             :    EDGE CASES (and how we handle them):
      81             :    - Deploying programs
      82             :      - Deploying a program
      83             :          - `fd_program_cache_queue_program_for_reverification()` will
      84             :            not do anything because the program does not exist in the
      85             :            cache yet, and so there is nothing to update. The next time
      86             :            the program is referenced in a transaction,
      87             :            `fd_program_cache_update_program()` will see that the program
      88             :            is missing in the cache and will perform ELF / sBPF
      89             :            verifications and insert the entry into the cache.
      90             :      - Deploying and invoking in the same slot
      91             :          - BPF loader checks will fail because the program account's
      92             :            slot value will be equal to the current slot, which
      93             :            gets caught by the `DelayedVisibility` checks.
      94             :    - Upgrading programs
      95             :       - Upgrading a program
      96             :          - The program account will have been referenced as writable
      97             :            in the transaction, and thus
      98             :            `fd_program_cache_queue_program_for_reverification()` will
      99             :            update the `last_slot_modified` to queue the program
     100             :            for reverification the next time it is referenced in a
     101             :            transaction.
     102             :       - Upgrading and invoking in the same transaction
     103             :         - Same as "deploy" case
     104             :    - Closing programs
     105             :       - Closing a program
     106             :         - We do not touch the program cache here. We let account
     107             :           loading / BPF loader checks handle cases where a user may try
     108             :           to invoke a closed program.
     109             :       - Closing + invoking a program in the same transaction
     110             :          - The program account's state will be set to uninitialized /
     111             :            retracted, so any future instructions that invoke the program
     112             :            will fail when the BPF loader checks the account state.
     113             :       - Closing + invoking a program in separate transactions
     114             :          - The program account's owner will be set to the system program
     115             :            and thus fail account loading checks.
     116             : 
     117             :    TL;DR: Deploys and upgrades are treated the same way - if the cache
     118             :    entry exists, it's queued for reverification. If it doesn't, the
     119             :    program cache will verify and add it to the cache the next time it's
     120             :    invoked. Cases where the program is closed / retracted are not
     121             :    explicitly handled.
     122             : 
     123             :    Q: Is the program cache concurrency-safe?
     124             :    A: Yes. In any given slot, the replay tile first iterates through
     125             :       each transaction in a single-threaded manner. For each
     126             :       transaction, it will read the accounts and will call
     127             :       `fd_program_cache_update_program()` for each program, and then
     128             :       dispatch the transaction to the exec tiles. This means
     129             :       that the first time any program is referenced in any transaction
     130             :       in the slot, the replay tile will update the associated program
     131             :       cache entry before dispatching the transaction to any of the exec
     132             :       tiles. Furthermore, we are not allowed to insert / reverify a
     133             :       program cache entry more than once within a single slot. This
     134             :       guarantees that any read / write accesses for a particular program
     135             :       in the program cache by the exec / writer tiles will occur after
     136             :       the cache entries have been processed by the replay tile in any
     137             :       given slot. Furthermore, if the program was upgraded, the writer
     138             :       tile simply updates a single header in the existing program cache
     139             :       entry `last_slot_modified`, which is behind a blocking write lock.
     140             :       Note that if there is read-write or write-write-contention
     141             :       between two transactions for any accounts, the scheduler will
     142             :       ensure that those two transactions are scheduled and finalized
     143             :       in a serial manner. */
     144             : 
     145             : /* `fd_program_cache_entry` defines the structure for a single
     146             :    program cache entry. */
     147             : struct fd_program_cache_entry {
     148             :   ulong magic;
     149             : 
     150             :    /* For any programs that fail verification, we retain this flag for
     151             :       to prevent any reverification attempts for the remainder of the
     152             :       epoch / until the program is modified again (instead of removing
     153             :       them from the cache). When `failed_verification` is set,
     154             :       the values of all other fields except for
     155             :       `last_slot_verified` are undefined. Any invocations of a
     156             :       program that fails verification will continue to fail until the
     157             :       program is queued for reverification by an eligible BPF loader
     158             :       instruction (e.g. the program is upgraded). */
     159             :    uchar failed_verification;
     160             : 
     161             :    /* Stores the last slot the program was modified. This field is
     162             :       updated at the end of a transaction for a program account that was
     163             :       referenced as a writable account within an instruction. For any
     164             :       programs that are freshly added to the cache, this field is set to
     165             :       0. */
     166             :    ulong last_slot_modified;
     167             : 
     168             :    /* Stores the last slot verification checks were ran for a program.
     169             :       Programs are reverified if they are mentioned in the current
     170             :       transaction, and if one of the following are true:
     171             :       - It is the first time they are referenced in a transaction in the
     172             :         current epoch
     173             :       - The program was a writable account in a transaction
     174             : 
     175             :       We reverify referenced programs at least once every epoch
     176             :       regardless of whether they were modified or not because changes
     177             :       in the active feature set may cause existing deployed programs
     178             :       to be invalided (e.g. stricter ELF / VM / sBPF checks). */
     179             :    ulong last_slot_verified;
     180             : 
     181             :    ulong entry_pc;
     182             :    ulong text_cnt;
     183             :    ulong text_off;
     184             :    ulong text_sz;
     185             : 
     186             :    ulong rodata_sz;
     187             : 
     188             :   /* We keep the pointer to the calldests raw memory around, so that we can easily copy the entire
     189             :      data structures (including the private header) later. */
     190             :    void *                calldests_shmem;
     191             :    fd_sbpf_calldests_t * calldests;
     192             :    uchar *               rodata;
     193             : 
     194             :    /* SBPF version, SIMD-0161 */
     195             :    ulong sbpf_version;
     196             : };
     197             : typedef struct fd_program_cache_entry fd_program_cache_entry_t;
     198             : 
     199             : /* arbitrary unique value, in this case
     200             :    echo -n "fd_program_cache_entry" | sha512sum | head -c 16 */
     201          33 : #define FD_PROGRAM_CACHE_ENTRY_MAGIC 0xb45640baf006ddf6
     202             : 
     203             : FD_PROTOTYPES_BEGIN
     204             : 
     205             : fd_program_cache_entry_t *
     206             : fd_program_cache_entry_new( void *                     mem,
     207             :                             fd_sbpf_elf_info_t const * elf_info,
     208             :                             ulong                      last_slot_modified,
     209             :                             ulong                      last_slot_verified );
     210             : 
     211             : ulong
     212             : fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info );
     213             : 
     214             : /* Loads a single program cache entry for a given pubkey. Returns 0 on
     215             :    success and -1 on failure. On success, `*cache_entry` holds a pointer
     216             :    to the program cache entry. */
     217             : int
     218             : fd_program_cache_load_entry( fd_funk_t const *                 funk,
     219             :                              fd_funk_txn_t const *             funk_txn,
     220             :                              fd_pubkey_t const *               program_pubkey,
     221             :                              fd_program_cache_entry_t const ** cache_entry );
     222             : 
     223             : /* Parses the programdata from a program account. Returns a pointer to
     224             :    the program data and sets `out_program_data_len` on success. Returns
     225             :    NULL on failure or if the program account is not owned by a BPF
     226             :    loader program ID, and leaves `out_program_data_len` in an undefined
     227             :    state. Reasons for failure vary on the loader version. See the
     228             :    respective functions in this file for more details.
     229             : 
     230             :    This is essentially load_program_with_pubkey(), sans the ELF parsing
     231             :    and whatnot which is done in load_program_from_bytes(). */
     232             : uchar const *
     233             : fd_program_cache_get_account_programdata( fd_funk_t const *        funk,
     234             :                                           fd_funk_txn_t const *    funk_txn,
     235             :                                           fd_txn_account_t const * program_acc,
     236             :                                           ulong *                  out_program_data_len,
     237             :                                           fd_spad_t *              runtime_spad );
     238             : 
     239             : /* Updates the program cache for a single program. This function is
     240             :    called for every program that is referenced in a transaction, plus
     241             :    every single account in a lookup table referenced in the transaction.
     242             :    This function...
     243             :    - Accepts a pubkey and reads the programdata from the account
     244             :    - Creates a program cache entry for the program if it doesn't exist
     245             :      in the cache already
     246             :    - Reverifies programs if either...
     247             :       - The program was recently modified in a STRICTLY PRIOR SLOT
     248             :       - The program has not been verified yet for the current epoch
     249             :    - Invalidated programs that fail ELF / sBPF verification
     250             :    - Updates the program cache entry for the program after
     251             :      reverification (syscalls, calldests, etc)
     252             : 
     253             :    With this design, the program cache is designed to only grow as new
     254             :    programs are deployed / invoked. If a program fails verification, it
     255             :    stays in the cache so that repeated calls won't DOS the validator by
     256             :    forcing reverifications (since we won't be able to distinguish failed
     257             :    verifications from new deployments).
     258             : 
     259             :    When a program is reverified (and the cache entry already exists in
     260             :    some ancestor funk transaction), we clone the record down to the
     261             :    current funk transaction and acquire a blocking write lock on the
     262             :    cloned funk record. */
     263             : void
     264             : fd_program_cache_update_program( fd_exec_slot_ctx_t * slot_ctx,
     265             :                                  fd_pubkey_t const *  program_key,
     266             :                                  fd_spad_t *          runtime_spad );
     267             : 
     268             : /* Queues a single program account for reverification. This function
     269             :    queries the cache for an existing entry and queues it for
     270             :    reverification by setting the `last_slot_modified` field in the
     271             :    program cache entry to the current slot. If the cache entry
     272             :    for the program does not exist yet (e.g. newly deployed programs),
     273             :    this function does nothing and instead,
     274             :    `fd_program_cache_publish_failed_verification_rec()` will insert
     275             :    the program into the cache.
     276             : 
     277             :    If the record exists in the program cache, this function will
     278             :    acquire a write-lock on the program cache entry. */
     279             : void
     280             : fd_program_cache_queue_program_for_reverification( fd_funk_t *         funk,
     281             :                                                    fd_funk_txn_t *     funk_txn,
     282             :                                                    fd_pubkey_t const * program_key,
     283             :                                                    ulong               current_slot );
     284             : 
     285             : FD_PROTOTYPES_END
     286             : 
     287             : #endif /* HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h */

Generated by: LCOV version 1.14