LCOV - code coverage report
Current view: top level - ballet/pack - fd_pack.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 22 32 68.8 %
Date: 2025-01-08 12:08:44 Functions: 1 46 2.2 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_ballet_pack_fd_pack_h
       2             : #define HEADER_fd_src_ballet_pack_fd_pack_h
       3             : 
       4             : /* fd_pack defines methods that prioritizes Solana transactions,
       5             :    selecting a subset (potentially all) and ordering them to attempt to
       6             :    maximize the overall profitability of the validator. */
       7             : 
       8             : #include "../fd_ballet_base.h"
       9             : #include "../txn/fd_txn.h"
      10             : #include "fd_est_tbl.h"
      11             : #include "fd_microblock.h"
      12             : 
      13           0 : #define FD_PACK_ALIGN     (128UL)
      14             : 
      15       43839 : #define FD_PACK_MAX_BANK_TILES 62UL
      16             : 
      17             : /* NOTE: THE FOLLOWING CONSTANTS ARE CONSENSUS CRITICAL AND CANNOT BE
      18             :    CHANGED WITHOUT COORDINATING WITH ANZA. */
      19       15237 : #define FD_PACK_MAX_COST_PER_BLOCK      (48000000UL)
      20         315 : #define FD_PACK_MAX_VOTE_COST_PER_BLOCK (36000000UL)
      21         306 : #define FD_PACK_MAX_WRITE_COST_PER_ACCT (12000000UL)
      22    27145119 : #define FD_PACK_FEE_PER_SIGNATURE           (5000UL) /* In lamports */
      23             : 
      24             : /* Each block is limited to 32k parity shreds.  We don't want pack to
      25             :    produce a block with so many transactions we can't shred it, but the
      26             :    correspondence between transactions and parity shreds is somewhat
      27             :    complicated, so we need to use conservative limits.
      28             : 
      29             :    Except for the final batch in the block, the current version of the
      30             :    shred tile shreds microblock batches of size (25431, 63671] bytes,
      31             :    including the microblock headers, but excluding the microblock count.
      32             :    The worst case size by bytes/parity shred is a 25871 byte microblock
      33             :    batch, which produces 31 parity shreds.  The final microblock batch,
      34             :    however, may be as bad as 48 bytes triggering the creation of 17
      35             :    parity shreds.  This gives us a limit of floor((32k - 17)/31)*25871 +
      36             :    48 = 27,319,824 bytes.
      37             : 
      38             :    To get this right, the pack tile needs to add in the 48-byte
      39             :    microblock headers for each microblock, and we also need to subtract
      40             :    out the tick bytes, which aren't known until PoH initialization is
      41             :    complete.
      42             : 
      43             :    Note that the number of parity shreds in each FEC set is always at
      44             :    least as many as the number of data shreds, so we don't need to
      45             :    consider the data shreds limit. */
      46           3 : #define FD_PACK_MAX_DATA_PER_BLOCK (((32UL*1024UL-17UL)/31UL)*25871UL + 48UL)
      47             : 
      48             : /* Optionally allow up to 1M shreds per block for benchmarking. */
      49           0 : #define LARGER_MAX_DATA_PER_BLOCK  (((32UL*32UL*1024UL-17UL)/31UL)*25871UL + 48UL)
      50             : 
      51             : /* ---- End consensus-critical constants */
      52             : 
      53    27145128 : #define FD_TXN_P_FLAGS_IS_SIMPLE_VOTE   (1U)
      54       25482 : #define FD_TXN_P_FLAGS_SANITIZE_SUCCESS (2U)
      55       17943 : #define FD_TXN_P_FLAGS_EXECUTE_SUCCESS  (4U)
      56             : 
      57             : 
      58             : /* The Solana network and Firedancer implementation details impose
      59             :    several limits on what pack can produce.  These limits are grouped in
      60             :    this one struct fd_pack_limits_t, which is just a convenient way to
      61             :    pass them around.  The limits listed below are arithmetic limits.
      62             :    The limits imposed by practical constraints are almost certainly
      63             :    much, much tighter. */
      64             : struct fd_pack_limits {
      65             :   /* max_{cost, vote_cost}_per_block, max_write_cost_per_acct are
      66             :      consensus-critical limits and must be agreed on cluster-wide.  A
      67             :      block that consumes more than max_cost_per_block cost units
      68             :      (closely related to, but not identical to CUs) in total is invalid.
      69             :      Similarly, a block where the sum of the cost of all vote
      70             :      transactions exceeds max_vote_cost_per_block cost units is invalid.
      71             :      Similarly, a block in where the sum of the cost of all transactions
      72             :      that write to a given account exceeds max_write_cost_per_acct is
      73             :      invalid. */
      74             :   ulong max_cost_per_block;          /* in [0, ULONG_MAX) */
      75             :   ulong max_vote_cost_per_block;     /* in [0, max_cost_per_block] */
      76             :   ulong max_write_cost_per_acct;     /* in [0, max_cost_per_block] */
      77             : 
      78             :   /* max_data_bytes_per_block is derived from consensus-critical limits
      79             :      on the number of shreds in a block, but is not directly enforced.
      80             :      Separation of concerns means that it's not a good idea for pack to
      81             :      know exactly how the block will be shredded, but at the same time,
      82             :      we don't want to end up in a situation where we produced a block
      83             :      that had too many shreds, because the shred tile's only recourse
      84             :      would be to kill the block.  To address this, pack limits the size
      85             :      of the data it puts into the block to a limit that we can prove
      86             :      will never cause the shred tile to produce too many shreds.
      87             : 
      88             :      This limit includes transaction and microblock headers for
      89             :      non-empty microblocks that pack produces. */
      90             :   ulong max_data_bytes_per_block;    /* in [0, ULONG_MAX - 183] */
      91             : 
      92             :   /* max_txn_per_microblock and max_microblocks_per_block are
      93             :      Firedancer-imposed implementation limits to bound the amount of
      94             :      memory consumption that pack uses.  Pack will produce microblocks
      95             :      with no more than max_txn_per_microblock transactions.
      96             :      Additionally, once pack produces max_microblocks_per_block
      97             :      non-empty microblocks in a block, all subsequent attempts to
      98             :      schedule a microblock will return an empty microblock until
      99             :      fd_pack_end_block is called. */
     100             :   ulong max_txn_per_microblock;      /* in [0, 16777216] */
     101             :   ulong max_microblocks_per_block;   /* in [0, 1e12) */
     102             : 
     103             : };
     104             : typedef struct fd_pack_limits fd_pack_limits_t;
     105             : 
     106             : 
     107             : /* Forward declare opaque handle */
     108             : struct fd_pack_private;
     109             : typedef struct fd_pack_private fd_pack_t;
     110             : 
     111             : /* fd_pack_{align,footprint} return the required alignment and
     112             :    footprint in bytes for a region of memory to be used as a pack
     113             :    object.
     114             : 
     115             :    pack_depth sets the maximum number of pending transactions that pack
     116             :    stores and may eventually schedule.  pack_depth must be at least 4.
     117             : 
     118             :    bank_tile_cnt sets the number of bank tiles to which this pack object
     119             :    can schedule transactions.  bank_tile_cnt must be in [1,
     120             :    FD_PACK_MAX_BANK_TILES].
     121             : 
     122             :    limits sets various limits for the blocks and microblocks that pack
     123             :    can produce. */
     124             : 
     125           0 : FD_FN_CONST static inline ulong fd_pack_align       ( void ) { return FD_PACK_ALIGN; }
     126             : 
     127             : FD_FN_PURE ulong
     128             : fd_pack_footprint( ulong                    pack_depth,
     129             :                    ulong                    bank_tile_cnt,
     130             :                    fd_pack_limits_t const * limits );
     131             : 
     132             : 
     133             : /* fd_pack_new formats a region of memory to be suitable for use as a
     134             :    pack object.  mem is a non-NULL pointer to a region of memory in the
     135             :    local address space with the required alignment and footprint.
     136             :    pack_depth, bank_tile_cnt, and limits are as above.  rng is a local
     137             :    join to a random number generator used to perturb estimates.
     138             : 
     139             :    Returns `mem` (which will be properly formatted as a pack object) on
     140             :    success and NULL on failure.  Logs details on failure.  The caller
     141             :    will not be joined to the pack object when this function returns. */
     142             : void * fd_pack_new( void * mem,
     143             :     ulong pack_depth, ulong bank_tile_cnt, fd_pack_limits_t const * limits,
     144             :     fd_rng_t * rng );
     145             : 
     146             : /* fd_pack_join joins the caller to the pack object.  Every successful
     147             :    join should have a matching leave.  Returns mem. */
     148             : fd_pack_t * fd_pack_join( void * mem );
     149             : 
     150             : 
     151             : /* fd_pack_avail_txn_cnt returns the number of transactions that this
     152             :    pack object has available to schedule but that have not been
     153             :    scheduled yet. pack must be a valid local join.  The return value
     154             :    will be in [0, pack_depth). */
     155             : 
     156             : /* For performance reasons, implement this here.  The offset is STATIC_ASSERTed
     157             :    in fd_pack.c. */
     158       43179 : #define FD_PACK_PENDING_TXN_CNT_OFF 64
     159             : FD_FN_PURE static inline ulong
     160       43179 : fd_pack_avail_txn_cnt( fd_pack_t const * pack ) {
     161       43179 :   return *((ulong const *)((uchar const *)pack + FD_PACK_PENDING_TXN_CNT_OFF));
     162       43179 : }
     163             : 
     164             : /* fd_pack_current_block_cost returns the number of CUs that have been
     165             :    scheduled in the current block, net of any rebates.  It should be
     166             :    between 0 and the specified value of max_cost_per_block, but it can
     167             :    be slightly higher due to temporary cost model nonsense.  Due to
     168             :    rebates, this number may decrease as the block progresses.  pack must
     169             :    be a valid local join. */
     170             : FD_FN_PURE ulong fd_pack_current_block_cost( fd_pack_t const * pack );
     171             : 
     172             : /* fd_pack_bank_tile_cnt: returns the value of bank_tile_cnt provided in
     173             :    pack when the pack object was initialized with fd_pack_new.  pack
     174             :    must be a valid local join.  The result will be in [1,
     175             :    FD_PACK_MAX_BANK_TILES]. */
     176             : FD_FN_PURE ulong fd_pack_bank_tile_cnt( fd_pack_t const * pack );
     177             : 
     178             : /* fd_pack_set_block_limits: Updates the limits provided fd_pack_new to
     179             :    the new values.  Any future microblocks produced by this pack object
     180             :    will not cause a block to have more than max_microblocks_per_block
     181             :    non-empty microblocks or more than max_data_bytes_per_block data
     182             :    bytes (counting microblock headers as before).  Limits are inclusive,
     183             :    as per usual (i.e. a block may have exactly
     184             :    max_microblocks_per_block microblocks, but not more).  pack must be
     185             :    a valid local join.
     186             : 
     187             :    The typical place to call this is immediately after
     188             :    fd_pack_end_block; if this is called after some microblocks have been
     189             :    produced for the current block, and the current block already exceeds
     190             :    the limits, all the remaining microblocks in the block will be empty,
     191             :    but the call is valid. */
     192             : void fd_pack_set_block_limits( fd_pack_t * pack, ulong max_microblocks_per_block, ulong max_data_bytes_per_block );
     193             : 
     194             : /* Return values for fd_pack_insert_txn_fini:  Non-negative values
     195             :    indicate the transaction was accepted and may be returned in a future
     196             :    microblock.  Negative values indicate that the transaction was
     197             :    rejected and will never be returned in a future microblock.
     198             :    Transactions can be rejected through no fault of their own, so it
     199             :    doesn't necessarily imply bad behavior.
     200             : 
     201             :    The non-negative (success) codes are essentially a bitflag of two
     202             :    bits:
     203             :     * whether the transaction met the criteria for a simple vote or not,
     204             :     * whether this transaction replaced a previously accepted, low
     205             :       priority transaction, rather than being accepted in addition to all
     206             :       the previously accepted transactions.  Since pack maintains a heap
     207             :       with a fixed max size of pack_depth, replacing transaction is
     208             :       necessary whenever the heap is full.
     209             : 
     210             :    The negative (failure) codes are a normal enumeration (not a
     211             :    bitflag).
     212             :     * PRIORITY: pack's heap was full and the transaction's priority was
     213             :       lower than the worst currently accepted transaction.
     214             :     * DUPLICATE: the transaction is a duplicate of a currently accepted
     215             :       transaction.
     216             :     * UNAFFORDABLE: the fee payer could not afford the transaction fee
     217             :       (not yet implemented).
     218             :     * ADDR_LUT: the transaction tried to load an account from an address
     219             :       lookup table, which is not yet supported.
     220             :     * EXPIRED: the transaction was already expired upon insertion based
     221             :       on the provided value of expires_at compared to the last call to
     222             :       fd_pack_expire_before.
     223             :     * TOO_LARGE: the transaction requested too many CUs and would never
     224             :       be scheduled if it had been accepted.
     225             :     * ACCOUNT_CNT: the transaction tried to load more than 64 account
     226             :       addresses.
     227             :     * DUPLICATE_ACCT: the transaction included an account address twice
     228             :       in its list of account addresses to load.
     229             :     * ESTIMATION_FAIL: estimation of the transaction's compute cost and
     230             :       fee failed, typically because the transaction contained a
     231             :       malformed ComputeBudgetProgram instruction.
     232             :     * WRITES_SYSVAR: the transaction attempts to write-lock a sysvar.
     233             :       Write-locking a sysvar can cause heavy contention.  Agave
     234             :       solves this by downgrading these to read locks, but we instead
     235             :       solve it by refusing to pack such transactions.
     236             : 
     237             :     NOTE: The corresponding enum in metrics.xml must be kept in sync
     238             :     with any changes to these return values. */
     239           0 : #define FD_PACK_INSERT_ACCEPT_VOTE_REPLACE     (  3)
     240      494592 : #define FD_PACK_INSERT_ACCEPT_NONVOTE_REPLACE  (  2)
     241       37596 : #define FD_PACK_INSERT_ACCEPT_VOTE_ADD         (  1)
     242    13040262 : #define FD_PACK_INSERT_ACCEPT_NONVOTE_ADD      (  0)
     243           0 : #define FD_PACK_INSERT_REJECT_PRIORITY         ( -1)
     244           3 : #define FD_PACK_INSERT_REJECT_DUPLICATE        ( -2)
     245           0 : #define FD_PACK_INSERT_REJECT_UNAFFORDABLE     ( -3)
     246             : #define FD_PACK_INSERT_REJECT_ADDR_LUT         ( -4)
     247          12 : #define FD_PACK_INSERT_REJECT_EXPIRED          ( -5)
     248           0 : #define FD_PACK_INSERT_REJECT_TOO_LARGE        ( -6)
     249           3 : #define FD_PACK_INSERT_REJECT_ACCOUNT_CNT      ( -7)
     250           3 : #define FD_PACK_INSERT_REJECT_DUPLICATE_ACCT   ( -8)
     251           3 : #define FD_PACK_INSERT_REJECT_ESTIMATION_FAIL  ( -9)
     252          87 : #define FD_PACK_INSERT_REJECT_WRITES_SYSVAR    (-10)
     253           0 : #define FD_PACK_INSERT_REJECT_BUNDLE_BLACKLIST (-11)
     254             : 
     255             : /* The FD_PACK_INSERT_{ACCEPT, REJECT}_* values defined above are in the
     256             :    range [-FD_PACK_INSERT_RETVAL_OFF,
     257             :    -FD_PACK_INSERT_RETVAL_OFF+FD_PACK_INSERT_RETVAL_CNT ) */
     258           0 : #define FD_PACK_INSERT_RETVAL_OFF 11
     259           0 : #define FD_PACK_INSERT_RETVAL_CNT 15
     260             : 
     261             : FD_STATIC_ASSERT( FD_PACK_INSERT_REJECT_BUNDLE_BLACKLIST>=-FD_PACK_INSERT_RETVAL_OFF, pack_retval );
     262             : FD_STATIC_ASSERT( FD_PACK_INSERT_ACCEPT_VOTE_REPLACE<FD_PACK_INSERT_RETVAL_CNT-FD_PACK_INSERT_RETVAL_OFF, pack_retval );
     263             : 
     264             : /* fd_pack_insert_txn_{init,fini,cancel} execute the process of
     265             :    inserting a new transaction into the pool of available transactions
     266             :    that may be scheduled by the pack object.
     267             : 
     268             :    fd_pack_insert_txn_init returns a piece of memory from the txnmem
     269             :    region where the transaction should be stored.  The lifetime of this
     270             :    memory is managed by fd_pack as explained below.
     271             : 
     272             :    Every call to fd_pack_insert_init must be paired with a call to
     273             :    exactly one of _fini or _cancel.  Calling fd_pack_insert_txn_fini
     274             :    finalizes the transaction insert process and makes the newly-inserted
     275             :    transaction available for scheduling.  Calling
     276             :    fd_pack_insert_txn_cancel aborts the transaction insertion process.
     277             :    The txn pointer passed to _fini or _cancel must come from the most
     278             :    recent call to _init.
     279             : 
     280             :    The caller of these methods should not retain any read or write
     281             :    interest in the transaction after _fini or _cancel have been called.
     282             : 
     283             :    expires_at (for _fini only) bounds the lifetime of the inserted
     284             :    transaction.  No particular unit is prescribed, and it need not be
     285             :    higher than the previous call to txn_fini.  If fd_pack_expire_before
     286             :    has been previously called with a value larger (strictly) than the
     287             :    provided expires_at, the transaction will be rejected with EXPIRED.
     288             :    See fd_pack_expire_before for more details.
     289             : 
     290             :    pack must be a local join of a pack object.  From the caller's
     291             :    perspective, these functions cannot fail, though pack may reject a
     292             :    transaction for a variety of reasons.  fd_pack_insert_txn_fini
     293             :    returns one of the FD_PACK_INSERT_ACCEPT_* or FD_PACK_INSERT_REJECT_*
     294             :    codes explained above.
     295             :  */
     296             : fd_txn_e_t * fd_pack_insert_txn_init  ( fd_pack_t * pack                                     );
     297             : int          fd_pack_insert_txn_fini  ( fd_pack_t * pack, fd_txn_e_t * txn, ulong expires_at );
     298             : void         fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_e_t * txn                   );
     299             : 
     300             : 
     301             : /* fd_pack_schedule_next_microblock schedules transactions to form a
     302             :    microblock, which is a set of non-conflicting transactions.
     303             : 
     304             :    pack must be a local join of a pack object.  Transactions part of the
     305             :    scheduled microblock are copied to out in no particular order.  The
     306             :    cumulative cost of these transactions will not exceed total_cus, and
     307             :    the number of transactions will not exceed the value of
     308             :    max_txn_per_microblock given in fd_pack_new.
     309             : 
     310             :    The requested_cus field of each transaction will be populated with
     311             :    the requested execution CUs, which is different from the total cost
     312             :    CUs used by `total_cus`.  The flags field will be set to 0 or
     313             :    FD_TXN_P_FLAGS_IS_SIMPLE_VOTE.
     314             : 
     315             :    The block will not contain more than
     316             :    vote_fraction*max_txn_per_microblock votes, and votes in total will
     317             :    not consume more than vote_fraction*total_cus of the microblock.
     318             : 
     319             :    Returns the number of transactions in the scheduled microblock.  The
     320             :    return value may be 0 if there are no eligible transactions at the
     321             :    moment. */
     322             : 
     323             : ulong fd_pack_schedule_next_microblock( fd_pack_t * pack, ulong total_cus, float vote_fraction, ulong bank_tile, fd_txn_p_t * out );
     324             : 
     325             : 
     326             : /* fd_pack_rebate_cus adjusts the compute unit accounting for the
     327             :    specified transactions to take into account the actual consumed CUs
     328             :    after execution.  When a transaction is scheduled by
     329             :    schedule_next_microblock, pack assumes that it uses all the CUs it
     330             :    requests for the purposes of several CU limits.  If it doesn't use
     331             :    all the requested CUs, this function "rebates" them to pack so that
     332             :    they can be consumed by a different transaction in the block.
     333             : 
     334             :    pack must be a valid local join of a pack object.  txns, indexed [0,
     335             :    txn_cnt), should point to the first of txn_cnt transactions scheduled
     336             :    in a microblock by pack.  Although txns does not need to be the same
     337             :    specific pointer passed to out in a call to schedule_next_microblock,
     338             :    it should point to the same data as returned by a call to
     339             :    schedule_next_microblock, other than flags and executed_cus fields.
     340             :    Similarly, txn_cnt should be the return value of the corresponding
     341             :    prior call to schedule_next_microblock.  txn_cnt==0 is okay and a
     342             :    no-op.  txns==NULL is okay if txn_cnt==0.
     343             : 
     344             :    This function reads the requested_cus and executed_cus fields of the
     345             :    transactions in txns.  It expects that both refer to just execution
     346             :    CUs, and do not include e.g. write lock cost units.
     347             : 
     348             :    IMPORTANT: CU limits are reset at the end of each block, so this
     349             :    should not be called for transactions from a prior block.
     350             :    Specifically, there must not be a call to fd_pack_end_block between
     351             :    the call to schedule_next_microblock this is paired with and the call
     352             :    to rebate_cus.
     353             : 
     354             :    This function operates independently of microblock_complete.  In
     355             :    general, you probably need to call both.  microblock_complete must be
     356             :    called before scheduling another microblock to that bank tile, while
     357             :    rebate_cus is optional and has much more relaxed ordering
     358             :    constraints.  The restriction about intervening calls to end_block
     359             :    and that this must come after schedule_next_microblock are the only
     360             :    ordering constraints. */
     361             : void fd_pack_rebate_cus( fd_pack_t * pack, fd_txn_p_t const * txns, ulong txn_cnt );
     362             : 
     363             : /* fd_pack_microblock_complete signals that the bank_tile with index
     364             :    bank_tile has completed its previously scheduled microblock.  This
     365             :    permits the scheduling of transactions that conflict with the
     366             :    previously scheduled microblock.  It is safe to call this multiple
     367             :    times after a microblock or even if bank_tile does not have a
     368             :    previously scheduled; in this case, the function will return 0 and
     369             :    act as a no-op.  Returns 1 if the bank_tile had an outstanding,
     370             :    previously scheduled microblock to mark as completed. */
     371             : int fd_pack_microblock_complete( fd_pack_t * pack, ulong bank_tile );
     372             : 
     373             : /* fd_pack_expire_before deletes all available transactions with
     374             :    expires_at values strictly less than expire_before.  pack must be a
     375             :    local join of a pack object.  Returns the number of transactions
     376             :    deleted.  Subsequent calls to fd_pack_expire_before with the same or
     377             :    a smaller value are no-ops. */
     378             : ulong fd_pack_expire_before( fd_pack_t * pack, ulong expire_before );
     379             : 
     380             : /* fd_pack_delete_txn removes a transaction (identified by its first
     381             :    signature) from the pool of available transactions.  Returns 1 if the
     382             :    transaction was found (and then removed) and 0 if not. */
     383             : int fd_pack_delete_transaction( fd_pack_t * pack, fd_ed25519_sig_t const * sig0 );
     384             : 
     385             : /* fd_pack_end_block resets some state to prepare for the next block.
     386             :    Specifically, the per-block limits are cleared and transactions in
     387             :    the microblocks scheduled after the call to this function are allowed
     388             :    to conflict with transactions in microblocks scheduled before the
     389             :    call to this function, even within gap microblocks. */
     390             : void fd_pack_end_block( fd_pack_t * pack );
     391             : 
     392             : 
     393             : /* fd_pack_clear_all resets the state associated with this pack object.
     394             :    All pending transactions are removed from the pool of available
     395             :    transactions and all limits are reset. */
     396             : void fd_pack_clear_all( fd_pack_t * pack );
     397             : 
     398             : 
     399             : /* fd_pack_metrics_write writes period metric values to the metrics
     400             :    system.  pack must be a valid local join. */
     401             : void
     402             : fd_pack_metrics_write( fd_pack_t const * pack );
     403             : 
     404             : 
     405             : /* fd_pack_leave leaves a local join of a pack object.  Returns pack. */
     406             : void * fd_pack_leave(  fd_pack_t * pack );
     407             : /* fd_pack_delete unformats a memory region used to store a pack object
     408             :    and returns ownership of the memory to the caller.  Returns mem. */
     409             : void * fd_pack_delete( void      * mem  );
     410             : 
     411             : /* fd_pack_verify (for debugging use primarily) checks to ensure several
     412             :    invariants are satisfied.  scratch must point to the first byte of a
     413             :    piece of memory meeting the same alignment and footprint constraints
     414             :    as pack.  Returns 0 on success and a negative value on failure
     415             :    (logging a warning with details). */
     416             : int fd_pack_verify( fd_pack_t * pack, void * scratch );
     417             : 
     418             : FD_PROTOTYPES_END
     419             : #endif /* HEADER_fd_src_ballet_pack_fd_pack_h */

Generated by: LCOV version 1.14